New dedicated Certificates page in Wild Central UI showing:
- All tracked certs (central, wildcard, per-service)
- Status (valid with days remaining, or missing)
- Per-domain Provision button
- Renew All button
Cert provisioning is now user-initiated only — reconciliation logs
warnings about missing certs but does NOT auto-provision. This
prevents unexpected wildcard cert requests on startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API changes:
- /api/v1/cert/status now returns all relevant certs (central,
wildcard, per-service) with existence status and expiry
- /api/v1/cert/provision accepts ?domain= param for per-domain
provisioning
- Removed stale syncHAProxy references, uses reconcileNetworking
UI changes:
- Added TLS Certificates card to Cloudflare page showing cert status
for each domain with provision/renew actions
- Updated useCert hook and cert API types for new response format
Next: extract into dedicated Certificates page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HAProxy validation fails if the config references cert files that
don't exist (wildcard.pem for L7 routes, domain.pem for central TLS).
Now reconciliation checks for cert existence before including:
- L7 HTTP routes: only generated when wildcard cert exists
- Central domain TLS frontend: only generated when domain cert exists
This ensures HAProxy never enters a failure state due to missing
certs. Once certs are provisioned (via certbot), the next
reconciliation picks them up automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the NATS server fails to start (stale lock files, corrupt store
from a crash), automatically clean the data directory and retry once.
This prevents the frustrating "NATS server failed to start" error
after unclean shutdowns.
The NATS KV data (registered services) is also stored as YAML files
in the services directory, so cleaning the NATS store loses nothing
— services are re-registered on the next startup or API call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Services registered via the service API have a domain but no internal
domain (that's a Wild Cloud instance concept). The dnsmasq generator
was producing broken entries like `local=//` and `address=//`.
Fix: skip internal domain entries when InternalDomain is empty, in
both config.go and config_modular.go. Also fix DNS entries to point
to Central's IP (where HAProxy listens) rather than the backend
address — all traffic flows through Central.
Added 3 tests covering: service-only, mixed services+instances.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When HTTP services are registered (where Central terminates TLS),
reconciliation now checks for and provisions TLS certificates:
- Derives gateway domain from central domain (e.g., payne.io)
- Checks for existing wildcard cert (*.payne.io)
- If no wildcard exists and Cloudflare token + operator email are
configured, provisions one via certbot DNS-01
- For services outside the gateway domain, provisions individual certs
- Services with TLS passthrough (k8s) are skipped (backend handles TLS)
- Graceful: skips silently if no Cloudflare token or email configured
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace "Instance Routes" with "Registered Services" on the Central
Ingress page. Services are fetched from /api/v1/services and display
name, domain, backend, source, reach level, and backend type.
Central no longer manages instances directly — it shows services
registered by Wild Cloud, Wild Works, and itself.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Fix config endpoint to return wrapped { configured, config } response
that the web UI expects. Fix services handler to return stored version
with defaults applied.
New tests:
- GetGlobalConfig: wrapped response, empty/not-configured case
- GetGlobalSecrets: leaf redaction preserves structure, raw mode
- UpdateGlobalConfig: write and verify
- CloudflareVerify: no-token case
- Services: register, list, get, update, deregister, TCP passthrough
defaults, validation errors
15 new test cases, all passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The secrets endpoint was flattening nested maps (e.g., cloudflare.apiToken)
to a single "********" string. The UI checks for nested keys to determine
if credentials are configured, so the structure must be preserved.
Now redactSecrets() recursively walks the map and only redacts leaf values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VITE_API_BASE_URL must be empty (relative URLs) so API calls go to
the same origin the browser loaded the page from. Setting it to
http://localhost:15055 breaks when accessing via LAN hostname since
the browser resolves localhost to its own machine, not the server.
The Go backend handles /api/v1/* routes before the Vite proxy
catch-all, so relative URLs work correctly in dev mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add /api/v1/cloudflare/verify, /api/v1/cloudflare/zone endpoints
so the Central UI's Cloudflare page can verify tokens and manage
zone configuration. This was a missing endpoint from the extraction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this, the frontend handler tries to serve static files from
/var/www/html/wild-central instead of proxying to the Vite dev server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vite dev server pinned to port 5174 (avoids conflict with Wild Cloud on 5173)
- .envrc updated with WILD_CENTRAL_VITE_URL=http://localhost:5174
- API base URL set to http://localhost:15055 via .env
- Page title changed to "Wild Central"
Restart air and the Vite dev server to pick up these changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configure Wild Central for development alongside Wild Cloud:
- Port 15055 (avoids conflict with Wild Cloud on 5055)
- NATS port 14222 (avoids conflict with production NATS)
- Data dir shared with wild-cloud-redmond-data
- Air config for live reloading
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract Central pages from the wild-cloud web app into a standalone
React app for Wild Central. Includes:
- Central overview, DNS, DHCP, Firewall, VPN, Ingress, CrowdSec pages
- Simplified sidebar with Central-only navigation
- Branding updated to "Wild Central"
- All Cloud-specific pages, components, hooks, and API services removed
- TypeScript type-check and production build pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a service is registered, updated, or deregistered, Central now
automatically regenerates HAProxy routes and dnsmasq DNS entries to
match the current set of registered services.
- L4 tcp-passthrough services (Wild Cloud) → HAProxy SNI routes
- L7 http services (Wild Works) → HAProxy Host-based reverse proxy
- All services with reach != off → dnsmasq DNS entries
- SSE events broadcast on config changes
- Reconciliation runs asynchronously (non-blocking)
This closes the loop: service registration is no longer inert — it
actually updates the networking stack.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default paths for dnsmasq and nftables config files should match
the existing production layout (wild-cloud.conf, wild-cloud.nft) so
Wild Central works as a drop-in when reading from the production data
directory. These can be overridden via env vars for fresh installs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow overriding the default API port (5055) for development and
testing when the production daemon is already running.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document the project overview, architecture, development commands,
environment variables, and the service registration API contract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port Castle's tunnel config generator to Go. Central can now manage
cloudflared ingress configuration for public service exposure.
Services with reach=public get projected at <subdomain>.<publicDomain>
with Host/SNI rewriting to the internal domain so the gateway's wildcard
cert validates and existing routing works unchanged.
- Locally-managed config.yml (not remotely-managed token tunnel)
- Public surface is exactly the set of reach=public services
- Auto-removes config when tunnel is disabled or no public services
- 7 tests covering basic generation, subdomain override, edge cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend HAProxy config generation to support L7 HTTP routes alongside
existing L4 SNI passthrough routes. This enables Wild Works services
to be reverse-proxied with TLS termination by Central while Wild Cloud
k8s instances continue using L4 SNI passthrough.
- Add HTTPRoute type: domain, backend address, optional health path
- Add GenerateWithOpts for full config options (HTTP routes, wildcard cert)
- L4 SNI frontend routes known Cloud domains to k8s LBs; unknown SNIs
fall through to L7 TLS-termination frontend for Host-based routing
- L7 frontend terminates TLS with wildcard cert, routes by Host header
- Health check support via HAProxy httpchk
- Backward compatible: existing Generate() works unchanged
- 3 new tests (L7-only, mixed L4+L7, backward compat)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Central now starts an embedded NATS JetStream server as its core
coordination bus. This provides:
- wild-services KV bucket: service registrations from Cloud/Works
- wild-presence KV bucket: node liveness with TTL-based keys
- wild-events stream: events from all sources (24h retention)
NATS is started in main.go before the API and shut down on SIGTERM.
Cloud and Works will connect to Central's NATS (port 4222) to register
services and maintain presence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the service registration abstraction — the key contract between
Central and its consumers (Wild Cloud, Wild Works). Services register
with Central to get DNS, gateway routing, TLS, and public exposure.
- internal/services/manager.go: CRUD for service registrations stored
as YAML files, with reach model (off/internal/public), backend types
(tcp-passthrough/http/static), and TLS modes
- internal/api/v1/handlers_services.go: HTTP endpoints
POST/GET/PATCH/DELETE /api/v1/services
- Full test coverage for registration, update, deregister, validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the Central networking functionality from wild-cloud/api into
a standalone service. Wild Central manages DNS (dnsmasq), gateway
(HAProxy), firewall (nftables), VPN (WireGuard), TLS (certbot),
security (CrowdSec), and DDNS — all the network appliance concerns.
All Cloud-specific code (instances, clusters, nodes, apps, backups,
operations, kubectl/talosctl tooling) has been removed. The API struct
and route registration contain only Central endpoints. Tests updated
to match the new API signature.
Builds and all tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>