c730cfaa45
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
62 lines
2.6 KiB
Markdown
62 lines
2.6 KiB
Markdown
# 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 -->
|