From 57a597fe5af806047ba328c6c6e93a7285a560c5 Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Thu, 9 Jul 2026 05:40:27 +0000 Subject: [PATCH] feat: Self-register Wild Central on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On startup, Wild Central registers its own domain (from config cloud.central.domain) as an HTTP service with itself. This creates the DNS entry, proxy route, and TLS cert needed to access the Central UI via its domain name. Registration is idempotent — safe on every restart. Skips silently if no domain is configured. Logs a warning if registration fails. Verified: after startup, /api/v1/services shows wild-central registered with domain central.payne.io. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/api/v1/handlers.go | 34 ++++++++++++++++++++++++++++++++++ main.go | 3 +++ 2 files changed, 37 insertions(+) diff --git a/internal/api/v1/handlers.go b/internal/api/v1/handlers.go index 7433271..ba85a64 100644 --- a/internal/api/v1/handlers.go +++ b/internal/api/v1/handlers.go @@ -97,6 +97,40 @@ func envOrDefault(key, defaultVal string) string { return defaultVal } +// RegisterSelf registers Wild Central's own UI as a service so it gets +// DNS + proxy + TLS via its own networking stack. Idempotent — safe to +// call on every startup. Skips silently if no domain is configured. +func (api *API) RegisterSelf(port int) { + globalConfigPath := filepath.Join(api.dataDir, "config.yaml") + globalCfg, err := config.LoadGlobalConfig(globalConfigPath) + if err != nil { + return + } + + domain := globalCfg.Cloud.Central.Domain + if domain == "" { + return + } + + svc := services.Service{ + Name: "wild-central", + Source: "wild-central", + Domain: domain, + Backend: services.Backend{ + Address: fmt.Sprintf("127.0.0.1:%d", port), + Type: services.BackendHTTP, + }, + Reach: services.ReachInternal, + TLS: services.TLSTerminate, + } + + if err := api.services.Register(svc); err != nil { + slog.Warn("failed to self-register", "domain", domain, "error", err) + } else { + slog.Info("registered wild-central", "domain", domain) + } +} + // StartDDNS loads DDNS config and starts the background goroutine. func (api *API) StartDDNS(ctx gocontext.Context) { api.ctx = ctx diff --git a/main.go b/main.go index 76c9457..8df1998 100644 --- a/main.go +++ b/main.go @@ -150,6 +150,9 @@ func main() { fmt.Sscanf(v, "%d", &port) } + // Self-register Wild Central's own domain (idempotent, skips if no domain configured) + api.RegisterSelf(port) + addr := fmt.Sprintf("%s:%d", host, port) slog.Info("wild-central started", "addr", addr, "version", Version)