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:
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/alexei/docker-watcher/internal/config"
|
||||
"github.com/alexei/docker-watcher/internal/crypto"
|
||||
"github.com/alexei/docker-watcher/internal/deployer"
|
||||
"github.com/alexei/docker-watcher/internal/dns"
|
||||
"github.com/alexei/docker-watcher/internal/docker"
|
||||
"github.com/alexei/docker-watcher/internal/events"
|
||||
"github.com/alexei/docker-watcher/internal/health"
|
||||
@@ -192,10 +193,23 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize DNS provider from settings (nil for wildcard mode).
|
||||
dnsProvider := initDNSProvider(settings, encKey)
|
||||
if dnsProvider != nil {
|
||||
dep.SetDNSProvider(dnsProvider)
|
||||
proxyManager.SetDNSProvider(dnsProvider)
|
||||
slog.Info("DNS provider initialized", "provider", settings.DNSProvider)
|
||||
}
|
||||
|
||||
// Build API server.
|
||||
apiServer := api.NewServer(db, dockerClient, npmClient, dep, webhookHandler, eventBus, encKey)
|
||||
apiServer.SetStaleScanner(staleScanner)
|
||||
apiServer.SetProxyManager(proxyManager)
|
||||
apiServer.SetDNSProvider(dnsProvider)
|
||||
apiServer.SetDNSProviderChangedCallback(func(provider dns.Provider) {
|
||||
dep.SetDNSProvider(provider)
|
||||
proxyManager.SetDNSProvider(provider)
|
||||
})
|
||||
router := apiServer.Router()
|
||||
|
||||
// Serve embedded static files for the SPA frontend.
|
||||
@@ -309,3 +323,30 @@ func ensureDefaultAdmin(db *store.Store) error {
|
||||
slog.Info("default admin user created", "username", "admin")
|
||||
return nil
|
||||
}
|
||||
|
||||
// initDNSProvider creates a DNS provider from settings. Returns nil for wildcard mode.
|
||||
func initDNSProvider(settings store.Settings, encKey [32]byte) dns.Provider {
|
||||
if settings.WildcardDNS || settings.DNSProvider == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
token := settings.CloudflareAPIToken
|
||||
if token != "" {
|
||||
decrypted, err := crypto.Decrypt(encKey, token)
|
||||
if err != nil {
|
||||
slog.Error("dns: failed to decrypt API token", "error", err)
|
||||
return nil
|
||||
}
|
||||
token = decrypted
|
||||
}
|
||||
|
||||
provider, err := dns.NewProvider(settings.DNSProvider, dns.Config{
|
||||
Token: token,
|
||||
ZoneID: settings.CloudflareZoneID,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("dns: failed to create provider", "error", err)
|
||||
return nil
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user