dnsmasq (6 new, 30.8% → 32%):
- Empty instances, multiple instances, nil config, configured IP
- No leaked empty entries (address=//, local=//)
- Empty fields don't produce broken entries
Also fixed a bug: empty Domain/InternalDomain fields in commented-out
entries (no LB IP case) now guarded against producing # local=// and
# address=// directives.
haproxy (7 new):
- No HTTPS frontend with no routes
- HTTP-only and L4-only route scenarios
- CertsDir customization and default
- Custom TCP route ports
- Health check directive in L7 backends
services (8 new, 72.7% → 75.8%):
- Not-found errors for Get, Deregister, Update
- DeregisterBySource partial match (source AND backend)
- Overwrite preserves file count
- Empty dir and non-YAML file handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests added (13 new):
- HAProxy ACL ordering: L7 before L4 wildcard, L4 exact before L4 wildcard
- HAProxy subdomains: false=exact only, true=wildcard+exact
- Reconciliation integration: HAProxy config from services, DNS config
from services, Central config domain injection, correct DNS target IPs
- dnsmasq: reach:internal has local=/, reach:public does not
- Services: idempotent registration, no duplicates in list
Bug fix:
- Run initial reconciliation on startup so Central's own domain (from
config) gets HAProxy + DNS routes even with zero registered services.
Previously, Central only got routes after the first service was
registered, causing 503 on fresh restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite the service registration model per the architecture plan:
Model changes:
- Domain is the unique key (removed Name field)
- Removed ExtraDomains — each domain is its own registration
- Removed SourceNode, BackendStatic, ReachOff
- Added Subdomains bool for wildcard matching control
- Files stored as <domain_with_underscores>.yaml
- API routes: /services/{domain:.+} (regex for dots in path)
- Added DeregisterBySource for cleanup before re-registration
Reconciliation changes:
- No ExtraDomains iteration — each service IS one domain
- reach:internal → sets InternalDomain (generates local=/)
- reach:public → sets Domain (no local=/)
- tcp-passthrough → DNS points to backend IP
- http → DNS points to Central IP
All 22 tests pass (9 manager + 8 handler + 5 config/cert).
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>