Defer Unbound; manage DNS via systemd-resolved (Hetzner + Quad9)
closedDecision
v1 simplicity: no local Unbound recursive resolver. Outbound mail goes via SMTP relay (smarthost), which removes direct-MX lookups from the cell.
References:
docs/deployment.md:67lists Unbound as a cell service, butdocs/deployment.md:135showsdns/ unbound resolver (planned)— the role does not exist yet.deployment/playbooks/site.ymlhas no dns/unbound role in the converge order;deployment/roles/recovery-bootstrap/tasks/main.yml:238notes “No unbound/DNS unit exists yet (TBD)”.- Outbound relay decided in
docs/adr/0009-cell-architecture.md:101-108 (D28)anddocs/deployment.md:71-74;internal/relay/stalwart.goApplyOutboundroutes all non-local mail via thesovrn-relayMtaRoute, androuter.go:115-127fails startup when relay config fails so a cell never silently falls back to direct-MX.
Remaining cell DNS with relay (low volume, any recursion works):
- smarthost A/AAAA for
mail-eu.smtp2go.com:2525(sovrn.yml:125-138), - sovrnd dnsprober MX/TXT/CNAME via
net.DefaultResolver(internal/domain/resolver.go,internal/dnsprober/net.go), - healthz
dnsleg vianet.DefaultResolver(healthz.go:291-309), - ACME Http01 HTTPS to the LE directory + inbound challenge
(
deployment/roles/stalwart/files/bootstrap-stalwart.sh:285-412).
Plan — DNS via systemd-resolved (Hetzner + unfiltered Quad9)
No new dns/ role. Manage the host resolver in the common/ role:
deployment/inventory/group_vars/all/sovrn.yml: addsovrn_dns_primary(185.12.64.1 185.12.64.2+2a01:4ff:ff00::add:1 2a01:4ff:ff00::add:2) andsovrn_dns_fallback(9.9.9.10 149.112.112.10, unfiltered Quad9 — no threat-block, sodnsprober/verifier sees ground truth and inbound senders never vanish; DNSSEC validation stays on).deployment/roles/common/: write/etc/systemd/resolved.conf.d/sovrn-dns.conf(DNS=,FallbackDNS=,LLMNR=no,MulticastDNS=no,DNSSEC=allow-trust-anchor), enablesystemd-resolved, symlink/etc/resolv.conf->/run/systemd/resolve/stub-resolv.confso all Gonet.DefaultResolverusers and Stalwarthickory-resolverin system mode hit the127.0.0.53cache. Do NOT write a static/etc/resolv.conf— Hetzner cloud-init/DHCP would clobber it on reboot.- Docs cleanup:
docs/deployment.md, ADR-0009 D24,sovrn.yml/healthz comments off the “local Unbound must answer” assumption. - Verify on staging:
just update,resolvectl statusshows Hetzner+Quad9,dig @127.0.0.53 example.com,GET /healthzgreen, relay outbound e2e, reboot-persistence check.
Revisit triggers
Enable Stalwart inbound RBL/DNSBL or SPF/DKIM-verify, MTA-STS/DANE needs, observed provider-resolver incidents/latency, or a hard DNSSEC requirement. Tracked in the child issue (inbound spam architecture + Unbound revisit).
1 Comment
Implemented (v1 simplicity path, unfiltered Quad9).
Changes:
deployment/inventory/group_vars/all/sovrn.yml: newsovrn_dns_primary(Hetzner185.12.64.1 185.12.64.2+2a01:4ff:ff00::add:1 2a01:4ff:ff00::add:2),sovrn_dns_fallback(9.9.9.10 149.112.112.10, unfiltered),sovrn_dns_dnssec(allow-trust-anchor); health comment reworded off Unbound.deployment/roles/common/: newtemplates/sovrn-dns.conf.j2drop-in +handlers/main.yml(restart systemd-resolved);tasks/main.ymlinstallssystemd-resolved, writes/etc/systemd/resolved.conf.d/sovrn-dns.conf, symlinks/etc/resolv.conf-> stub, enables the service. No staticresolv.conf(Hetzner cloud-init would clobber it).docs/deployment.md(service list, health paragraph, layoutdns/entry),healthz.goresolver comment,recovery-bootstrapstart-order comment. ADR-0009 D24 left untouched (accepted record; deferral documented here instead).Verification:
go build ./...+go test -run 'TestHealth|TestDns|TestDNS' .— ok.ansible-playbook --syntax-check playbooks/site.yml— ok.Staging still to do:
just update,resolvectl status,dig @127.0.0.53,/healthzgreen, relay e2e, reboot persistence.