Files
anchorhost-backend/DOMAIN_SYSTEM_PLAN.md
wfalkowski 2768f5f97d
Build and publish Docker image / build-and-publish (push) Successful in 8s
feat(domains): migrate user namespaces to wildcard CNAMEs
2026-10-20 18:10:23 +00:00

4.9 KiB

Domain System Plan

Current user namespace

The domain manager provisions two DNS records in the existing anchorhost.eu zone for each username:

  • u-<username>.anchorhost.eu CNAME anchorhost.eu.
  • *.u-<username>.anchorhost.eu CNAME anchorhost.eu.

Users claim individual hostnames such as play.u-<username>.anchorhost.eu in the application database. The wildcard record makes each single-label hostname resolve without another DNS write. Claims remain necessary for ownership, duplicate prevention, and service linking. DNS resolution alone does not create a Traefik route.

The DNS API refuses to replace a conflicting record at the namespace owner. This matters for accounts that still have an NS delegation from the previous design: an NS delegation and a CNAME cannot coexist at the same name. Those namespaces require an explicit migration.

Automatic domain for every service

1. Allocate a stable service hostname

When a service is created, derive a DNS-safe label from its display name and reserve it under the owner's namespace. Enforce a unique constraint on the normalized full hostname; on collision, add a short stable suffix. Persist the assigned hostname against the service record, and release it only when the service is permanently deleted. Renames should update the display name without silently changing the public hostname.

2. Publish a service-ready state

Only publish a hostname after the service, network endpoint, and platform route are ready. Show the assigned address and readiness state in the dashboard. If provisioning fails, retain the reservation for retry and surface a useful error rather than leaving a hostname that resolves to a dead endpoint.

3. Route web services

For HTTP/HTTPS services, create a Traefik IngressRoute from the assigned Host() name to the service's Kubernetes Service and declared HTTP port. Validate ownership and namespace before creating or deleting routes. Provision TLS using a certificate that covers the user namespace; wildcard certificates require DNS-01 with a provider credential, while HTTP-01 cannot issue wildcard certificates. Keep the DNS provider token out of user workloads and API responses.

4. Handle game ports by protocol

DNS does not carry a TCP/UDP port. For Minecraft Java, publish an _minecraft._tcp.<service-host> SRV record with the allocated external port and point it at the service hostname; Java clients can then connect without users typing the port. Verify client behavior and ensure the SRV target has an address record. Minecraft Bedrock and arbitrary TCP/UDP services need protocol-specific support or a proxy that can multiplex traffic. Traefik TCP SNI routing only applies when clients send usable TLS SNI; it is not a general raw TCP port multiplexer. Until a compatible proxy or a direct port is available, continue showing the allocated port.

5. Lifecycle and safety

  • Create routes and DNS metadata idempotently, then verify ready Service endpoints before marking a domain active.
  • Remove only resources whose labels/annotations and database ownership identify the service being deleted.
  • On stop, preserve the hostname but show the service as offline; on delete, remove route and SRV metadata before releasing the name.
  • Rate-limit hostname claims and validate every label, hostname length, user ownership, and service ownership server-side.
  • Record provisioning failures and cleanup outcomes without logging provider tokens or other secrets.

Migration from per-user delegated zones

The one-time migration is implemented by scripts/migrate_delegated_dns.py. It defaults to a read-only dry run, checks for conflicts, copies child-zone RRsets into the parent zone, removes parent DS/NS delegation records, creates the namespace and wildcard CNAMEs, and updates existing platform claim metadata. It leaves child zones intact as a rollback source. Run it from the backend environment and review the complete dry-run output before passing --apply.

Do not remove existing NS or DS records as part of an ordinary domain claim. The migration is idempotent and stops before writing if it finds conflicting records or child-zone apex data that cannot coexist with the required CNAME. After cutover, verify DNSSEC and public resolution before retiring any child zone. Resolver caches may retain old delegation or record data for the previous TTL.

Acceptance checks before enabling automatic service names

  • A new account's base and wildcard names resolve to the platform edge after DNS cache expiry.
  • Existing names under a legacy NS delegation are detected and never overwritten.
  • A duplicate service label cannot be claimed by another service or user.
  • HTTP hostnames reach only the owning service and receive a valid certificate.
  • Minecraft Java SRV lookup returns the allocated external port; other protocols retain an explicit port unless a tested proxy supports portless access.
  • Service stop, restart, rename, and deletion leave DNS, route, and database state consistent.