Captures a long infra session: fleet *.internal DNS (git-sourced, 42 names, three resolvers including a new colo one), waterland studio containerised on irv-ml1, Homepage cleaned up and themed with Australis Skyfall over an Arbo-generated background, and four unmanaged stacks adopted into stacks/. Four detail files added. Auto-archived 4 entries to archival-memory.md (Recent decisions 2, Tried and abandoned 2); 5 held back by the open-deferred guard rather than moved. Also records three operator-owned open items: the colo DNS repoint, the static-v6 convention, and the deliberately belayed AI-tab Dormant regrouping.
120 lines
5.7 KiB
Markdown
120 lines
5.7 KiB
Markdown
# `[2026-08-19]` Fleet `.internal` DNS — git-sourced, agent-managed, three resolvers
|
|
|
|
Operator: *"with ipv6 i can't memorize the IP addresses anymore. need a way to
|
|
keep track of local .internal dns names that can be agent managed and is
|
|
lightweight."* Built and live in one session; commit `b8003c7`.
|
|
|
|
## Shape
|
|
|
|
```
|
|
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
|
|
dns/README.md workflow, naming, the IPv6 caveat
|
|
```
|
|
|
|
Deliberately the same posture as `deploy-stack.sh`: the file is intent, the
|
|
resolvers are derived state, you see a diff before anything changes.
|
|
`--dry-run` / `--yes` / `--site <s>`. Verified idempotent — a second run prints
|
|
`nothing to do`.
|
|
|
|
Naming is `<host>.<site>.internal` with sites **`ana` / `esh` / `nh3`**
|
|
(operator's call). `.internal` is ICANN-reserved for private use since 2024;
|
|
`.local` is reserved for mDNS, which is why the pre-existing
|
|
`searxng.pfi.local` was a standards collision that merely happened to work.
|
|
|
|
Every name is published to **every** resolver — the site label says where a
|
|
host *is*, not which resolver knows about it.
|
|
|
|
## The framing correction that mattered most
|
|
|
|
The ask reads as "I can't memorise v6 addresses", but the deeper problem is
|
|
that **v6 addresses are derived, not assigned**, so they cannot reliably be
|
|
*written down once* either. SLAAC gives EUI-64 (MAC-coupled) or
|
|
privacy-extension (rotating) addresses, and UniFi has **no v6 equivalent of a
|
|
DHCP reservation** — so a hand-maintained v6 table rots on its own.
|
|
|
|
⇒ The fix has two halves and only the second is DNS: (1) pin static v6 on
|
|
server-class hosts, (2) then the name table is just a file. Surfaced to the
|
|
operator before building.
|
|
|
|
**Verified 2026-08-19: no fleet host has a global v6 address at all yet** —
|
|
ESH's `/56` is live only on `esh-cameras`, NH3's LANs are back to
|
|
`ipv6_interface_type: none`, the colo has none. So the `v6:` column ships
|
|
EMPTY and correct, and the naming layer was built first rather than blocking
|
|
on v6. Names established now need no renaming when addresses land.
|
|
|
|
Suggested convention when they do (awaiting operator): each server static at
|
|
its site's `/64` with low-order bits echoing the v4 host octet —
|
|
`esh-docker-vm` at `…::45` — so addresses are declarable *and* semi-memorable.
|
|
|
|
## Two properties not to break
|
|
|
|
**Authority is scoped to the ZONE, not the resolver.** Only rewrites ending in
|
|
`.internal` are managed. ESH's resolver turned out to carry three hand-made
|
|
`esteban.net` rewrites (`eshnas`, `brotherprinter`, `eshhome`) — **my first
|
|
read of the config missed them**, because an `awk` range on `rewrites:` matched
|
|
an empty-looking block. A resolver-wide authoritative sync would have silently
|
|
deleted all three on first run. Verified intact after sync.
|
|
|
|
**Within `.internal` it IS authoritative** — names added by hand in the AdGuard
|
|
UI get deleted by the next sync. That is the point: one place to look.
|
|
|
|
## The colo had no resolver at all
|
|
|
|
ESH and NH3 each ran AdGuard; **ana-docker resolved straight against
|
|
`1.1.1.1`**, so the colo had no way to answer for internal names. Closed with
|
|
`stacks/adguard-ana/`.
|
|
|
|
⚠️ Its API is on **8053**, not 8080 — `:8080` and `:3000` were already taken on
|
|
that busy host. The port is therefore carried **per-site in the yaml**, not
|
|
assumed by the script, so the odd one out cannot be forgotten.
|
|
|
|
⚠️ It ships with **no blocklists**, deliberately. The other two filter ads for
|
|
human browsing; this one resolves for a rack of servers, where a blocklist
|
|
false-positive breaks service-to-service calls at 3am for no upside.
|
|
|
|
First boot uses a **seed config** (`conf/AdGuardHome.seed.yaml`) copied into
|
|
the conf volume before first start, so the container comes up configured
|
|
instead of sitting in the setup wizard.
|
|
|
|
## Credential — service account, not the operator's
|
|
|
|
Added a dedicated **`infra-ops`** AdGuard user to all three resolvers rather
|
|
than asking for the `lkraven` password (per the standing migrate-off-operator-
|
|
creds directive). Password vaulted at
|
|
`nh3-dev/adguard-infra-ops-password`; `lkraven` untouched; pre-change configs
|
|
backed up on each host as `AdGuardHome.yaml.bak-preinfraops-*`. Both existing
|
|
resolvers kept answering across the restart.
|
|
|
|
Two landmines worth keeping:
|
|
|
|
- **Go's bcrypt rejects `htpasswd`'s `$2y$` prefix.** Same algorithm, different
|
|
marker; `golang.org/x/crypto/bcrypt` accepts only `$2a$`/`$2b$`. Normalise
|
|
the prefix, and self-verify the hash with `htpasswd -vb` BEFORE installing it
|
|
on a live resolver.
|
|
- **The vault appends a trailing newline on `get`.** A password carrying a
|
|
stray `\n` fails auth in a way that looks exactly like a wrong password.
|
|
`dns-sync.py` strips it.
|
|
|
|
## `pfi.local` migration — and the one that must NOT move
|
|
|
|
`searxng.pfi.local` → `searxng.ana.internal`, with the **old `Host()` kept
|
|
alongside** in the Traefik rule so nothing breaks mid-migration; both return
|
|
200. Drop the fallback once the access log shows the old name unused.
|
|
|
|
**`matrix.pfi.local` deliberately NOT migrated.** A Matrix `server_name` is
|
|
baked into every user ID, room ID and signing key, and federation identity
|
|
derives from it — renaming it is not a DNS change, it is rebuilding the
|
|
homeserver's identity and invalidating its history. The operator approved
|
|
"migrate pfi.local" generally; this was surfaced as a deliberate exclusion
|
|
rather than executed blindly.
|
|
|
|
## Still open
|
|
|
|
Colo hosts still point at `1.1.1.1`, so they do not yet *use* the new resolver
|
|
— it only answers what asks it directly. Repointing a whole site's DNS is a
|
|
bigger change than standing the service up, and is the operator's to schedule.
|
|
|
|
See also [[2026-08-17-fleet-ipv6-mesh]].
|