Names for fleet hosts so addresses stop needing to be memorised. Built because
IPv6 makes that hopeless — and, more to the point, because v6 addresses are
derived rather than assigned, so they cannot reliably be written down once and
trusted either.
dns/internal.yaml source of truth: 38 hosts + 4 service aliases
scripts/dns-sync.py reconciles AdGuard resolvers against it
stacks/adguard-ana/ the colo's resolver, which did not exist
Naming is <host>.<site>.internal with sites ana/esh/nh3 (operator's call).
.internal is ICANN-reserved for this; .local is reserved for mDNS, which is
why searxng.pfi.local was a collision that merely happened to work.
Same posture as deploy-stack.sh: file is intent, resolvers are derived state,
you see a diff before anything changes. Every name is published to every
resolver, so the site label says where a host IS, not who knows about it.
Two properties that matter:
- Authority is scoped to the ZONE, not the resolver. ESH carries hand-made
esteban.net rewrites predating this; they are read, ignored and preserved.
Resolver-wide authority would have silently deleted them.
- Within .internal it IS authoritative, so UI-added names get removed. That is
the point — one place to look.
Colo gap closed: ana-docker had no resolver at all (hosts went straight to
1.1.1.1). Its AdGuard runs API on 8053 because 8080/3000 were taken, so the
port is carried per-site in the yaml rather than assumed by the script. It
ships with no blocklists — a false positive on a server network breaks
service-to-service calls for no upside.
Auth is a dedicated infra-ops AdGuard user, not the operator's account,
password vaulted at nh3-dev/adguard-infra-ops-password. Pre-change configs
backed up on each resolver. Both resolvers stayed answering across the restart.
searxng.pfi.local -> searxng.ana.internal, with the old Host() kept alongside
so nothing breaks mid-migration. matrix.pfi.local deliberately NOT migrated: a
Matrix server_name is baked into every user id, room id and signing key, so
renaming it rebuilds the homeserver's identity rather than changing a DNS name.
The v6 column is empty and correct — no fleet host has a global v6 address
yet. The file documents why addresses must be pinned statically before they go
in, since a record that silently stops matching is worse than no record.
searxng: the healthcheck passed '--tries' and '--spider' as separate argv
entries, so wget consumed '--spider' as the value of '--tries'. Spider mode
never engaged and every 30s probe downloaded the response to disk; the
container's working directory had accumulated 295,287 healthz.N files since
April, and the directory scan to pick the next free filename is what
intermittently blew the 10s timeout and flapped the dashboard card to
UNHEALTHY. Restored '--tries=1'. The junk was in the writable layer, so the
recreate cleared it. Now healthy, fails=0, 200 in 0.16s.
seafile: none of the three services declared a restart policy, so Docker
defaulted them to 'no'. The daemon stopped all three within 200ms on
2026-05-06 and nothing brought them back — a three-month outage whose only
trace was an EXITED card. Exit 255 is what a container ignoring SIGTERM
reports when the daemon stops it, not a crash. Added restart: unless-stopped.
Stack is back up; mysql gates on its healthcheck as designed and seahub
started without the race. 302 -> login page.
Both stacks were running unmanaged on ana-docker and are now tracked here.
homepage: AI tab reordered by clickability per operator — chat frontends,
ComfyUI and the control plane on top; vLLM /docs seats and TTS endpoints
below. Corrects the previous commit's UNRESOLVED tab-bar section: it was
warm-up time after a recreate, not a defect.