feat: Cloudflare DNS management with automatic record sync
Add flexible DNS management to Docker Watcher. By default, wildcard DNS is assumed (current behavior). When disabled, users can configure a Cloudflare DNS provider with API token and zone selection. DNS A records are automatically created/updated/deleted in sync with proxy consumers (deployed instances and standalone proxies). - Settings: wildcard_dns toggle, dns_provider, cloudflare credentials - Cloudflare client: Provider interface with EnsureRecord/DeleteRecord/ListRecords - DNS lifecycle hooks in deployer and proxy manager (best-effort) - Settings UI: DNS config section with provider picker, zone selector, test button - DNS Records page at /dns with filtering, sync status, reconciliation - Records visible in both wildcard and managed modes - Cleanup on provider change: removes old records when switching modes
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Phase 2: Cloudflare DNS Client
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Create an `internal/dns` package with a `Provider` interface and a Cloudflare implementation
|
||||
using the Cloudflare API v4 (direct HTTP, no SDK).
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Define `Provider` interface in `internal/dns/provider.go`
|
||||
- `EnsureRecord(ctx, fqdn, ip) error` — create or update A record
|
||||
- `DeleteRecord(ctx, fqdn) error` — delete A record if exists
|
||||
- `ListRecords(ctx) ([]Record, error)` — list all A records in the zone
|
||||
- `Record` struct: ID, FQDN, Type, Content (IP), Proxied, TTL
|
||||
- [ ] Task 2: Create `internal/dns/cloudflare.go` — Cloudflare implementation
|
||||
- HTTP client with `Authorization: Bearer <token>` header
|
||||
- Base URL: `https://api.cloudflare.com/client/v4`
|
||||
- `EnsureRecord`: GET records by name, create if missing, update if IP differs
|
||||
- `DeleteRecord`: GET record by name, DELETE if found
|
||||
- `ListRecords`: GET all A records in zone
|
||||
- `ListZones`: GET zones for the token (for zone picker)
|
||||
- `TestConnection`: verify token works (GET /user/tokens/verify)
|
||||
- [ ] Task 3: Create `internal/dns/dns.go` — factory function
|
||||
- `NewProvider(providerName, config) (Provider, error)`
|
||||
- Config struct with token, zoneID
|
||||
- Returns `nil, nil` when providerName is empty (wildcard mode)
|
||||
- [ ] Task 4: Wire DNS test/zones endpoints in `internal/api/settings.go`
|
||||
- `POST /api/settings/dns/test` — create temp Cloudflare client, call TestConnection
|
||||
- `GET /api/settings/dns/zones` — create temp client, call ListZones
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/dns/provider.go` — interface + Record type
|
||||
- `internal/dns/cloudflare.go` — Cloudflare implementation
|
||||
- `internal/dns/dns.go` — factory function
|
||||
- `internal/api/settings.go` — wire test/zones endpoints to real client
|
||||
|
||||
## Acceptance Criteria
|
||||
- Provider interface defined with EnsureRecord, DeleteRecord, ListRecords
|
||||
- Cloudflare client makes correct API calls with proper auth headers
|
||||
- EnsureRecord is idempotent (create if missing, update if changed, no-op if same)
|
||||
- DeleteRecord is idempotent (no error if record doesn't exist)
|
||||
- ListZones returns zone ID + name pairs
|
||||
- TestConnection returns success/failure
|
||||
|
||||
## Notes
|
||||
- Cloudflare API v4 docs: zones endpoint, dns_records endpoint
|
||||
- Use `context.Context` for timeout control on all HTTP calls
|
||||
- A records only (type "A"), TTL=1 (auto), proxied=false (DNS only, not CF proxy)
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
Reference in New Issue
Block a user