Files
bf-infra/docs/handoff-20260825-agh-dns-cutover.md

193 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Übergabe — nächste Session: AdGuard als Resolver + Split-Horizon (TP23 Stufe 2 ∥ TP31 Schritt 4)
> **Einstieg:** diese Datei, dann [`teilprojekte.md`](teilprojekte.md) → **TP31** und **TP32**.
> **Zustand am Netz: stabil.** Caddy läuft produktiv und ist abgenommen, keine Config-Transaktion offen.
>
> 🔄 **Zweite Fassung (2026-08-25, spät).** Die erste entstand *vor* der Caddy-Abnahme und enthielt drei
> Annahmen, die live nicht halten — sie stehen unten unter **„Was den Plan ändert"**. Wer die alte
> Fassung im Kopf hat: **Punkt 2 des Auftrags („Konfiguration übernehmen") ist so falsch.**
## Warum eine eigene Session
Der Schritt verschiebt die **DNS-Auflösung des ganzen Standorts** vom Core in einen Container. Heute
liefert der DHCP in **allen sieben VLANs** `dns-server = <VLAN>.1` (live geprüft) — AdGuard bekommt
**niemand**. Ein Fehler trifft sofort jedes Gerät und ist schlecht zu diagnostizieren, wenn die
Auflösung selbst das Kaputte ist. **Nicht ans Ende einer langen Sitzung legen** (Lehre aus TP28).
## Ausgangslage (Stand 2026-08-25 23:30, live geprüft)
- ✅ **Caddy produktiv und abgenommen**: LXC **130** `caddy-bf1`, **`10.40.20.40`**, `onboot=1`,
`startup order=10`, im Backup-Job. Caddy **v2.11.4**, **16 Sites**. Abnahme über den *Host-Header*
von einem zweiten Host: **11× 200**, `nautobot` 302, 0,02–0,11 s.
Config im Repo **`caddy/Caddyfile`**, Ausrollen mit **`caddy/deploy.sh`**.
- ℹ️ `pve-mcp` 400 / `ha-mcp` 403 / `gitea-mcp` 404 sind **keine Proxy-Fehler** — dieselben Codes
kommen direkt vom Origin. Nicht als Baustelle behandeln.
- 🧹 Der Zertifikatsspeicher hält **17** Zertifikate bei 16 Sites: `grafana` ist verwaist (harmlos).
- ✅ **`hass` liefert 200** (gelöst 2026-08-26). Ursache war **nicht** der fehlende YAML-Eintrag:
HA hat die http-Config nach **`.storage/http`** migriert, der `http:`-Block in `configuration.yaml`
ist seither **wirkungslos**. `10.40.20.40` ist über den WS-Zweischritt `http/config/configure` →
`http/config/promote` in die Trusted-Liste gekommen und als `stable` bestätigt. Details in TP31.
⇒ **Damit ist die CNAME-Umstellung von `hass` nicht mehr blockiert.**
- ✅ **Aufgeräumt** (TP31 Schritt 1): LXC `120/121/128/129/130(alt)/131` **und `104`** zerstört,
72/72 PBS-Snapshots weg — **`ct/104` ebenfalls auf 0**, und am selben Abend die vier gestoppten
Gäste **`103 minarca`, `109 node-red`, `115 hms-mqtt-publisher`, `201 openproject`** samt ihren
**28 Snapshots** (User-Entscheid 2026-08-25).
🎯 **Ergebnis: auf pve läuft jetzt jeder existierende Gast** — 11 LXC + 2 VMs, kein einziger
`stopped`. Der PBS-Datastore enthält nur noch aktuelle Gäste.
⚠️ **Reihenfolge dabei ist kein Detail:** die Snapshot-Liste **vor** dem `pct destroy` einsammeln.
`pvesm list --vmid <id>` liefert danach 0, obwohl die Backups noch liegen — der Filter braucht die
Gast-Config. Gegenprobe deshalb **direkt am Datastore** (`/nodes/pve/storage/pbs/content`).
- ✅ **Startreihenfolge auf pve repariert** (TP32): HA startet nach MariaDB, Docker, MQTT, z2m25.
## 🔴 Was den Plan ändert — drei Funde vom 2026-08-25 (spät), alle live erhoben
### 1. Die Rewrites sind **veraltet, nicht wertlos** — Ziele umbiegen statt löschen
> 🔴 **Korrektur (User, 2026-08-26):** „manche hosts leben auf vlan 20 weiter" — die erste Fassung
> hat die `10.40.41.x`-Einträge pauschal als tot bezeichnet. Falsch ist das **Ziel**, nicht der
> **Name**: `nas`, `macmini`, `appletv`, `tv`, `mfc` gibt es weiter, sie sind nur umgezogen
> (NAS `10.40.20.10`, macmini `10.40.20.167`, Brother MFC `10.40.10.6` — der steht auf **VLAN10**).
> ⇒ **Jeder Name einzeln bewerten: umbiegen, wenn der Host lebt; streichen, wenn nicht.**
`adguard_bf1` (`10.40.20.16`, Docker in LXC 400) läuft seit 7 Tagen und hält **24 Rewrites** — davon
ist der überwiegende Teil **tot**:
| Klasse | Anzahl | Beispiel | Was damit zu tun ist |
|---|---|---|---|
| Ziel im toten VLAN1, **Host lebt** | 8 | `nas.bf…→10.40.41.10`, `mfc.bf…→10.40.41.4`, `macmini.bf…→10.40.41.167` | **umbiegen** auf die VLAN20/VLAN10-Adresse |
| Ziel **`nginx-bf.emsenhuber.net`** | 9 | `portainer`, `z2m25`, `nodered`, `grafana`, `vw`, `speedtest`, … | Ziel ersetzen durch **`10.40.20.40`** — Caddy tritt an die Stelle des NPM (LXC 128 ist zerstört) |
| **FritzBox-Reste** | 2 | `*.myfritz.net → 100.72.62.31` / `10.40.41.254` | streichen (FB6660 seit 2026-07-01 ausgebaut) |
| FM-Standort | 3 | `nas.fm…→10.101.0.10` | dort gepflegt, s. Fund 2 |
⇒ **Übernehmen: `upstream_dns`, `bootstrap_dns`, Filterlisten.**
Die 16 Service-Rewrites entstehen **neu aus `caddy/Caddyfile`** (jeder Site-Name → `10.40.20.40`).
Die Host-Rewrites oben werden **einzeln entschieden** — nicht blind mitschleppen, aber auch nicht
pauschal wegwerfen.
Was dort **richtig** steht und mitmuss:
```
upstream_dns:
- https://px35ynlol3.cloudflare-gateway.com/dns-query
- '[/bf.lan/]10.40.20.1'
- '[/40.10.in-addr.arpa/]10.40.20.1'
```
> ⚠️ **Cave (User, 2026-08-26): `emsenhuber.net` ist unsere Domain, nicht `bf.lan`.**
> `bf.lan` ist nur der **DHCP-interne Suffix**, den der Core heute vergibt (`domain=bf.lan` +
> `add-dns-entries-suffix=lan`) — er hat keine Namensautorität. **Offene Entscheidung für diese
> Session:** so lassen oder auf `bf.emsenhuber.net` heben (dann zieht der conditional upstream mit
> und die Lease-Namen liegen unter der echten Domain). Nicht nebenbei umstellen — es betrifft jeden
> Lease-Namen.
🔑 Der conditional upstream ist **nicht optional**: der Core vergibt `domain=bf.lan` und legt aus jeder Lease
einen `*.lan`-Eintrag an (`add-dns-entries-suffix=lan`). Ohne diesen conditional upstream sterben
**alle** Lease-Namen. Ebenso bleibt **mDNS Sache des Core** (`mdns-repeat-ifaces` = vlan10/20/30/40/90).
### 2. Die Redundanz **gibt es** — wechselseitig standortübergreifend (und sie ist überall stale)
> 🔴 **Korrektur (User, 2026-08-26):** „die redundanz gab es schon! und zwar wechselseitig
> standortübergreifend. bei fm klarerweise auf ds415+ gehostet."
**Live gegengeprüft (2026-08-26, `nslookup` über den WG-Tunnel):**
| Instanz | wo | antwortet auf 53 | `nas.bf.emsenhuber.net` → |
|---|---|---|---|
| `adguard_bf1` | BF, Docker in LXC 400 (`10.40.20.16`) | ✅ | BF-Rewrite, VLAN1-Ziel |
| `adguard_fm2` | **BF** (`10.40.20.17`), für FM | ✅ | — |
| FM-Resolver 1 | **FM**, DS415+ (`10.101.0.16`) | ✅ | **`10.40.41.10`** |
| FM-Resolver 2 | **FM**, DS415+ (`10.101.0.17`) | ✅ | **`10.40.41.10`** |
⇒ Das Muster ist **reziprok**: jeder Standort hostet auch eine Instanz des anderen, spiegelbildlich
auf `.16`/`.17`. (`10.101.0.10` — die FM-NAS selbst — antwortet **nicht** auf 53.)
🔑 **Die Konsequenz ist größer als „ein Paar bauen":** die BF-Namen liegen in **vier** Resolvern an
**zwei** Standorten, und **beide FM-Instanzen antworten heute mit der toten VLAN1-Adresse**. Ein
Rewrite-Satz, den nur das neue BF-Paar kennt, erzeugt **standortabhängig verschiedene Antworten** für
denselben Namen. Zum Auftrag gehört deshalb eine Festlegung:
- **entweder** den Rewrite-Satz als *eine* Quelle definieren und auf alle Instanzen ausrollen,
- **oder** — wartungsärmer und konsistent mit der SoT-Antwort für CF/Caddy (s. TP31) — festlegen, dass
**FM die BF-Namen nicht mehr selbst beantwortet**, sondern per conditional upstream nach BF fragt.
- ⚠️ **Nicht in dieser Session mitfixen, aber notieren:** FM zeigt auf tote Ziele. Das braucht ein
eigenes Fenster und den DS415+-Zugang.
### 3. Die Firewall-Vorratsregeln zeigen auf die **falsche** IP
TP5 Etappe 2 hat vorgesorgt, aber hart auf den heutigen Container:
```
TP5-E2: LAN->AGH dns accept udp dst-address=10.40.20.16 dst-port=53
TP5-E2: LAN->AGH dns-tcp accept tcp dst-address=10.40.20.16 dst-port=53
```
🔴 **Das ist der Punkt, an dem dieser Cutover sonst scheitert:** heute fragt jeder Client seinen
**eigenen Gateway** (`input`-Kette, keine Regel nötig). Nach dem DHCP-Flip wird DNS zu einem
**`forward`-Fluss über VLAN-Grenzen** — und die Firewall ist seit 2026-08-18 deny-by-default.
Ohne passende Regeln verlieren **IOT, MEDIA, UNTRUSTED, GUEST und MGMT** schlagartig die Auflösung,
während VLAN20 (wo der Resolver steht) unauffällig weiterläuft.
✅ **Entscheid (User, 2026-08-26): die Vorratsregel auf `10.40.20.16` wird GESTRICHEN** — „`.16` war
historisch, passt aber nicht mehr ins System." Also **nicht erweitern, sondern ersetzen**: die zwei
alten Zeilen raus, zwei neue auf das Paar `10.40.20.36/.37` rein — **über Nautobot Golden Config**,
nicht ad hoc (Core-Config ist GC-only), und **bevor** irgendein DHCP angefasst wird.
## Der Auftrag
**User-Entscheid 2026-08-25:** Split-Horizon **nicht** auf dem Core, sondern **in AdGuard, zusammen mit
TP23 Stufe 2** — „nichts zweimal bauen".
1. **LXC 110 `agh-bf1` + 111 `agh-bf2` bauen**, Muster exakt wie LXC 130 (Debian 13, `tag=20`,
`onboot=1`, `startup order=10`, in den Backup-Job `backup-6dcf5c10-610f`).
**Als Paar** — ein einzelner AGH wäre ein DNS-SPOF auf pve.
**Adressen `10.40.20.36` / `.37`** — TP32 will DNS auf `.3x`, `.30/.31/.32` sind Nautobot;
`.36/.37` sind **frei geprüft** (kein ARP, keine Lease, DHCP-Pool beginnt erst bei `.100`).
⚠️ Nebenbefund: TP32 nennt als Pool `.200–.249`, live läuft `.100–.200` — Konflikt mit
„`.100–.199` App-Services". Bestand bleibt; nur beim *Vergeben* aufpassen.
2. **Basis-Config übernehmen** (Upstreams/Bootstrap/Filter) aus `adguard_bf1`,
`/opt/docker/adguard_bf1/conf/AdGuardHome.yaml` (Backup daneben: `…bak-tp23-20260817`).
**Rewrites NEU bauen** — s. Fund 1.
3. **Die 16 Rewrites setzen**: jeder Site-Name aus `caddy/Caddyfile` → **`10.40.20.40`**.
4. **Firewall zuerst** (Fund 3): `TP5-E2`-Regeln auf `.36/.37` erweitern, per GC deployen,
`--refresh-compliance` fahren.
5. **Testen, bevor irgendein DHCP angefasst wird:** `dig @10.40.20.36 vw.emsenhuber.net`
(erwartet `10.40.20.40`), `dig @10.40.20.36 <irgendein-lease>.lan` (erwartet die Lease-IP),
und dasselbe gegen `.37`. **Zusätzlich aus einem anderen VLAN** — sonst prüft man nur VLAN20 und
übersieht genau den Firewall-Fall.
6. **VLAN für VLAN umstellen**, nicht alle sieben: **VLAN20 → VLAN10 → Rest**, **VLAN30 (IoT, 26 Geräte)
zuletzt**; dort ist ein Reconnect am teuersten.
### ⏱️ Die Falle, die den Zeitplan bestimmt: `lease-time=1d`
Alle sieben Server stehen auf **1 Tag** (GUEST 12 h). Ein geändertes `dns-server` erreicht die Clients
erst zur **Erneuerung (T1 ≈ halbe Laufzeit)** — der Cutover wirkt also **verzögert und ungleichzeitig**,
und ein Rollback wirkt **genauso langsam**. Zwei Konsequenzen:
- **Vor dem Flip die `lease-time` im betroffenen VLAN auf ~10 min senken** (GC-Deploy), umstellen,
verifizieren, danach zurücksetzen. Sonst zieht sich die Umstellung über Stunden.
- **„Es geht noch" beweist nach dem Flip gar nichts** — das Gerät hat schlicht die alte Lease.
Abnahme ist **eine frische Lease + `scutil --dns` / `resolvectl status`**, nicht ein `ping`.
### 🔙 Rollback (vorher lesen, nicht danach)
1. `dns-server` im betroffenen `/ip dhcp-server network` zurück auf `<VLAN>.1` — der Core ist
unverändert ein vollwertiger Resolver (**DoH zum CF-Gateway, `verify-doh-cert=yes`,
`allow-remote-requests=yes`**); er wird in diesem TP **nicht** abgeschaltet.
2. Clients erneuern lassen (s. `lease-time` oben) oder gezielt bouncen.
3. Firewall-Regeln dürfen stehen bleiben — sie schaden nicht.
⇒ **Der Core bleibt bis zum Ende der Session unangetastet als Rückfallebene.** Erst wenn alle sieben
VLANs sauber über AGH laufen, wird über sein Abschalten überhaupt geredet (eigener Entscheid).
## Fallen, die schon bezahlt sind
- 🔴 **Ein Statuscode vom öffentlichen FQDN beweist nichts.** `grafana.emsenhuber.net` lieferte
monatelang **302** — das war der **Access-Login**, während der Dienst gar nicht existierte.
**Immer gegen das Origin messen.** Umgekehrt gilt dasselbe: die 400/403/404 der MCP-Endpunkte sind
echte Serverantworten, kein Proxy-Fehler.
- 🔴 **`pvesm free` scheitert an geschützten PBS-Snapshots stillschweigend.**
Entschützen: `pvesh set /nodes/pve/storage/pbs/content/<volid> --protected 0`.
- 🔴 **Startreihenfolge auf pve hängt NICHT an der VMID**, solange ein Gast `startup: order=` trägt.
- ⚠️ **Core-Config läuft über Nautobot Golden Config** — DHCP- *und* Firewall-Änderung brauchen
Template **und** ComplianceRule, sonst Drift. Letzter geprüfter Stand: **104/104, 0 Drifts (08-23)**;
vor dem Cutover einmal `--refresh-compliance` fahren, damit die Ausgangslage sauber ist.
- ⚠️ **Caddy in LXC 130 hat einen eigenen Netz-Namespace** und ist deshalb nicht von der
macvlan/ipvlan-Blindheit betroffen — ein AGH **im Docker-LXC 400** wäre es. Ein weiterer Grund für
eigene LXCs statt „Container dazu".
- ⚠️ **Mit Split-Horizon greift CF Access innerhalb des Netzes nicht mehr.** Bewusster Preis
(User 2026-08-25); innen tragen TP5-Segmentierung und die App-eigene Auth.
## Danach offen (TP31 Rest)
Tunnel `bf-public` + cloudflared auf **ap1/ap2** (arm64, 95 MB Flash frei — ⚠️ je ein **physischer**
`device-mode`-Wechsel am Gerät nötig), CNAMEs umhängen (`n8n` → `gitea` → `hass` → `vw`; **`hass` erst
nach dem 400er-Fix**), dann `bf-nas`/`bf-pve`/`bf-mm` löschen und die alten Container entfernen.
Danach: **`adguard_bf1` im Docker-LXC 400 abbauen** — sonst stehen zwei AdGuards mit
widersprüchlichen Rewrites im selben Netz.