# BF-Netzwerk — Teilprojekte (kanonische Steuerung, Stand 2026-06-10)
> **Zweck:** Das große Netz-Projekt in **einzeln durchziehbare Teilprojekte (TP)** zerlegt — jedes mit klarem Scope,
> aktuellen Entscheidungen, Abhängigkeiten und Status. Jedes TP ist so geschnitten, dass es eine frische Session
> **mit sauberem Kontext** straight abarbeiten kann. Architektur/Historie: `~/.claude/plans/ich-will-das-delegieren-atomic-sunrise.md`.
## Kanonisches VLAN-Modell (gültig — überschreibt ältere Tabellen)
| VLAN | Name | Subnetz (Ziel) | Zweck |
|---|---|---|---|
| 10 | MAIN | 10.40.10.0/24 | Trusted (Handy/Laptop/Apple Watch) |
| 20 | INFRA | 10.40.20.0/24 | NAS/VMs/Docker/HA |
| 30 | IOT | 10.40.30.0/24 | Smart-Home (HA-managed), Waage, Fitness-Tracker |
| **40** | **MEDIA** | 10.40.40.0/24 | **Sonos, AppleTV, TV, VuPlus, Cast-Ziele** (getauscht ggü. früher) |
| **50** | **UNTRUSTED** | 10.40.50.0/24 | **Spielekonsole, eBook-Reader, Low-Trust-Internet** |
| 90 | GUEST | 10.40.90.0/24 | nur Internet |
| 99 | MGMT | 10.40.99.0/24 | Switch/AP/Hypervisor-Mgmt |
| 100 | WAN | (Arris-Transfer) | zum Arris-Kabelmodem (Liwest, Bridge-Mode, Public-IP an ap1) |
IP-Blöcke: `.1–10` Router/Gateways · `.11–20` Switches/APs. **GW je VLAN = `.1` (crs326-core).**
Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`.
*(Historisch, überholt: „ap1=.1, ap2=.2 · ALLNET=.11, Omada=.12" — aus der Zeit vor dem Core-Cutover.)*
## Ist-Stand (Kurz)
**Stand 2026-08-18 — live gegen die Geräte geprüft (MikroMCP/REST), nicht aus der Doku abgeschrieben.**
*(Die Fassung vom 2026-08-16 stand bis zum 18. hier und war an vier Stellen überholt — Standalone-APs,
RouterOS-Versionen, „plain 1.1.1.1", 68/68. Wer diesen Block liest, liest ihn als Erstes: er wird
ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)*
- **Alle vier MikroTik laufen auf RouterOS 7.24** (stable, 2026-08-17 von Christoph selbst gehoben, TP27),
**RouterBOOT ebenfalls 7.24** auf allen vieren (`current-firmware` = `upgrade-firmware`, geprüft 08-18).
- **L3 + WAN-Edge = `crs326-core`** (CRS326-24G-2S+, Technik UG): Routing, DHCP (7 Server,
54 Reservierungen), DNS, NAT, **NTP-Server für den Standort** und der WG-Hub (TP10, Peers `fm`/`dw`)
laufen hier. Mgmt-SVI **`10.40.99.1`** (vlan99), Gateway `.1` je VLAN. **WAN = `vlan100`** — DHCP-Client
hält die öffentliche Liwest-IP **`90.146.33.177/24`** direkt vom Arris (Bridge-Mode) → kein Double-NAT.
**DNS-Upstream = DoH zum CF-Gateway mit `verify-doh-cert=yes`** (TP23 Stufe 1 ✅ 08-18);
`servers=1.1.1.1` steht nur noch als Bootstrap für den DoH-Hostnamen.
- 🔥 **Die Firewall ist deny-by-default — in BEIDEN Ketten** (TP5 E3/E4 ✅ 08-18). `TP10-seal-lan` ist
`disabled`; Mgmt-Input kommt nur noch aus VLAN10, von HA (`8728`), von Nautobot (`22`) und über
`cf-mesh`. Ein neuer Dienst über eine VLAN-Grenze braucht ab jetzt eine explizite Regel.
- **L2-Branch = `crs326-branch`** (Technik OG): reiner L2-Switch, Mgmt **`10.40.99.2`**,
Uplink `ether1` → core `ether8`.
- **WLAN = CAPsMAN auf dem Core** (TP28 ✅ 08-17, GC-Modell nachgezogen 08-18): ap1 **`10.40.99.11`**
(an core `ether15`), ap2 **`10.40.99.12`** (an branch `ether24`), beide **reine CAPs** —
`cap enabled=yes`, `discovery-interfaces=vlan99`. Die APs halten **keine eigene WLAN-Config** mehr;
Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, `ft-over-ds=yes`.
- **SSDP-Relay** (TP7.1 ✅ 08-18): LXC **108 `mcast-relay`** auf pve, zweibeinig `10.40.10.21` (VLAN10) +
`10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs. Die macOS-Sonos-App findet damit ihr
System; mDNS macht weiterhin der Core.
- **Ausgebaut / gelöscht:** ALLNET-`sw1` und Omada-`sw2` sind **physisch weg** (2026-08-08 bzw. 2026-07-23,
in Nautobot *Decommissioned*); Omada-Controller + `omada-mcp` gelöscht (2026-07-16); FritzBox 6660 raus
seit 2026-07-01 (Liwest-Tausch auf Arris). **VLAN1 ist leer** (`/ip arp` auf `bridge` = 0 Einträge,
08-18) — TP19 ist geschlossen; `cudy1` hängt stromlos an core `ether11`, physischer Ausbau ist hands-on.
- **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 4 Geräte in der Registry),
**Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf,
**92/92 Compliance-Checks grün, 0 Drifts** bei **23 Rules**, Stand 2026-08-18).
📜 Historische Fassung (Stand bis 2026-08-08, Topologie vor dem Core-Cutover — bewusst erhalten, nicht als Ist lesen)
> ALLNET-Core `.11` (FW2.2.1, VLANs 10/20/30/40/50/90/99/100; GE1/GE2/GE8 Trunk; **TP1 ✅ 2026-06-15**). Omada-Switch `.12` (adopted, **TP2 ✅ 2026-06-15**; Trunk-ALL auf P1+P2, Access-Profile auf P3-P8; Profile noch PVID=1 bis Omada-PVID-Fix).
> Omada-Controller **`.175`** (DS923/macvlan, mbentley 6.2, 🟢 live). omada-mcp **`.176`** (pve-LXC, StreamableHTTP, 🟢 live, 84 read-Tools; Controller-API v2 intern via christoph@emsenhuber.com).
> 2× hAP ax² (7.23.1): ap1 `.1` (ALLNET P1) — **TP4 ✅ 2026-07-01: Router + WAN-Edge** (VLAN-Subinterfaces, DHCP, DNS, NAT, Public-Edge). ap2 `.2` (Omada P2 — Trunk).
> **Edge-Cutover ✅ 2026-07-01:** FB6660 physisch entfernt (Liwest-Kabelmodemtausch, kein Zurück), Arris-Kabelmodem im Bridge-Mode; WAN = `sw1 gi16 Access VLAN100 → gi15 Trunk → ap1 ether2 → vlan100 auf bridge` (**NICHT** ether1); ap1 hält Public-IP direkt (Liwest, kein CGNAT), Double-NAT weg. Telefonie war auf der FB und wird nicht weitergeführt. Vorarbeit: TP10-Kern durch (2026-06-27, WG-Hub live) → Bridge-Gate vorher gelöst. Runbook: `routeros/wan-cutover-runbook.md`.
> Kontrolle: MikroMCP (MikroTik) live · ALLNET = Klickanleitungen · Omada → Controller + omada-mcp 🟢.
**Nächster Schritt (Stand 2026-08-21 abends): TP25 — Steering-Test in den Schlafräumen.**
> 📋 **Einstieg: [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md)** — dort steht
> der verifizierte Ist-Stand, die Messfallen und der nächste Test.
> ⚠️ **Auf `crs326-core` ist eine Config-Transaktion OFFEN** (`TEST-ether3-trunk-ap1-sideboard`,
> Frist 2026-09-04): ap1 hängt testweise am Sideboard, `ether3` wurde dafür zum Trunk. Die Compliance
> ist deshalb **absichtlich** nicht 92/92 — `python3 nautobot/gc-transaktion.py status`.
> 🆕 **TP29** (Anwesenheitserkennung fürs Energiesparen) ist als Idee angelegt, nicht auf dem Pfad.
> 📌 **Die TP6-Prämisse ist am 2026-08-20/21 gemessen** (Runbook §5): formal Fall B (+28…+47 ms unter
> Dauer-Upload), aber im Videocall **nicht wahrnehmbar** — kein einziger Problem-Klick über sechs
> Lastphasen. Laut war stattdessen der Funk: **Sticky Client + Band-Ping-Pong, zwölf Wechsel in neun
> Minuten**, je Wechsel bis zu 4 s ohne Durchsatz →
> [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md). Deshalb steht jetzt
> **TP25 vor TP6**; TP6 bleibt offen und begründet, aber nachrangig.
>
> *Vorherige Fassung (Stand 2026-08-18, erledigt):*
TP6 wird hier seit Monaten als „DAS Ziel" geführt, aber `docs/call-quality-diagnosis.md` hat den
Bufferbloat 2026-06-16 bereits gemessen (**A/A+**, „CAKE wäre marginal, nicht deployt"). Die Messung
stammt aus der FritzBox-Ära, der Edge ist seither getauscht — also **einmal gegen den heutigen Edge
nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Nacht auf den 18. abgeschlossen worden (Etappe 3 + 4: deny-by-default in `forward` **und** `input`, MGMT isoliert, `seal-lan` abgeschaltet), TP23 Stufe 1 (DoH auf dem Core) ebenfalls. Nebenher offen, blockiert nichts: **TP25** (Signal-Boden + Re-Walk), **TP28-Rest** (die fünf `wifi-*`-Features vom AP-Scope auf den Core umhängen — die einzigen verbliebenen Compliance-Drifts), **TP7.1** (SSDP-Relay für die macOS-Sonos-App), **TP16.3**, **TP24**.
> *Vorherige Fassung (Stand 2026-08-14, überholt): „TP5 — Deny-by-default am Ende von `forward`." Alles Davorliegende ist durch:* CRS326-Bring-up + L3-Umzug ap1→`crs326-core` (TP15/TP21.1 ✅ 2026-08-08), WLAN-Neuprovisionierung ohne CAPsMAN (TP3 ✅ 2026-08-08), Nautobot-Plattform + Fleet-Adoption + Deploy-Pipeline (TP21/TP22 ✅ 2026-08-13). Danach **TP6 (CAKE) = das Projektziel**. Kleine Follow-ups nebenher: DoH-Cloudflare auf core, DC-USV (**TP24**, seit 2026-08-16 eigener TP). ⚪ **Omada-PVID-Fix ist obsolet** (User-Entscheid 2026-07-22 — Omada spielt seit der TP15-Korrektur keine Rolle mehr im Netz, nie mehr aufgreifen).
> *Vorherige Fassung (Stand 2026-07-22, erledigt): „TP15 CRS326-Bring-up als neuer L3-Core, danach WLAN-Neuprovisionierung ohne CAPsMAN; TP5 folgt nach TP21/CRS326-Adoption."*
> 🎯 **Ziel: sauberes System „from scratch", keine Leichen/Altlasten.** Migrations-Session-Endstand = **VLAN1 + altes cudy-WLAN komplett weg** (VLAN1 nur noch Mgmt, `defconf`-Client-DHCP zurückgezogen, cudy-Uplinks aufs Ziel-Client-VLAN getaggt oder cudy abgebaut). **Unterpunkte/Aufräum-Liste bei Migration:** (a) die Interim-Härtung ap2 `wifi1/wifi2 frame-types=admit-only-vlan-tagged` (TP3.1, 2026-07-06) bleibt gültig — verschwindet automatisch mit VLAN1; (b) ~~`st-main rrm/wnm` = reines Roaming-Provisorium~~ → **kein Provisorium mehr (2026-08-13, TP3.3): Steering ist fester Bestandteil des Golden-Config-Templates + ComplianceFeature `wifi-steering`** — es war die Neuprovisionierung, die es stillschweigend verloren hatte; (c) ~~**cudy1 = verbleibender aktiver VLAN1-Leak**~~ → **ERLEDIGT (User-bestätigt + verifiziert 2026-08-14): cudy1 ist offline.** `core ether11` trägt **kein Link** (kein `R`-Flag), Nautobot-Status *Offline* (TP22 Schritt 5). Damit existiert kein aktiver VLAN1-Leak mehr; der Dual-DHCP-Race aus `project_vlan1_dhcp_incident` ist Historie. Der formale Teardown (Gerät ausbauen, Doku/Inventar) bleibt TP19.
> ⚠️ **HW-Constraint hAP ax²:** 1 GB RAM, aber nur **128 MB Flash + KEIN USB** → **Container auf dem Router NICHT praktikabel**.
> Folge: **native RouterOS-Features bevorzugen** (z.B. Built-in-mDNS statt Container); Container (mDNS-Reflector, BIND9-DNS, Omada-Controller) laufen auf **NAS/pve**, nicht am hAP.
> 🔒 **Governance-Entscheid (User, 2026-07-16, ausgeweitet 2026-07-16): Config ausschließlich über Nautobot — keine Direkt-Changes mehr an einzelnen im Netz verstreuten Geräten.** Ursprünglich als ap1/ap2-Config-Freeze bis TP21 formuliert, noch am selben Tag verallgemeinert: **jede** Netz-Gerätekonfiguration (Router, APs, künftig auch die CRS326-Switches) läuft ab jetzt exklusiv über Nautobot Golden Config (TP21), sobald das steht — keine ad-hoc MikroMCP-Writes/UI-Klicks mehr an Einzelgeräten. **TP19.1 war die letzte Ausnahme** (ap1-DNS-Fix + ap2/sw1/sw2-Mgmt-VLAN-Umzug, s.u.) — davon ist nur der risikolose ap1-Teil noch direkt gemacht worden, ap2 wurde bewusst auf „nach Nautobot" vertagt. **Macht Nautobot + alle Prerequisites (TP18→TP20→TP20.1→TP21, jetzt auch Komodo) zum aktuell kritischen Pfad**, vor TP5/TP6. Betrifft nicht Software-Migrationen auf NAS/pve (anderes Substrat) und die Frage, ob sw1/sw2 vor der CRS326-Ablösung noch angefasst werden (offen, s. TP15/TP19.1).
> ✅ **Genehmigte Ausnahme (User, 2026-07-17): der zentrale CF-Tunnel-Zugang auf ap1 (TP16/TP16.1).** Alle für den zentralen Cloudflare-Zugang **auf ap1** notwendigen (Vor-)Arbeiten dürfen direkt via MikroMCP/CF-API gemacht werden, ohne auf Nautobot GC zu warten — konkret die nativen WireGuard-Mesh-Zeilen aus TP16.1 (und analoge cloudflared/WG-Anpassungen am ap1-Edge). Begründung: Remote-Erreichbarkeit ist die Voraussetzung, um überhaupt sicher am Netz zu arbeiten (Henne-Ei zu Nautobot). **Scope-Grenze:** Die Ausnahme gilt **nur für ap1**. Sobald der zentrale Zugang später auf die **CRS326** wandert (TP15), gilt die Ausnahme dafür **vorerst NICHT** — CRS326-Config läuft dann regulär über Nautobot GC (oder braucht eine eigene, dann neu zu erteilende Ausnahme). Nachtrag der ap1-Ausnahmezeilen in die GC als Follow-up, sobald Nautobot steht.
> ✅ **Genehmigte Ausnahme #4 (User, 2026-08-16): die WireGuard-Zeilen des Cloudflare-Mesh-Bring-ups auf `crs326-core` (TP16.1).** Dürfen direkt via MikroMCP/REST gesetzt werden. **Begründung ist strukturell, nicht bequem:** TP22-Regel 1 schließt jede Sektion mit `<>` generell vom GC-Push aus, und `/interface wireguard` ist genau so eine — ein privater Schlüssel ist über Golden Config **nicht deploybar**. Damit ist die frühere TP16.1-Zusage „CRS326-Zeilen laufen regulär über GC" gegenstandslos. **Auflage:** vorher `/system backup`, danach Ist-Stand als intended config zurück nach Nautobot. **Scope-Grenze:** nur der Mesh-Bring-up auf `crs326-core` — nicht spätere WG-Änderungen, nicht andere Geräte, und sie verlängert sich nicht automatisch. Langfristige Auflösung = das Secret-Handling-Design in TP16.1 (Privkey ist Geräte-Identität, nicht Config).
> ✅ **Genehmigte Ausnahme #2 (User, 2026-07-22): der CRS326-Bring-up (TP15).** Die **Grundkonfiguration** beider CRS326 — Management-IP, Bridge, VLAN-Filtering, Trunks/Access-Ports — darf **direkt** via MikroMCP/Winbox gemacht werden. **Begründung = Henne-Ei:** Golden Config kann nicht auf ein Gerät pushen, das im Werkszustand keine erreichbare IP hat, und die GC-Templates existieren noch nicht (TP21 „offen: Templates/intended config"). **Auflage:** der so entstandene Ist-Stand wird anschließend als **intended config nach Nautobot zurückgeschrieben**, danach übernimmt GC — die Ausnahme deckt das Bootstrapping, nicht den Dauerbetrieb. **Scope-Grenze:** gilt für die beiden CRS326 im Bring-up. Sie hebt die Regel für ap1/ap2 **nicht** auf (deren Neuprovisionierung läuft weiterhin über GC) und verlängert sich nicht automatisch auf spätere CRS326-Änderungen. Damit ist die frühere Formulierung in TP16.1 („CRS326-Config läuft regulär über Nautobot GC oder braucht eine eigene, dann neu zu erteilende Ausnahme") **erfüllt** — dies ist diese Ausnahme.
---
## Teilprojekte
### TP1 — ALLNET Core-Switch finalisieren ▸ ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt)
- ✅ GE8 Trunk (native vlan1, tagged 10/20/30/40/50/90/99/100) — war bereits aktiv.
- ✅ VLAN 40 umbenannt: UNTRUSTED → **MEDIA** (`switch/cli/04-vlan50-media-untrusted.cli`).
- ✅ VLAN 50 angelegt + benannt: **UNTRUSTED**.
- ✅ GE1/GE2 (AP-Trunks) um VLAN 50 ergänzt; GE8 auto-includiert (Trunk).
- Ergebnis: Core trägt alle VLANs (10/20/30/40/50/90/99/100) an P1/P2/P8.
### TP2 — Omada Access-Switch + Controller ▸ ✅ FERTIG (2026-06-15)
- ✅ Omada Controller 6.2.10.17 auf DS923 (macvlan `.175`): `omada/docker-compose.yml`, Stack via Portainer endpointId=14. **Wizard abgeschlossen** (configured=true), Creds in `mcp/omada/.env`.
- ✅ pve-mcp (samik081) in Claude Code eingetragen (`~/.claude/settings.json`, SSE `.173:3000`).
- ✅ omada-mcp **live** auf **pve-LXC (`.126`, endpointId=2), ipvlan-IP `.176`**, Stack Id=200, URL in `~/.claude/settings.json`.
- **Transport:** StreamableHTTP `http://10.40.41.176:3000/mcp` (Health `/health` → 200). Settings-Bug (doppelter Eintrag) behoben 2026-06-15.
- **Auth:** Omada **Open API Client-Credentials**. Creds in `mcp/omada/.env`. Token-Format: `Authorization: AccessToken=` (nicht Bearer). OMADA_TOOL_CATEGORIES=all (alle 84 read-Tools aktiv; write für VLANs/Ports nicht im MCP implementiert → direkte Open API oder Controller-UI).
- omadac_id=`adbef8fa…`, site_id=`6a2fe68e…`.
- ✅ **VLANs im Controller:** 1/10/20/30/40/50/90/99/100 — alle angelegt, Switch adopted (switchConsistent=true).
- ✅ **Port-Plan (ES208G, alle 8 Ports konfiguriert):**
| Port | Name | Profil | Gerät | Status |
|------|------|--------|-------|--------|
| 1 | uplink | Trunk-ALL | → ALLNET GE8 | ✅ korrekt |
| 2 | ap2 | Trunk-ALL | hAP ax² #2 | ✅ korrekt |
| 3 | Port3 | Access-MAIN | — (frei) | ⚠️ PVID=1 |
| 4 | dock/monitor | **Access-MAIN** | Docking-Station | ⚠️ PVID=1 · **Lifeline-Port, nicht anfassen** |
| 5 | drucker | Access-MAIN | Drucker | ⚠️ PVID=1 |
| 6 | Port6 | Access-MAIN | — (frei) | ⚠️ PVID=1 |
| 7 | macmini | Access-INFRA | Mac mini | ⚠️ PVID=1 · **fixe IP 10.40.41.x, nicht anfassen** |
| 8 | ds923+ | Access-MAIN | NAS (2. Port) | ⚠️ PVID=1 · **nicht anfassen** |
- ⚠️ **Profile-PVID-Constraint (bis TP4 offen):** NAS, Mac mini und andere Geräte haben fixe IPs im
Bereich `10.40.41.x` (VLAN-1-Subnetz der FritzBox). Solange diese nicht auf VLAN-10/20-Subnetze migriert
sind, muss `nativeNetworkId=Default(VLAN 1)` in allen Access-Profilen bleiben — sonst kein DHCP, kein
Internet für diese Geräte. Fix nach TP4.
- ⚠️ **Bekannter Fehler `untagNetworkIds` (Analyse 2026-06-15 via Controller-API v2):**
Access-MAIN hat `untagNetworkIds=[VLAN1, VLAN20, VLAN10]` (3 untagged!) statt `[VLAN10]`.
Access-INFRA hat `untagNetworkIds=[VLAN20, VLAN1]` statt `[VLAN20]`. Geräte erhalten Frames
aus mehreren VLANs ungetaggt → Inter-VLAN-Tests verfälscht. Fix erst sinnvoll nach TP4 zusammen
mit PVID-Korrektur.
- **Sichere Testports (kein kritisches Gerät):** P3, P5, P6.
- ✅ Portainer-Ablösung: **Komodo** empfohlen (einzige Option mit nativem MCP `mcp-server-komodo`). Eigener Task.
- Abh. erfüllt: TP1 (GE8 Trunk ✅).
- **→ TP4 wird vorgezogen:** Alle VLANs im Controller definiert (status=false = kein Management-IP, normal).
Traffic läuft noch auf VLAN 1 bis TP4 die L3-Interfaces + DHCP-Server je VLAN aktiviert.
### TP2.1 — Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) ▸ ✅ FERTIG (2026-07-16, User solo)
- **Root Cause:** beim VLAN20-Umzug des Omada-Controllers (TP14.1, 2026-07-10) ist die Controller↔sw2-Verbindung zerbrochen (sw2 seither „Disconnected", ursprünglich als reiner Mgmt-Loss vertagt). Beim Rettungsversuch hat der User versehentlich einen **Werks-Reset** am Switch ausgelöst. Ein anschließender Wiederherstellungsversuch (durch Claude) hat Teile der Config **unbemerkt halluziniert** — der damalige „wiederhergestellte" Stand war **nicht vertrauenswürdig**.
- **Entscheidung (User 2026-07-15): Controller-Zustand als invalid betrachtet, sw2 komplett neu — diesmal OHNE Managementsoftware.** Kein Omada-Controller-Re-Adopt, kein Omada-Ecosystem-Anschluss (s. TP15-Korrektur — Omada bekommt generell keine Rolle mehr, auch nicht WLAN). sw2 direkt über eigenes Web-UI konfiguriert, wie ein eigenständiger Switch.
- **✅ 2026-07-16 (User-Aussage): „voll durch, Funktionsweise erfolgreich getestet."** Nach dem gescheiterten Claude-Versuch hat der User sw2 **komplett allein** neu aufgesetzt (Runbook `docs/tp2.1-omada-switch-standalone-runbook.md` als Playbook-Referenz, nicht Schritt-für-Schritt-Log). **Keine formale Einzelschritt-Verifikation dieser Session** (kein Port-für-Port-Protokoll, keine Live-Prüfung durch Claude) — Status beruht auf User-Bestätigung „es geht einfach", nicht auf eigener Nachkontrolle.
- **sw2 bleibt Solo:** „bekommt nie Geschwister" — keine weiteren Omada-Geräte geplant. Interimslösung bis zur Ablösung durch CRS326 (TP15).
- **Offen/nachzuziehen (nicht blockierend):** finale Portbelegung + IP von sw2 in `docs/ip-plan.md`/CLAUDE.md-Hardware-Tabelle nachtragen (aktuell dort noch „Adoption pending" — stale, aber Wert unbekannt, nicht geraten); `omada/.secrets` auf neue Standalone-Creds prüfen; ob Omada-Controller-Container jetzt komplett überflüssig ist.
- Abh.: TP1 ✅ (VLAN-Trunk am Uplink). Verwandt: TP15 (löst sw2 mittelfristig durch CRS326 ab), TP14.1 (Auslöser).
### TP3 — WLAN ▸ 📜 **VOLLSTÄNDIG HISTORISCH — der Ist-Stand steht in TP28, nicht hier**
> 🔴 **Lesehinweis (überarbeitet 2026-08-19): dieser gesamte TP ist Geschichte, von der ersten bis zur
> letzten Zeile.** Er beschreibt zwei überholte Zustände nacheinander — die **CAPsMAN-Ära (Juni/Juli
> 2026)** und danach den **Standalone-Rebuild vom 2026-08-08**. Beides gilt nicht mehr:
> seit **TP28 (2026-08-17)** läuft das WLAN wieder über **CAPsMAN, mit `crs326-core` als Manager**;
> ap1/ap2 sind reine CAPs ohne eigene WLAN-Konfiguration.
>
> ⚠️ **Bis 2026-08-19 trug dieser TP im Header „beide hAP ax² sind reine Standalone-APs, CAPsMAN
> abgeschafft" und der Lesehinweis verwies als „Ist-Stand" auf den 08-08-Eintrag.** Wer die WLAN-Frage
> hatte, landete zuerst hier — und bekam die falsche Antwort. Genau daran ist eine frische Session am
> 2026-08-19 hängengeblieben. **Für den aktuellen WLAN-Stand: TP28 lesen, für Funkparameter TP25.**
> Der Wert dieses TPs sind die Gotchas (VLAN-Leck ohne `datapath`, Bring-up-Fallen), nicht der Status.
- ✅ **5 sichtbare SSIDs live, beide APs, beide Bänder, je SSID→VLAN** (wifi datapath vlan-id):
`dirtyvader-main`(10, WPA3-only, FT), `-iot`(30, **nur 2,4 GHz**, WPA2+3), `-media`(40, WPA2+3, **FT=no** wg. Sonos),
`-untrusted`(50, WPA2+3), `-guest`(90, WPA2+3). Alt-SSIDs `install*`/`BF-Main`/`wv*` entfernt. Temp-PSK (User setzt echte).
- ✅ **CAPsMAN-Architektur:** ap1=Manager+AP (lokale Radios), ap2=CAP (dynamisch via Provisioning, `remote-cap=Ok`).
**Kernlektion:** Der Manager kann seine **eigenen** Radios NICHT über CAPsMAN provisionieren → ap1 lokal zuweisen
(Master via `set` + statische Slave-VAPs), ap2 zieht Master+Slaves dynamisch. Ein CAP hält **keine** Eigen-Config.
- ✅ **Kanal-Plan (non-DFS, überlappungsfrei):** ap1 ch36/5180 + ch1/2412 · ap2 ch149/5745 + ch11/2462.
(Auto-Channel hatte beide APs auf 5500-DFS/2437 gelegt = Co-Channel → fix gepinnt. ap2-Kanaländerung braucht
`remote-cap provision`-Push.) `bound`-Problem damit obsolet/sauber gelöst.
- ✅ **Sonos-Migration abgeschlossen (2026-07-02):** alle 10 Sonos auf `dirtyvader-media`/VLAN 40 mit MAC-fest
reservierten DHCP-Leases in `dhcp-v40`. Household geschlossen, iOS-App findet alle Geräte (mDNS via Repeater).
Inventar/IPs = SSoT `docs/sonos-inventar.md` (IPs haben sich verschoben — Arc jetzt `.199`, Arbeitszimmer-Paar auf `.191/.192`).
- **Vertagt:** hidden `dirtyvader-infra`(20)+`-mgmt`(99) (Mgmt verkabelt). **TP8:** eine 802.1X-SSID
(`dirtyvader-secure`) mit FreeRADIUS-Dynamic-VLAN → dann PSK-`main`/`infra` einstampfen (Konsolidierung).
- **SSID-Strategie-Entscheid (Begründung):** unterschiedl. WPA-Typ/Band erzwingen separate SSIDs; „alles in 1 SSID
per Auth" nur via 802.1X (Geräte ohne EAP wie Sonos/TV/IoT brauchen ohnehin PSK) → Hybrid in TP8.
- **Mesh = NEIN** (beide APs wired-trunk → Loop-Risiko). DoH-Upstream = Cloudflare Gateway (seit TP4 aktiv).
- Abh. erfüllt: TP4 ✅ (L3/VLAN-Interfaces). Folge: TP8 (802.1X), Sonos-Migration.
- 🔄 **CAPsMAN wird abgeschafft (User-Entscheid 2026-07-22) — WLAN-Neuaufbau from scratch.** Anlass: WLAN dauerhaft instabil, besonders wenn **beide** APs laufen. Beide APs bekommen künftig **eigenständige, von Nautobot Golden Config identisch generierte RouterOS-Configs** statt Manager/CAP.
- **Begründung (kein Widerspruch zu Nautobot, aber ein echter Reibungspunkt):** CAPsMAN erzeugt auf dem CAP **dynamische** Objekte (`wifi13–22`), die nie in dessen eigener Config stehen → Golden-Config-Compliance sieht auf ap2 faktisch nichts. Genau diese Halbheit war die Ursache von TP3.1 (CAPsMAN schrieb `datapath.vlan-id` nicht auf die Leitung und legte die tagged-Member am CAP nicht an).
- **Was NICHT verloren geht:** 802.11r/k/v braucht kein CAPsMAN, sondern nur gleiche SSID/PSK/Mobility-Domain → Fast-Roaming bleibt. Preis: Radio-/Kanaländerungen sind zwei Deployments statt eines Provision-Pushes — mit Nautobot ohnehin ein Job-Run.
- **Reihenfolge:** WLAN-Neuprovisionierung erst **nach** dem CRS326-Bring-up (TP15), da Routing/DHCP/DNS vorher umziehen.
- ✅ **DURCH (2026-08-08): beide APs als reine Standalone-APs neu provisioniert, CAPsMAN weg.** ap1 `10.40.99.11` (an core `ether15`), ap2 `10.40.99.12` (an branch `ether24`); Uplink geräteseitig auf **`ether1`** (PoE-Port, `poe-out=off`) umgestellt, ether2 aus dem Trunk entfernt. 6 SSIDs (main WPA3+FT / infra/iot/media/untrusted/guest WPA2+3, iot nur 2,4 GHz, media FT=no); **jede SSID mit eigenem `datapath` bridge+vlan-id** → das TP3.1-„cfg-main-2g ohne datapath"-Leck ist strukturell weg. Kanäle non-DFS ap1 ch36/ch1, ap2 ch149/ch11. Client-Test grün (VLAN10/30-Leases korrekt). Configs `routeros/.secrets/ap{1,2}-prov-base.rsc`, PSKs `routeros/.secrets/wlan-psks.env`, As-built → Nautobot via `nautobot/ingest-ap-rebuild.py`. **Einheitliches Admin-PW auf allen 4 MikroTik** (User-Vorgabe). Henne-Ei beim Bring-up: Werks-`192.168.88.1` über Core-Temp-Pfad (`192.168.88.2` + masq, wieder entfernt) via scp+`/import`; Werks-Inline-Overrides auf wifi1/wifi2 mussten geleert werden (`!configuration.ssid !security.*`), sonst funkt die Main-VAP die Werks-SSID. Offen: DoH auf core (nur plain 1.1.1.1), breiterer CLAUDE.md-Gotcha-Cleanup (ap1-als-L3-Router-Referenzen sind überholt).
### TP3.1 — ap2 VLAN-Trunk-Fix ▸ ✅ Config live (2026-07-01) — `routeros/tp3.1-ap2-vlan.rsc`
- **Root Cause:** ap2 (CAP, local forwarding) hatte `bridge vlan-filtering=no` → `datapath.vlan-id` wurde nie
auf die Leitung geschrieben → **ALLE** WLAN-Clients auf ap2 (main/media/iot/…) egressten **ungetaggt in VLAN1**
(defconf/`10.40.41.x`) statt ins Ziel-VLAN. Symptom: iPhone (VLAN10) im OG „verbunden, aber sporadisch kein
WLAN-Symbol" — beim Roaming ap1↔ap2 sprang es VLAN10↔VLAN1 (Subnetz-Flip → Abbruch). Sonos-Terrasse landete
auf ap2 fälschlich auf `10.40.41.183`. TP3 „ein CAP hält keine Config" gilt nur für Manager-Forwarding — hier
local forwarding, daher **braucht ap2 doch VLAN-Config**.
- ✅ **Fix (wie ap1):** ap2-Bridge `vlan-filtering=yes`; VLAN1 untagged=bridge,ether2 (Mgmt), VLANs 10/30/40/50/90
tagged auf ether2. Da nur die Master-Radios wifi1(5G)/wifi2(2.4G) Bridge-Ports sind (Slave-VAPs forwarden über
den Master), zusätzlich **wifi1/wifi2 als tagged-Member** je Client-VLAN (wifi1: 10/40/50/90, wifi2: 10/30/40/50/90).
CAPsMAN legt diese auf dem CAP **nicht** automatisch an → manuell gesetzt. Danach `remote-cap provision`.
- **Lockout-Risiko gehandhabt:** ap2-Mgmt (10.40.41.2) untagged VLAN1 blieb erhalten; kein Scheduler (device-mode=home)
→ Recovery = Power-Cycle OG. Ausgeführt im leeren-registration-table-Fenster.
- ⏳ **Offen — funktionaler OG-Test durch User:** Handy auf `dirtyvader-main` im OG → Lease muss `dhcp-v10`/`10.40.10.x`
sein (nicht `defconf`/`10.40.41.x`). Falls kein DHCP: Omada-Port-2-Trunk (tagged 10/30/40/50/90) prüfen.
- Abh.: TP3 ✅, TP4 ✅ (Bridge-VLAN-Muster gespiegelt). Config ap2-lokal (nicht CAPsMAN-verwaltet).
- 🔁 **Interim-Härtung 2026-07-06 (Migrations-Provisorium, fliegt mit VLAN1 raus):** Symptom „kein DHCP auf
`dirtyvader-main`, Altnetz ebenso, nichts geändert". Diagnose via MikroMCP (ap1+ap2): **Dual-DHCP-Race** — dieselben
Main-MACs (`56:71:6C:22:E6:A9`, `F2:62:4C:E1:AF:09`, `C0:C7:DB:DE:71:BE`) hielten gleichzeitig eine korrekte
VLAN10-Lease (`dhcp-v10`/`10.40.10.x`) **und** eine VLAN1-Lease (`defconf`/`10.40.41.18x`); `defconf` und `dhcp-v10`
boten parallel → `offering ... without success` + Minutentakt-Flapping. `bridge`-ARP auf ap1 = 333 Einträge (flache
VLAN1 lebt voll weiter; IoT/Shelly untagged auf `10.40.41.2xx`). **Griff:** auf ap2 die Master-Radio-Bridge-Ports
`wifi1`/`wifi2` von `frame-types=admit-all` auf **`admit-only-vlan-tagged`** — untagged Main/Slave-Frames werden
verworfen statt in VLAN1 gedumpt (im Zielbild ohnehin korrekt, daher kein Rückbau nötig, verschwindet mit VLAN1).
`ether2` bleibt `admit-all` (VLAN1-Mgmt/Trunk, kein Lockout). **Rollback:** `frame-types=admit-all` auf beiden Ports.
⚠️ **Rest-Leak bleibt: cudy1 (altes WLAN)** bridged Clients weiter untagged in VLAN1 — nur per Switch-Port-Tag bzw.
Alt-WLAN-Abbau (Migration) zu schließen, NICHT auf der MikroTik-Seite. Zusätzlich `st-main rrm=yes wnm=yes` (802.11k/v,
behandelt nur das Roaming-Symptom). SSoT-Detail: Memory `project_vlan1_dhcp_incident`.
- 🎯 **ROOT CAUSE des ap1-Lecks gefunden (2026-07-22, live aus `/interface/wifi/configuration print`) — es ist KEIN `frame-types`-Problem:** `cfg-main-2g` (SSID `dirtyvader-main` auf 2,4 GHz, gebunden an Master-Radio `wifi2`) hat **überhaupt keinen `datapath`-Block** — weder `bridge` noch `vlan-id`. Das 5-GHz-Pendant `cfg-main-5g`/`wifi1` taggt sauber nach VLAN 10, **2,4 GHz taggt gar nicht** → egress ungetaggt in VLAN 1. Ein Client, der zwischen den Bändern **derselben SSID** wechselt, springt damit `10.40.10.x` ↔ `10.40.41.x` — Subnetzwechsel mitten in der Verbindung. Erklärt das Dual-DHCP-Race und das „verbunden, aber sporadisch kein WLAN" ohne jede Zusatzannahme. **Fix nicht mehr direkt anwenden** — geht mit der WLAN-Neuprovisionierung (CAPsMAN-Abschaffung, TP3) über Nautobot ein. Nebenbefund: `cfg-infra` existiert auf ap1, wird von **keinem** Interface benutzt (deckt sich mit TP3.3), und ap1 funkt real auf **ch36/ch1** — die Interfaces überschreiben das Channel-Profil `ch-5g` (5745/ch149).
- 🔴 **Update 2026-07-15: gleiches Leck jetzt auf ap1 vermutet.** `dirtyvader-main`-Clients bekommen zeitweise
`10.40.41.x`-IPs statt `10.40.10.x` — identisches Dual-DHCP-Race-Muster wie oben, aber die 2026-07-06-Härtung
wurde **nur auf ap2** gemacht. Nächster Schritt: gleiche `admit-only-vlan-tagged`-Umstellung auf ap1s lokalen
Main-Radio-Bridge-Ports (⚠️ NICHT `ether2` anfassen — das ist ap1s Mgmt-/WAN-Trunk-Pfad). Diagnose+Fix-Schritte:
`docs/tp19-vlan1-cudy-teardown-runbook.md` Phase A (~30-60 Min). Diese Härtung ist wie die ap2-Variante ein
Provisorium, das mit TP19s VLAN1-Abbau automatisch obsolet wird.
### TP3.2 — WLAN Coverage / P750W-Discovery ▸ ✅ **ABGESCHLOSSEN (2026-08-17, User-Test erfolgreich)** — `docs/wifi-p750w-coverage-and-map.md`
- ✅ **Brother P750W (VLAN30) gelöst:** DHCP-`client-id`-Fix (matcht über MAC → feste `10.40.30.5`); **an ap1 gepinnt**
(geordnete wifi-access-list `accept w2-iot` → blanket `reject`, überlebt ap2-Re-Provision) — dozende Legacy-Clients
nur am co-located-GW ap1 per ARP erreichbar, CAP ap2 nicht; mDNS `vlan30` in `mdns-repeat-ifaces`+5353 → in MAIN
auffindbar. Druck+Selbsttest OK. Web-UI `http://10.40.30.5/general/information.html`.
- ✅ **Nebenbei:** `multicast-enhance=enabled` (iot+media), Guest `ft=yes`, INFRA-SSID(20) angelegt (**nur ap2**),
Steering-Profil `st-main`/`neighbor-group=bf-main` auf beide Main-Configs.
- **Nomenklatur:** dynamische ap2-CAP-Slaves (`wifi13–22`) nicht praktikabel umbenennbar (`name-format` unzuverlässig)
→ Map dokumentiert statt umbenannt. Backups `bf-ap{1,2}-pre-nomen-20260702.backup`/`.rsc` auf den Geräten.
- ⏳ **Offene Coverage-Arbeit → ausgelagert nach TP3.3** (Fortsetzung in neuer Session).
### TP3.3 — WLAN Coverage-Kur ▸ 📜 **IN TP25 AUFGEGANGEN (2026-08-17)** — offene Arbeit steht dort, nicht hier
> ⚠️ Der Header sagte bis 2026-08-19 „🔜 TODO (Fortsetzung, neue Session)" — dieselbe Falle wie bei TP3:
> ein Status, der nicht mitgezogen wurde. Der Re-Walk und die Dead-Spot-Kartierung sind **Schritt 5 von
> TP25** und zwar bewusst **nach** den Funkparameter-Fixes; die 802.11k/v-Reparatur ist am 2026-08-13
> erledigt worden. Was hier steht, ist Begruendung und Messhistorie.
- ✅ **BEHOBEN 2026-08-13, noch am selben Abend (User: „aus Zeitgründen zuerst 11k/v, der nächste Call steht an").** `st-main` (`rrm=yes wnm=yes neighbor-group=bf-main`) auf beiden APs wiederhergestellt und an `cfg-main-5g`/`cfg-main-2g` gehängt — **via GC-ConfigPlan**, bewusst nicht per Direkt-Write: der Push läuft so NAS→Kabel→AP und nicht über die WLAN-Verbindung, die man gerade umkonfiguriert. `/system backup` je AP davor (`ap{1,2}-pre-steering-20260813.backup`). **Gegengeprüft am Gerät** (nicht am Plan-Status): `st-main rrm=true wnm=true ng=bf-main`, beide Main-Configs verweisen darauf.
- ⚠️ **Erwartete Nebenwirkung: alle Clients haben kurz reassoziiert** (ap1 25→2, ap2 7→14 unmittelbar danach) — `steering=` auf einer `wifi configuration` zu setzen re-applied die VAPs, auch die nicht angefassten. Nach ~2 Min eingependelt (ap1 15 / ap2 13, VLAN30 = 28 Hosts auf der Core-Bridge). **Nicht während eines Calls machen.**
- ✅ **In GC verankert:** Steering steht jetzt im AP-Zweig des Templates (`st-main`/`bf-main` als Konstante — es gibt genau eine Roaming-Domain pro Standort) + neues ComplianceFeature **`wifi-steering`**. Eine künftige Neuprovisionierung kann es damit nicht mehr stillschweigend verlieren — genau das war der Fehler vom 2026-08-08.
- ⏳ **Offen: der Praxistest.** Dieselbe Route noch einmal gehen. Reißt es weiterhin ab, ist es echte Coverage → Dead-Spot-Kartierung.
- 🔴 **Der Befund (2026-08-13): Steering war auf BEIDEN APs weg.** `/interface/wifi/steering` = **0 Einträge** auf ap1 *und* ap2 (live geprüft). Vor dem Rebuild war `st-main` mit `rrm=yes wnm=yes` + `neighbor-group=bf-main` auf beiden Main-Configs gesetzt (s. TP3.2 + TP3.1-Interim). Die Neuprovisionierung am 2026-08-08 aus `routeros/.secrets/ap{1,2}-prov-base.rsc` hat es **nicht wieder angelegt**.
- **Wirkung:** kein **802.11k** (Neighbor Reports → der Client kennt die Nachbar-APs gar nicht) und kein **802.11v** (BSS Transition → der AP kann den Client nicht wegschicken). Der Client bleibt am sterbenden AP kleben, bis die Verbindung zusammenbricht, und muss dann komplett neu scannen + assoziieren = **harter Abriss statt Übergabe**. Genau das Symptom „einmal durch die Wohnung, WLAN weg" (User-Erlebnis 2026-08-13 während eines Telegram-Videocalls, musste auf 5G ausweichen).
- ⚠️ **`ft=yes` ist KEIN Gegenbeweis** (auf beiden APs gesetzt, `ft-mobility-domain` beidseitig leer = gleicher Default, FT selbst also intakt). **802.11r macht die Übergabe schnell, sobald der Client sie beschließt — es bringt ihn nicht dazu, sie zu beschließen.** Ohne 11k/v fällt die Entscheidung viel zu spät. „FT ist an" ≠ „Roaming funktioniert".
- **Reihenfolge-Konsequenz:** Diese Regression **vor** der Dead-Spot-Kartierung beheben und dieselbe Route nachlaufen — sonst kartiert man Löcher, die in Wahrheit Roaming-Verhalten sind, und zieht daraus womöglich Hardware-/Platzierungs-Entscheidungen (s. „ap1 ins Sideboard"-Idee unten).
- **Weg (Governance):** Steering gehört ins **GC-Template** (`/interface/wifi/steering` + `steering=` an der Main-Configuration) → feature-scoped Deploy. Kein Direkt-Write. Betrifft `wifi-configuration` (bereits Compliance-Feature) + ein neues Feature `wifi-steering`.
- 💡 **Idee „ap1 ersetzt den Sideboard-Switch" (User 2026-08-13):** ap1 hat 4 freie Ports (`ether2–5`; `ether3/4/5` sind bereits Bridge-Ports mit `pvid=1`, `ether2` ist seit der ether1-Umstellung **kein** Bridge-Port mehr und müsste wieder aufgenommen werden) → die Wohnzimmer-Multimediageräte könnten direkt an ap1, der Smart-Managed-Switch entfiele. **Technisch machbar** (untagged VLAN40 wie `core ether2`), **aber platzierungsgebunden:** ein Media-Sideboard ist RF-mäßig einer der schlechtesten AP-Standorte (geschlossen, Metallgehäuse der AV-Geräte, niedrig). **User-Vorgabe: Platzierung hat Vorrang vor dem eingesparten Gerät.** Entscheidung daher erst **nach** der Steering-Reparatur + Re-Walk.
**Problem:** Kanten/Terrasse grenzwertig — Clients (iPhone/iPad) landen bei **−76..−89 dBm** und springen zwischen
allen 4 Main-Radios (ap1-5G/2,4G ↔ ap2-5G/2,4G) → „0–1 Striche + häufiger Abriss"; **Terrasse auch mittags** schlecht.
FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
**Bereits geklärt/erledigt (nicht nochmal aufrollen):**
- NICHT config-verursacht (ap1 kein Reboot, RF-Konfig unverändert). **TX schon am AT-Max** (18/15 dBm, EIRP-Deckel — kein Hebel).
- Kanalplan intern überlappungsfrei (ap1 ch1/ch36, ap2 ch11/ch149). **Steering** `st-main`/`neighbor-group=bf-main`
auf beiden Main-Configs gesetzt (hilft Client-Entscheidung, macht Signal aber nicht stärker).
- **ap1-only ist vom Tisch** (echte Funklöcher) → beide APs nötig, aber besser platziert.
**Offene Arbeit:**
1. **Dead-Spot-Kartierung** (Tageslicht): User stellt sich in jeden Problempunkt (Terrasse zuerst, entfernte Räume,
dicke Wand) → je Punkt `registration-table` + `monitor` (Signal je Radio/Band/AP). Zusätzlich Nachbar-WLAN-Scan
`/interface/wifi/scan` je AP (**störend** — wirft Clients kurz ab) gegen Abend-Congestion. Output → SSoT-Doku.
2. **Remediation nach Befund:** Platzierung (ap2 Richtung Schwachseite umstellen / **3. AP** / Outdoor-Unit / Kabelweg);
Roaming-Feintuning (802.11v `transition`-Schwellen ab 7.21, Band-Overlap reduzieren); Kanalplan aus Scan nachziehen.
3. **INFRA(20) auf ap1 ergänzen** (fehlt — sendet nur auf ap2): lokaler `w2-`/`w5-infra`-Slave analog w2-/w5-media.
4. Danach Re-Walk-Test (wie 2026-07-02, aber mit Kartierung als Baseline).
**Sicherheitsnetz:** Backups `bf-ap{1,2}-pre-nomen-20260702.backup`/`.rsc` auf den Geräten. Kontext-Memory `project_wifi_coverage`
+ `ref_wifi_powersave_multicast`. Abh.: TP3 ✅, TP3.1 ✅, TP3.2 ✅.
### TP4 — Router-Cutover (hAP#1 = Router + WAN-Edge) ▸ ✅ FERTIG (2026-07-01) — ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1); alles unten beschreibt den damaligen ap1-Aufbau und gilt als **Historie/Referenz**, nicht als Ist
- ✅ **Bridge VLAN-Filtering aktiviert** (`vlan-filtering=yes`); VLAN-Tabelle: VLAN1 untagged bridge+ether2, VLANs 10/20/30/40/50/90/99/100 tagged bridge+ether2.
- ✅ **VLAN-Subinterfaces:** vlan10/20/30/40/50/90/99 auf bridge; je GW `.1` (10.40.10.1 … 10.40.99.1).
- ✅ **DHCP:** dhcp-v10…dhcp-v99 je auf eigenem VLAN-Interface, pools .100-.200, GW+DNS = VLAN-GW.
- ✅ **DNS:** RouterOS-nativ, allow-remote-requests=yes. **Upstream seit 2026-06-16: DoH Cloudflare Gateway** (`use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query`, verify-doh-cert, `servers=1.1.1.1` als Bootstrap; FB-Upstream abgelöst). Statische Einträge für alle Infra-Hosts unter `bf.lan` (13 Einträge, Transition-IPs bis PVID-Fix).
- ⚠️ Auto-Suffix `.lan` (RouterOS-Einschränkung, kann nicht auf leer gesetzt werden) — TP5-Cleanup.
- ✅ **NAT:** srcnat masquerade out-interface=vlan100 (ap1 = Public-Edge; kein Double-NAT mehr). Fallback-Konfig für ether1 bleibt reversibel dokumentiert (`routeros/wan-cutover-runbook.md`).
- ✅ **Routing-Tabelle:** 0.0.0.0/0 via Liwest-Gateway auf vlan100; alle 7 VLAN-Subnetze als Connected-Routes aktiv.
- ✅ **Edge-Cutover vollzogen (2026-07-01):** FritzBox 6660 physisch weg (Liwest hat gegen ein Arris-Kabelmodem im Bridge-Mode getauscht — kein Zurück). WAN = `Arris → sw1 gi16 Access VLAN100 → gi15 Trunk → ap1 ether2 → vlan100 auf bridge` (**NICHT** ether1). ap1 hält Public-IP direkt (Liwest, kein CGNAT). **WAN-Input-Seal live** (TP10-seal-Regeln: est/inv/dhcp/icmp/lan/wandrop im `input`-Chain, `list_firewall_rules` 2026-07-02 verifiziert). Telefonie war auf der FB und wird nicht weitergeführt (User-Bestätigung 2026-07-02).
- **DNS-Entscheidung** (Technitium **und** externer BIND verworfen): RouterOS-DNS als Resolver, keine DHCP→DNS-Auto-Registrierung (soweit möglich). Infra-Hosts als statische `/ip dns static`-Einträge unter `bf.lan`; dynamische Clients bewusst ohne Namen. Filtering/DNSSEC via DoH-Upstream (TP3). HA: 2. Resolver auf ap2, statische Einträge beidseitig pflegen.
- Abh.: TP1 ✅. TP3-Abhängigkeit entfällt (WLAN läuft weiterhin über FB bis TP3).
- **Ermöglicht jetzt:** PVID-Fix in Omada-Profilen (MAIN→PVID10, INFRA→PVID20), untagNetworkIds-Bereinigung, DHCP-Reservierungen für NAS/macmini/pve auf neue VLAN-Subnetze migrieren.
### TP5 — Firewall & Inter-VLAN-Policy ▸ ✅ **DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf** · **+ E5 (2026-08-19)** — 📋 `docs/tp5-deny-by-default-runbook.md`
> 📌 **„DURCH" heißt nicht „abgeschlossen".** E5 (MAIN→IOT) kam einen Tag nach dem Drop — unter deny-by-default
> braucht **jeder** neue Dienst über eine VLAN-Grenze seine Regel. Weitere Etappen sind normaler Betrieb,
> kein Rückschritt. Der Weg dahin ist immer derselbe: **erst den Drop-Log lesen**, dann die Regel schreiben.
- ✅ **ETAPPE 5 (2026-08-19): `MAIN → IOT` aufgemacht — `routeros/tp5-e5-main-iot.rsc`, Backup `core-pre-tp5-e5-main-iot-20260819.backup`, Compliance danach 92/92, 0 Drifts.**
- **Auslöser (User):** Brother P750W und die Shellys aus VLAN10 nicht erreichbar. Der Drop arbeitete
korrekt — es gab schlicht **nie** eine `vlan10→vlan30`-Zeile. Offen waren nur `MAIN→MEDIA`,
`MAIN→INFRA`, `MAIN→MGMT`; IOT fehlte als einziges Paar in der Liste.
- 🔴 **Merkregel: der Drucker fällt mit `udp/161` auf, NICHT mit `631`.** Im Drop-Log stehen
40 Zeilen SNMP nach `10.40.30.5` und kein einziges IPP — macOS fragt zuerst den SNMP-Status ab
und bricht ab, bevor es überhaupt zu IPP kommt. **Wer nur 631 whitelistet, repariert nichts.**
- **Log-Matrix `in:vlan10 out:vlan30`:** 497 ICMP · 489 `udp/5353` · 489 `udp/137` ·
237 `udp/3722` (Apple-Continuity → `.30.201`) · 40 `udp/161`. Quellen: `.10.183` (1596 Zeilen,
Sweep über alle 254 Adressen), `.10.180` (118), `.10.82` (38).
- **Entscheid (User): voll einbahnig, kein Portfilter.** MAIN steht zu INFRA/MEDIA/MGMT ohnehin
ungefiltert offen — und INFRA hält NAS, Proxmox und Nautobot, also deutlich mehr Wert als die
Shellys. MAIN→IOT eng zu filtern wäre Symbolik, kostet aber bei jedem neuen IoT-Gerät eine Runde
Fehlersuche. **Der Sicherheitswert sitzt in der Gegenrichtung:** `IOT→MAIN` bekommt bewusst
**kein** `accept` und bleibt gedroppt; Antworten laufen über `defconf: accept est,rel,untracked`.
- **Abnahme am Gerät** (von `10.40.10.82`, also der Quelle aus dem Log): `.30.5` ICMP 0 % Loss,
`snmpget sysName.0` → `P750W`, tcp/631+9100+80 offen; `.30.202/.203/.221` HTTP **200**.
`.30.201` bleibt still — kein Firewall-Thema mehr (die Regel ist ungefiltert), Gerät aus.
- ⚠️ **Bewusste Nebenwirkung:** der Subnetz-Sweep von `.10.183` läuft ab jetzt durch statt im
Drop-Log aufzulaufen. Betriebsneutral, aber der Log wird an dieser Stelle leiser — **nicht mit
„Problem gelöst" verwechseln**, wenn später jemand die Log-Rate als Metrik liest.
- 🔧 **Werkzeug-Fix nebenbei: `gc-manual-plan.py --assert` URL-encodet die Query jetzt.** Ein
Kommentar mit Leerzeichen (`TP5-E5: MAIN->IOT`) warf `http.client InvalidURL` — **nach** dem
erfolgreichen Deploy, d.h. der Change war durch und nur die Gegenprüfung crashte. Das ist die
schlechtere Reihenfolge: man steht mit einem Traceback da und weiß nicht, ob es gewirkt hat.
- ✅ **ETAPPE 3 + 4 DURCH (2026-08-18, nachts).** `forward` und `input` enden jetzt mit `drop`, die
Log-Regel steht in beiden Ketten **davor** und protokolliert weiter, was der Drop frisst.
Artefakte: `routeros/tp5-etappe2-nachtrag.rsc`, `-etappe3-drop.rsc`, `-etappe4-input-mgmt.rsc`,
`-etappe4-nachtrag-discovery.rsc` (+ die beiden autorevert-abbestellen-Plaene).
Backups `core-pre-tp5-{nachtrag,etappe3,etappe4}-20260818.backup`.
- 🔴 **Das 70-Sekunden-Ergebnis aus Etappe 2 (die Log-Regel sei verstummt) war falsch.** Ueber
dreieinhalb Stunden gemessen liefen **119 Zeilen** auf, in vier Gruppen — drei davon haetten den
Drop zum Ausfall gemacht:
| Zeilen | Was | Warum es die Whitelist nicht traf |
|---|---|---|
| 92 | Matter `udp/5540`, SwitchBot `.30.227` → HA | die E2-Regel `IOT->INFRA Dienste` ist **TCP-only** |
| 18 | **`10.40.20.31`** → `.99.{2,11,12}:22` | der Netmiko-Deploy kommt vom **`nautobot-worker`**, nicht vom Web-Container `.20.30`, auf den die E2-Regel lautete (deren Zaehler stand auf 0) |
| 8 | `.99.x:8728` → HA | Rueckrichtung des Mgmt-Pfads, nach Conntrack-Ablauf als `new` |
| 1 | Shelly `.30.225:80` → HA | gleiches Muster |
➡️ **Merkregel: ein Zaehlerstand ueber 70 Sekunden ist keine Messung.** Und wer den Deploy-Pfad
whitelistet, muss die IP des **Workers** nehmen, nicht die des Web-Containers.
- 🔴 **Fund in Etappe 4, der erst spaeter zugeschlagen haette: die CAPs finden ihren Manager per
Layer-2-Discovery.** `/interface/wifi/cap` auf ap1 zeigt `discovery-interfaces=vlan99` und
`current-caps-man-address: 04:F4:1C:B4:17:76%vlan99` — eine **MAC**, keine IP. Der input-Drop
verwarf MNDP (`udp/5678`); bestehende CAPWAP-Sitzungen liefen als `established` unbeeindruckt
weiter, kaputt gewesen waere es **erst beim naechsten AP-Reboot**. Geschlossen mit
`TP5-E4: MNDP discovery` (LAN) + `TP5-E4: CAPsMAN capwap` (nur vlan99).
- 📌 **`input` ist der riskantere Teil, nicht `forward`:** der Core ist DNS- **und** DHCP-Server fuer
alle sieben VLANs, und beides lief allein ueber `TP10-seal-lan`. Reihenfolge deshalb zwingend
Ersatzregeln → `seal-lan disabled=yes` → Drop. `seal-lan` wurde **abgeschaltet, nicht geloescht**
(schnellerer Rueckweg, und der Auto-Revert greift genau dort an).
- ✅ **Nebeneffekt wie vorhergesagt: die 5 `TP5: mDNS in vlanXX`-Input-Regeln sind endlich scharf**
(vlan30 = 87, vlan20 = 13 Pakete binnen Minuten; sie standen seit dem 2026-08-14 auf 0).
- 🎯 **Abnahme am Geraet:** `forward`-Drop **11 Pakete in 15 Minuten — ausschliesslich die eigene
pve→branch:22-Sonde**; `input`-Drop nur Broadcast-Rauschen (MNDP auf VLAN1-nativ, NetBIOS
137/138 aus VLAN20, SSDP-Announcements einer Sonos aus VLAN40). Haus im Normalbetrieb:
23 Leases, 30 WLAN-Clients, beide CAPs `Ok`, WAN steht. Funktionstest: Shelly aus INFRA `200`,
Internet `301`, MacBook erreichbar, `branch:22` von pve aus **zu** (korrekt).
**Und der Pfadbeweis: ein GC-Deploy auf ap2 lief MIT scharfem Drop durch.**
- 🛟 **Auto-Revert-Scheduler** (10 min, Name ohne Bindestrich) lief vor jedem der beiden Schnitte
und wurde nach bestandener Abnahme per eigenem Plan abbestellt.
- 🔜 **Bleibt offen:** die **DNS-Bypass-Sperre** — sie gehoert bewusst nicht hierher, sondern an das
Ende von TP23 (Stufe 6), wenn die Ausnahme-Oberflaeche nachweislich traegt.
- ✅ **ETAPPE 2 DURCH (2026-08-17) — `routeros/tp5-etappe2-whitelists.rsc`, ConfigPlan `86fbd528`, Backup `core-pre-tp5-etappe2-20260817.backup`.**
- **Basis waren 43.027 Logzeilen** aus dem Fenster 2026-08-16T17:21 → 2026-08-17T14:05 (Kollektor `10.40.20.18`),
also **~21 Stunden echter Nutzverkehr** statt der 10-Minuten-Stichprobe von Etappe 1. Die vollständige Matrix:
| Paar | Zeilen | Dienste (Ziel:Port, absteigend) |
|---|---|---|
| `vlan20 → vlan99` | **20.682** | `8728` auf branch (7468) / ap1 (7468) / ap2 (5731) = HA-`mikrotik`-Integration · `:22` je 8 = Nautobot-Netmiko |
| `vlan10 → vlan20` | **8.082** | macmini `udp/3722` **6234** (Continuity) · NAS `6690` 1578 · Nautobot `8080` 137 · AirPlay `7000` 69 · pve-mcp `3000` 22 · HA `9583` 16 · pve `:22` 12 · macmini `49152`/`udp 59107` je 8 · NAS `:22` 4 · **Ollama `11434`** 2 |
| `vlan20 → vlan10` | **7.123** | Rückrichtung Continuity (eigene Flows je Richtung) |
| `vlan20 → vlan30` | **6.238** | HA pollt ~22 Shellys auf `tcp/80` |
| `vlan30 → vlan20` | 306 | **`.30.249` → HA `8123`** (203) · macmini `49152` (1) |
| `vlan10 → vlan30` | 237 | **nur** `udp/3722` → `.30.201` = das iPhone im IOT-SSID (D2) |
| `vlan10 → vlan99` | 214 | Arbeitsplatz `.10.82` → branch/ap1/ap2 `:22`/`:80`/`:443` |
| `cf-mesh → *` | **137** | `100.96.0.1` → Nautobot `8080` (62) · NAS `6690` (46) · macmini/NAS/branch/ap1/ap2 `:22` · pve `8006` · pve-mcp `3000` |
| `vlan10 → vlan10` | 17 | same-VLAN mDNS `5353` (vom Repeater auf dem Core) |
| **`vlan40`/`vlan50`/`vlan90`** | **0** | ➡️ **D6 bestätigt: Internet-only-Pinning ist risikofrei.** `out:bridge` kommt **nicht mehr vor** (VLAN1-Rückbau hält). |
- **Entscheidungen (User 2026-08-17):** **D1 = MAIN→INFRA pauschal** (MAIN *ist* das Trusted-VLAN; 13 Einzelports
wären eine Wartungsfalle, die bei jedem neuen Dienst still bricht). **D4 = MGMT named sources** — MAIN komplett
+ HA `.20.105:8728` + Nautobot `.20.30:22` + `cf-mesh`, Rest drop. **D5** = beobachtetes `8123` + MQTT als Absicht;
**Influx `8086` NICHT** (nie beobachtet, der Weg läuft über HA).
- 🎯 **ABNAHME am Gerät bestanden:** 11 Regeln (`*1B`–`*25`) existieren, `disabled=false`, **alle vor der Log-Regel**
(`place-before`, sonst wäre das Gate unten nicht messbar), Selbstprotokoll `tp5e2: ENDE whitelist-regeln=11`.
**Und der Zähler der Log-Regel steht: 54.129 bei 14:17:06 und unverändert bei 14:18:13; die letzte Zeile im
Kollektor ist `14:17:16` — genau der Deploy-Zeitpunkt.** Die Regel ist also **verstummt**.
- ⚠️ **Aber das ist noch KEIN Freibrief für Etappe 3.** 70 Sekunden Stille sind keine Aussage — das Runbook
verlangt bewusst „einige Tage", weil Geräte existieren, die **einmal pro Woche** funken (Backup-Jobs,
Drucker-Discovery). **Gate für Etappe 3: mehrtägiges Fenster ohne neue Zeilen.**
- 📌 **Zwei Dinge müssen VOR dem Drop passieren:** (a) **D2** — iPhone `.30.201` ins MAIN-SSID umhängen
(Hands-on Christoph); solange es im IOT-SSID hängt, killt der Drop die 237 `udp/3722`-Flows. Eine Whitelist
darauf wäre instabil, weil iOS **pro SSID** eine eigene Private Address vergibt. (b) `seal-lan` verengen (D4
`input`-Teil), was zugleich die 5 toten mDNS-`input`-Regeln scharf schaltet.
- ✅ **ABNAHME DES VLAN1-RÜCKBAUS BESTANDEN (2026-08-16): `out:bridge` = 0.** 10-Minuten-Messfenster nach dem Wiederanschalten, Logdatei vorher geleert. Vorher waren `vlan20 → bridge` **76 %** des gesamten Inter-VLAN-Verkehrs. Die Rate fiel von ~166 auf ~34 Zeilen/min — der Rückbau hat das Log auf echten Nutzverkehr eingedampft. Wiederaufnahme über `routeros/tp5-etappe1-resume.rsc` (GC, Backup `core-pre-tp5-resume-20260815.backup`).
- 📊 **Beobachtete Matrix, 339 Zeilen / 10 min** — das Arbeitsmaterial für Etappe 2:
| Paar | Zeilen | was genau |
|---|---|---|
| `vlan20 → vlan99` | 126 | **HA `10.40.20.105` → `10.40.99.2:8728` + `10.40.99.11:8728`** (je 67) = der Management-Pfad |
| `vlan20 → vlan30` | 85 | HA → IoT: `.30.211:80` (44), `.30.8:10081` (22), `.30.244:8080` Nuki (22), `.30.5:631` Drucker (4) |
| `vlan10 → vlan20` | 65 | Arbeitsplatz → INFRA |
| `vlan20 → vlan10` | 61 | INFRA → MAIN |
| `vlan10 → vlan10` | 2 | **mDNS `10.40.10.183:5353 → 10.40.10.82:5353`** — s.u. |
- 🔑 **D4 ist damit belegt, nicht mehr geschätzt:** der Management-Pfad ist **HA** (`10.40.20.105`), das über seine vier `mikrotik`-Integrationen `8728` auf branch/ap1/ap2 pollt (core `.99.1` erscheint nicht, weil das `input` ist, nicht `forward`). Dazu MikroMCP vom Arbeitsplatz. **Ohne Whitelist sperrt der spätere Drop die Verwaltung des eigenen Netzes aus.**
- ⚠️ **Neuer Merkposten für Etappe 3 — same-VLAN-Verkehr erreicht die `forward`-Kette:** `vlan10 → vlan10`, unicast **mDNS** zwischen zwei MAIN-Clients, erzeugt vom mDNS-Repeater auf dem Core. Zwei Pakete in 10 Minuten, harmlos — **aber ein blanker Drop am Kettenende würde sie treffen** und damit Discovery *innerhalb* von MAIN beschädigen. Zusammen mit `accept out-interface-list=WAN` die zweite Regel, die bekannt ist und nicht aus dem Log wiederentdeckt werden muss.
- ✅ **D2 vollzogen:** iPhone ist im MAIN-SSID, `10.40.30.201` steht im ARP auf `failed`.
- ✅ **Etappe 1 ausgerollt 2026-08-14** (über Nautobot GC, manual Config Plans; Backups `core-pre-/post-tp5-etappe1-20260814.backup`). Auf dem Gerät: `forward`-Regel `action=log log-prefix="TP5-WOULD-DROP"` am Kettenende, Syslog-Action `tp5syslog` → **Kollektor `10.40.20.18:514`** (syslog-ng-Container `tp5-syslog` auf dem Docker-LXC, Logs `/opt/tp5-syslog/log/`, 10-Minuten-Cron kappt bei 500 MB — der LXC hat nur ~2,5 GB frei). `firewall` ist aus dem Memory-Ring genommen (`topics=info,!firewall`), sonst wäre der On-Box-Log tagelang unbrauchbar. **Blockiert nichts.** Artefakte: `routeros/tp5-etappe1-log-lauf.rsc`, Push-Werkzeug `nautobot/gc-manual-plan.py`. Compliance danach **16/17** (der eine Rotpunkt `ip-address` = bekannte VLAN1-Bring-up-IP `10.40.41.211` aus dem TP22-Backlog, **keine neue Drift**).
- 🔴 **Fund aus dem Log-Lauf, der Etappe 3 bestimmt: die Regel fing anfangs auch allen Internet-Verkehr** (`out:vlan100`, ~¼ der Zeilen in einer ruhigen Minute, unter Last mehr). Die Log-Regel steht deshalb jetzt auf `out-interface-list="!WAN"`. **Konsequenz für den späteren Drop: er darf NICHT bare ans Kettenende** — davor gehört `accept out-interface-list=WAN`, sonst ist das ganze Haus offline. Diese Whitelist-Zeile ist damit bekannt und muss nicht aus dem Log wiederentdeckt werden.
- 🔍 **Erste Befunde nach 60 s** (Rate nach Einengung ~166 Zeilen/min): **76 % des Inter-VLAN-Verkehrs ist `vlan20 → bridge`, also INFRA nach VLAN1** — ein Monitoring-Host `10.40.20.151` scrapet ~10 tote/alte `10.40.41.x`-Ziele, HA `.20.105` zieht auf `10.40.41.100:8006` (pve unter seiner **alten** VLAN1-Adresse, real `.20.100`), `.20.107` auf `10.40.41.10:8080`. **Das ist TP19-Material, das TP5 gerade sichtbar macht.** Weiter: `vlan20 → vlan99:8728` (MikroTik-API auf branch/ap1/ap2 = **der Management-Pfad selbst**, muss in die Whitelist, betrifft D4), `vlan20 → vlan30` (HA → Drucker `.30.5:631`, Shelly, Nuki `.30.244:8080`), `vlan10 ↔ vlan20` (Synology Drive `6690`, Apple-Continuity `udp/3722`).
- ✅ **D2 entschieden (User 2026-08-14): iPhone wird ins MAIN-SSID umgehängt.** Faktenlage war: `10.40.30.201`, dynamische Lease, Hostname `iPhone`, MAC `12:99:A6:AC:B0:CB` = iOS Private Wi-Fi Address, aktiv auf `dirtyvader-iot` mit 748 MB rx. Die statische Reservierung `10.40.10.196` „iPhone" (andere private MAC — iOS vergibt **pro SSID** eine eigene) belegt, dass es dasselbe Telefon ist. **Umhängen ist die Hands-on-Aufgabe von Christoph** (SSID am Gerät wechseln + `dirtyvader-iot` vergessen lassen); technisches Argument: eine Whitelist auf eine rotierende Private MAC wäre ohnehin instabil.
- 📋 **Session-Runbook liegt (2026-08-14): `docs/tp5-deny-by-default-runbook.md`** — Ist-Stand live erhoben, **beobachtete Inter-VLAN-Flow-Matrix** (conntrack + Counter), 6 offene Entscheidungen (D1–D6), 4 Etappen mit Absicherung, Verifikation + Rollback. **Kernansatz: Etappe 1 ist ein reiner Log-Lauf ohne Blockierung** (`action=log` ans Ende von `forward`) — die Whitelist wird aus dem Log abgeleitet, nicht am Schreibtisch geraten. Drei Funde, die vorab eine Entscheidung brauchen: ein **iPhone sitzt in VLAN30**, ein **ICMP-Dauersweep von `.10.82` auf alle Shellys**, und **vlan99 ist Mitglied der `LAN`-Liste** (→ `seal-lan` erlaubt Mgmt-Input aus jedem VLAN). **VLAN50/90 tragen null Inter-VLAN-Verkehr** → Internet-only-Pinning ist dort risikofrei.
- **Deny-by-default + least-privilege** (Ziel). MAIN→INFRA nur Whitelist-Dienste; MAIN↔MEDIA allow (+mDNS); UNTRUSTED/GUEST nur Internet; MGMT isoliert; Router-input gehärtet.
- ⚠️ **Gerätewechsel beachten (2026-08-14 nachgetragen):** der ✅-Block unten war ursprünglich **auf ap1** verifiziert (2026-07-02) — ap1 ist seit 2026-08-08 Werksreset und reiner AP. Die Regeln sind beim Core-Cutover (TP21.1) mitgewandert, waren am neuen Gerät aber **nie gegengeprüft**. Am **2026-08-14 auf `crs326-core` live nachverifiziert** (`list_firewall_rules`, chains `forward` 13 Regeln / `input` 12 Regeln): **alle unten gelisteten Regeln existieren, sind enabled, tragen ihre `TP5:`/`TP10-seal-`-Kommentare und zählen Traffic.** Merkregel: nach jedem Rollenwechsel eines Geräts gilt eine Verifikation vom Vorgänger nicht weiter.
- ✅ **Bereits live** (ap1 2026-07-02 → **re-verifiziert auf `crs326-core` 2026-08-14**):
- `forward accept vlan10↔vlan40` — TP5: MAIN↔MEDIA (AirPlay/Sonos)
- `forward accept vlan20↔vlan40` — TP5: INFRA↔MEDIA (HA↔Sonos)
- `input accept udp/5353 vlan10/vlan20/vlan30/vlan40` — mDNS-Repeater (TP7-Anschluss)
- `forward accept vlan90↔vlan40` + `input accept udp/5353 vlan90` — **GUEST→MEDIA (Sonos/Cast), permanent** (TP7.2, 2026-07-03)
- **WAN-Input-Seal komplett** (TP10-seal-est/inv/dhcp/icmp/lan/wandrop + WG-Hub UDP/51821 accept)
- ⏳ **Offen:** Deny-by-default am Ende von `forward` (**live bestätigt 2026-08-14: die `forward`-Kette endet nach `TP5: MEDIA->GUEST` ohne Drop → alles nicht explizit Erlaubte läuft durch**); UNTRUSTED/GUEST auf Internet-only pinnen; MGMT isolieren; IoT→INFRA-Whitelist bauen.
- 🔍 **Befund beim Re-Verify (2026-08-14) — die 5 `TP5: mDNS in vlanXX`-Input-Regeln sind derzeit tot:** sie stehen **hinter** `TP10-seal-lan` (`in-interface-list=LAN`, action=accept), das jeden LAN-Input schon vorher annimmt → Counter aller 5 mDNS-Regeln stehen auf **0 Pakete**, während `seal-lan` 3,5 Mio. zählt. **Kein Fehler und nichts zu tun** — sie sind die Vorbereitung auf genau den Schritt, der noch aussteht: sobald der Router-`input` gehärtet wird (`seal-lan` verengen/entfernen), werden sie scharf. **Falle für die TP5-Session: erst `seal-lan` verengen, DANN testen** — vorher beweist ein grüner mDNS-Test gar nichts, weil er über `seal-lan` läuft.
- 📌 **Ausgangslage für die Umsetzung (Live-Stand `crs326-core`, 2026-08-14):** `forward` = defconf (est/rel/untracked accept · fasttrack · drop invalid · drop WAN !dstnat) → 2× TP10-WG → 6× TP5-Holes (MAIN↔MEDIA, INFRA↔MEDIA, GUEST↔MEDIA), **kein abschließender Drop**. `input` = TP10-seal komplett (est/inv/dhcp/icmp/lan/wandrop) + WG-Listen 51821 + 5× mDNS. Der Deny-by-default-Griff ist damit **eine Regel am Ende von `forward`** plus die Whitelists davor — nicht ein Umbau der Kette. ⚠️ **Governance:** läuft über Nautobot GC (`ip-firewall-*`-Features aus TP22), **nicht** per Direkt-Write; vorher `/system backup` (TP22-Regel 3). ⚠️ **Rettungspfad:** ein zu früh gesetzter Drop killt Mgmt/DHCP/DNS quer durchs Haus — Reihenfolge Whitelists zuerst, Drop zuletzt, und `device-mode` auf core erlaubt scheduler → **Auto-Revert-Scheduler als Sicherung ist hier möglich** (anders als auf den hAPs).
- 🔊 **Im Paket enthalten (User-Vorgabe 2026-08-14): der macOS-Sonos-Client-Fix.** Ist-Stand: **iOS-App findet alles** (mDNS via Repeater ✅), **HA steuert/gruppiert sauber** (Sonos-Panel, via static hosts + `advertise_addr`, TP7.2 ✅) — **nur die macOS-Desktop-App findet die Sonos-Umgebung nicht.** Ursache ist bekannt und **keine Firewall-Frage**: die Desktop-App entdeckt per **SSDP (UDP 1900)**, und RouterOS reflektiert **nur mDNS, kein SSDP** → die Discovery quert die VLAN-Grenze MAIN(10)→MEDIA(40) nicht. Die `forward accept vlan10↔vlan40`-Regel ist bereits offen und hilft hier nicht, weil das Problem *vor* dem Verbindungsaufbau liegt. **Fix = SSDP-Reflector (TP7.1)** auf NAS/pve (nicht am Router — kein Flash) mit Bein in vlan10 + vlan40, plus `input accept udp/1900` auf den bedienten VLANs. **TP7.1 wird damit aus „vorgemerkt" in dieses Arbeitspaket gezogen** und ist mit der Deny-by-default-Arbeit gemeinsam zu testen (sonst prüft man zweimal dieselbe Strecke).
- **IoT→INFRA-Rückmeldung (deine Frage):** IoT darf **aktiv** zu INFRA-Diensten initiieren — Firewall-Allow IoT→INFRA NUR auf **MQTT (1883/8883), HA-API, InfluxDB (8086)** o.ä.; Antworten via established/related; IoT→MAIN bleibt deny. So pusht ein Sensor Daten an HA/MQTT/Influx, ohne dass IoT „frei" ist.
- Abh.: TP4 ✅ (L3-Basis, Rolle inzwischen auf `crs326-core`), **TP14.5 ✅** (Freigabe-Gate), **TP22 ✅** (GC-Deploy-Pipeline = der vorgeschriebene Weg für den Push). **Keine offenen Blocker mehr — TP5 ist der kritische Pfad.** Folge: **TP6 (CAKE)**. Hängt dran: **TP5.1** (DNS-Redirect/Torch für Low-Trust).
### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt
- **Kontext (2026-07-08/09):** Airfryer „airschreier" (Philips/Versuni, ESP, `10.40.30.180`) bekam per DHCP-Option 6 `dns 1.1.1.1` als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — **kein** Content-Block, via Gateway-Logs bewiesen, s. `memory/ref_esp_ota_doh_not_block`). Nebenwirkung: public **1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH** (`px35ynlol3.cloudflare-gateway.com`) → **DNS-Sicht auf das Gerät weg**, Gerät umgeht zudem die Filterung.
- **(a) DNS-Redirect statt Geräte-Override** (der dauerhafte Fix): dst-nat „UDP/53 (+TCP/53) von Low-Trust-Src → auf einen *plain* Resolver umbiegen" gibt **Logging zurück UND** funktionierendes OTA (kein langsamer DoH-Pfad fürs Gerät). Redirect-Ziel-Optionen: (i) ap1-Resolver mit *nicht*-DoH-Upstream für dieses Src-Set, (ii) direkt public 1.1.1.1 (dann nur Flow-Logging), (iii) eigener plain-Resolver. Sauberer als „Gerät verschwindet auf 1.1.1.1". Verortung: Teil des UNTRUSTED-Internet-only-Pinnings (TP5-„Offen"); Entscheidung v50-DNS-Policy (gefiltert vs. plain) hängt dran.
- **(b) Live-Torch als Ad-hoc-Observability:** DNS ist bei direktem 1.1.1.1 blind, **Flows nicht** (NATen über ap1). `torch` auf `vlan30`/`vlan50` gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS `eu-west-1` IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln.
- **Abh.:** UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker.
### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ 🟡 **GEMESSEN 2026-08-20/21 — Fall B, aber „messbar, nicht hörbar"**
> ✅ **Die Prämisse ist geprüft. Ergebnis + Rohdaten: [`tp6-praemisse-messrunbook.md`](tp6-praemisse-messrunbook.md) §5.**
>
> | | idle / C0 | Upload-Last | Download-Last |
> |---|---|---|---|
> | Durchlauf 1 (ohne Call) | 12,9 ms | **60,4 ms** | 41,3 ms |
> | Ort 1 Küche (mit Call) | 13,2 ms | **42,8 ms** | 37,1 ms |
> | Ort 2 Terrasse (mit Call) | 12,8 ms | **40,4 ms** | 36,3 ms |
> | Referenz 2026-06-16 | 12 ms | 17,5 ms | *nie gemessen* |
>
> **Linerate erstmals dokumentiert: Upload 9,6 Mbit · Download 54,6 Mbit.**
> Der Anstieg liegt in der Senderichtung bei **+28 bis +47 ms** und damit über der §3-Schwelle von
> ≲30 ms → **formal Fall B, CAKE ist begründet.** 🔴 **Aber:** durch alle sechs Lastphasen lief ein
> Telegram-Videocall, und es wurde **kein einziger Problem-Button gedrückt**. Messbar ja, wahrnehmbar
> nein. waveform gibt zeitgleich **Grade A** — kein Widerspruch, sondern ein kürzerer Test (Erklärung
> in §5.3). **Empfehlung: TP6 bleibt offen und begründet, hat aber niedrigere Priorität als TP25** —
> was die Calls tatsächlich zerreißt, sind die WLAN-Übergaben, und die repariert keine Queue.
> Wenn gebaut wird: **erst `/queue` ins GC-Template + ComplianceRule** (§4.1), dann die Queue, dann
> dieselbe Messung als A/B-Nachweis.
> 📋 **Eigene Session, Einstieg: `docs/handoff-tp6-session.md`** (Übergabe 2026-08-19),
> Arbeitsplan: `docs/tp6-praemisse-messrunbook.md`. Abbruchkriterium steht **vor** der Messung fest.
- 🔴 **Der Widerspruch, der zuerst aufzulösen ist:** dieser TP wird seit Monaten als „DAS Ziel" geführt —
aber `docs/call-quality-diagnosis.md` hat den Bufferbloat **bereits gemessen** und kommt zum
gegenteiligen Schluss. Wörtlich in dessen Ergebnistabelle:
`CAKE / QoS | kein Bufferbloat-Spielraum | ❌ nicht deployt (wäre marginal)`, und im Kopf:
TP6 „hat sich als **Nicht-QoS-Problem** entpuppt".
**Messwerte (2026-06-16):** 3 Flows = **10,3 Mbit** Upload gesättigt · idle **12 ms** ·
Vollast avg **17,5 ms** / max **26 ms** → **A/A+**. Bei solchen Zahlen holt CAKE nichts mehr.
- ⚖️ **Was das relativiert:** die Messung stammt aus der **FritzBox-Ära**. Seither ist der Edge komplett
getauscht (Arris im Bridge-Mode, NAT und Queuing liegen auf `crs326-core`). Die Leitung ist dieselbe,
das Pufferverhalten des neuen Edge ist **ungemessen**.
- ✅ **ERLEDIGT 2026-08-20/21 — gemessen statt vermutet** (die frühere Fassung dieser Zeile verlangte
genau das): der Bufferbloat-Test gegen den heutigen Edge ist gefahren, dreimal, davon zweimal mit
laufendem Videocall an verschiedenen Orten. Es ist **schlechter geworden als 2026-06-16** (60,4 statt
17,5 ms avg unter derselben Sättigung), die Ursache ist damit neu und liegt **nicht** an der Core-CPU
(~10 % unter Upload-Last). Zahlen und Einordnung: Runbook §5.
- 🔎 **Fasttrack ist auf dem Core faktisch tot — und das ist kein Entwurf, sondern ein Unfall**
(Befund 2026-08-18): die Regel `defconf: accept est,rel,untracked` steht **vor**
`defconf: fasttrack`, also wird Fasttrack nie erreicht. Live: **32.979.171 Pakete** auf der
accept-Regel, **0** auf der Fasttrack-Regel, **0 von 445** Verbindungen fasttracked.
- **Herkunft:** `ap1` hatte die Reihenfolge vor dem Cutover **richtig** (Fasttrack zuerst, wie im
MikroTik-defconf). Umgedreht wurde sie beim handgebauten Core-Aufbau (TP21.1).
- **Für TP6 ist das günstig:** Fasttrack umgeht die Queues, CAKE braucht sie — ein Konflikt, den man
sonst erst auflösen müsste, existiert hier gar nicht.
- ✅ **ENTSCHIEDEN (Christoph, 2026-08-19): der Zustand bleibt — und gilt ab jetzt als Absicht, nicht
als Unfall.** Begründung: bei ~10 Mbit WAN kostet der Verzicht nichts (die Core-CPU trägt den
Forward-Pfad ohnehin, 32,9 Mio Pakete sind der Beleg), und für CAKE ist er die bequemere Ausgangslage.
Damit ist auch die TP21-Warnung „das Re-Baseline hat den Fehler zum Soll erklärt" **aufgelöst**: die
Reihenfolge in der SoT ist jetzt gewollt. Sie war es bei der Aufnahme nur nicht.
- ⚠️ **Gegenbedingung, unter der neu bewertet wird:** wenn **Inter-VLAN** je auf Gigabit soll
(NAS ↔ MAIN läuft geroutet über dieselbe 800-MHz-CPU), ist der Verzicht teuer. **Die Messung dafür
steht aus** — und sie, nicht die WAN-Zahl, ist die Vorbedingung jeder Neubewertung.
- ⚠️ **Ungemessen ist der Preis:** ohne Fasttrack läuft **jedes** geroutete Paket durch die volle
Kette samt Conntrack, auf der Core-CPU. Für 10 Mbit WAN irrelevant, für **Inter-VLAN**-Transfers
(NAS ↔ MAIN, Synology Drive) möglicherweise nicht. Das ist messbar und sollte gemessen werden,
bevor jemand die Reihenfolge „repariert".
- 🔴 **Und es steht seit 2026-08-18 als ABSICHT in der SoT:** das Re-Baseline
(`ingest-core-firewall.py --apply`) hat die Reihenfolge unverändert nach Nautobot geschrieben,
Rule `ip-firewall-filter` mit `config_ordered=true`. ~~**Entscheidung nötig:** Reihenfolge korrigieren
(Fasttrack lebt wieder) · Fasttrack-Regel entfernen (ehrlich: wir nutzen sie nicht) · so lassen und
hier begründet stehen lassen.~~ → **entschieden am 2026-08-19, s. oben: so lassen.** Damit stimmen
SoT und Absicht wieder überein — bisher stimmten sie nur zufällig überein.
- 📋 **Runbook liegt bereit: `docs/tp6-praemisse-messrunbook.md`** (2026-08-19). Es reproduziert die
Methode von 2026-06-16 (3 Flows + 24 Pings idle/Last), misst erstmals auch die **Download-Richtung**
(die 2026-06-16 **nie** gemessen wurde — beim asymmetrischen Kabelanschluss die wahrscheinlichere
Problemseite), und trägt das Abbruchkriterium **vor** der Messung: A/A+ → TP6 wird **geschlossen statt
gebaut**. Enthält außerdem den Bau-Kontext für den Fall B, damit er nicht neu recherchiert wird —
vor allem: **`/queue` fehlt im GC-Template und hat keine ComplianceRule** (dieselbe blinde Stelle, die
TP26 für `/ip firewall filter` geschlossen hat), eine handgesetzte Queue wäre also driftend und
ungeschützt.
- 📌 **Nicht im Repo dokumentiert und deshalb Teil der Messung: die tatsächliche Liwest-Linerate.**
Die „~10 Mbit" stammen aus der Sättigungsmessung von 2026-06-16, nicht aus einem Tarifdatenblatt.
Ohne diese Zahl ist die übliche CAKE-Vorgabe „85–95 % Linerate" geraten.
- **Abh.:** TP5 ✅ (keine Blocker mehr). Vorgelagert: die Messung oben.
### TP7 — Media-Discovery (mDNS) für VLAN 40 ▸ ✅ FERTIG (2026-07-02) — `docs/tp7-mdns.md`
- ✅ **Built-in mDNS-Repeater** aktiv — **Live-Stand `crs326-core` (verifiziert 2026-08-14):**
`mdns-repeat-ifaces=vlan10,vlan20,vlan30,vlan40,vlan90` → MAIN/INFRA/IOT/GUEST ↔ MEDIA. Reversibel (Feld leeren).
📌 **`bridge` (=VLAN1) ist nicht mehr dabei** — mit dem VLAN1-Retirement erwartbar und korrekt, war aber nie nachgezogen.
*Historische Fassung (ap1, bis 2026-08-08): `…,vlan90,bridge` — `bridge`=VLAN-1 seit TP7.2 2026-07-03.*
Realbestand: MAIN=iOS-Client + MacBook, INFRA=Home Assistant, MEDIA=10× Sonos (siehe `docs/sonos-inventar.md`).
- ✅ **Funktional durchgetestet (2026-07-02):** iOS-Sonos-App (vlan10) **und** Home Assistant (vlan20) finden die
9 online-Sonos per `_sonos._tcp.local.` (Bonjour) automatisch. Household nicht mehr gesplittet.
- ✅ **TP5-Anschluss live:** `forward accept vlan10↔vlan40` + `forward accept vlan20↔vlan40` + `input accept udp/5353`
auf vlan10/20/30/40 sind gebaut (mit „TP5:"-Kommentar), damit Discovery + Steuerung + Streaming ohne Umwege gehen.
- ⚠️ **SSDP/Cast (UDP 1900) bewusst NICHT gebaut** (bräuchte Reflector-Container auf NAS/pve; hAP hat kein USB/Flash).
Konsequenz: **macOS-Desktop-Sonos-App nutzt SSDP → quert die VLAN-Grenze NICHT** und findet die Sonos nicht.
iOS-App (mDNS) und HA-Integration decken den Nutzungsbedarf ab; Spotify Connect läuft über Cloud.
- Abh.: TP4 ✅, TP5 (Sonos-Holes) ✅ live.
### TP7.1 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) ▸ ✅ **LÄUFT (2026-08-18)** — User-bestätigt: „sonos ist da!"
- ✅ **Umsetzung:** eigener LXC **108 `mcast-relay`** auf pve (Ubuntu 24.04, 1 Kern, 256 MB, `onboot`),
zweibeinig: `eth0` = **10.40.10.21** (VLAN10), `eth1` = **10.40.40.21** (VLAN40), Default-Route nur
über VLAN10, **kein IP-Forwarding**. Dazu `core ether13` (pve-Port) um `tagged vlan10, vlan40`
erweitert (`routeros/tp7.1-core-ether13-trunk.rsc`). Software: `alsmith/multicast-relay` als
systemd-Unit `multicast-relay.service`.
- **`--noMDNS` ist Pflicht:** mDNS reflektiert bereits der Core (TP7). Zweimal zu reflektieren
erzeugt Duplikate. **`--ssdpUnicastAddr 10.40.10.21`** ist ebenfalls noetig — die Sonos
antworten auf einen M-SEARCH per **Unicast**, und diese Antworten muessen zurueckgereicht werden.
- 🔑 **Warum zwei Beine zwingend sind** (Christophs Frage „vlan40 sollte keinen relay benoetigen?!?"):
SSDP ist **link-lokales Multicast mit TTL 1**. Der M-SEARCH des MacBooks verlaesst VLAN10 nie, die
`ssdp:alive`-NOTIFYs der Sonos verlassen VLAN40 nie. Ein Relay kann nur kopieren, was es selbst
hoert — es **muss** Mitglied beider Broadcast-Domaenen sein. Routing hilft wegen TTL 1 nicht, und
`forward accept vlan10<->vlan40` ist laengst offen: es scheitert **vor** dem Verbindungsaufbau, an
der Entdeckung. Dass AirPlay funktionierte, passt genau ins Bild — das ist mDNS.
- 🎯 **Beweis im Log:** `[Sonos Discovery] Relayed 268 bytes from 10.40.10.183:49358 on eth0 [ttl 64]
to 255.255.255.255:1900 via eth1/10.40.40.21` — echte Suchrufe beider Macs (`.10.183`, `.10.82`).
- ⚠️ **Falle beim Selbsttest:** ein M-SEARCH **vom Relay-Host selbst** wird nicht weitergereicht
(Loop-Schutz: das Werkzeug ignoriert eigene Quelladressen). Mein synthetischer Test meldete
deshalb 0 Antworten, waehrend die App bereits funktionierte. **Aus dem Relay heraus laesst sich
der Relay nicht pruefen** — entweder am echten Client testen oder am Log mitlesen.
- **Keine Firewall-Arbeit noetig:** der Relay spricht in beiden VLANs jeweils lokal; das trifft die
`forward`-Kette nicht.
- 📌 **Status-Update 2026-08-14:** Symptom vom User erneut bestätigt — **iOS-App ✅, HA-Sonos-Panel ✅ (steuern/gruppieren), macOS-Desktop-App findet die Umgebung nicht.** Damit ist das Bild vollständig: alles, was **mDNS** spricht, funktioniert; nur der **SSDP**-Weg fehlt. Der Fix wird **Bestandteil des TP5-Pakets** (gemeinsamer Test mit Deny-by-default statt zweimal dieselbe Strecke prüfen). ⚠️ **Nicht als Firewall-Problem fehldiagnostizieren:** `forward accept vlan10↔vlan40` ist längst offen — es scheitert an der Discovery, nicht am Datenpfad.
- **Bedarf:** macOS-Desktop-Sonos-App auf VLAN 10 findet die Sonos in VLAN 40 nicht (Discovery per SSDP UDP 1900,
RouterOS reflektiert kein SSDP). iOS-App + HA decken den Alltag ab, aber User will die Desktop-App nutzen.
- **Lösung:** kleiner SSDP-Reflector-Container mit Bein je Ziel-VLAN. Auf **NAS oder pve** (nicht am hAP, 128 MB
Flash + kein USB). Kandidaten: `bkupreck/ssdp-repeater`, `scyto/multicast-relay`, oder eigenes Python-Skript.
- **Scope:** vlan10 ↔ vlan40 (MacBook → Sonos). Optional vlan20 ↔ vlan40 falls HA je SSDP-Discovery brauchen sollte
(aktuell nicht — HA nutzt zeroconf/mDNS).
- **Anschluss TP5:** `input accept udp/1900` auf den bedienten VLANs (falls Router-lokal reflektiert wird) bzw.
Container-Port-Freigaben je Bein.
- **Alternative (verworfen):** macOS-Client temporär in VLAN 40 hängen (unpraktikabel für Alltagsnutzung).
- Abh.: TP7 ✅. Kein Blocker für TP5-Deny-by-default.
### TP7.2 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery ▸ ✅ FERTIG (2026-07-03) — `docs/sonos-inventar.md`
- **Root Cause (widerlegt frühere Doku):** HA-`media_player.*`-Sonos alle `unavailable`. HA sitzt **nicht** auf
VLAN 20, sondern noch auf `10.40.41.105` (VLAN-1-Transfernetz, IP-Migration steht aus). Der mDNS-Repeater
deckte VLAN 1 **nicht** ab, und die HA-Sonos-Integration ist **SSDP-abhängig** (`manifest dependencies:[ssdp]`,
soco 0.31.1) → quert die VLAN-Grenze nicht. Die alten „HA nutzt mDNS/zeroconf, fixed 2026-07-02"-Notizen waren falsch.
- ✅ **HA-Fix (discovery-unabhängig):** `sonos: media_player: hosts:` mit allen 11 Sonos-IPs + `advertise_addr: 10.40.41.105`
in `configuration.yaml` (Backup `.bak-sonos-20260703`). `advertise_addr` fixt die UPnP-Event-Callbacks trotz
HA-Multi-Homing (`.105`/`172.16.0.18`/docker). Core-Restart → alle 6 Online-Zonen `idle/playing` (Wohnzimmer-Push
verifiziert = Callbacks OK). Terrasse `.198` offline = kein Entity (kommt beim Einschalten). Kurzes Flapping direkt
nach Restart = Settling, danach stabil.
- ✅ **Nebeneffekt:** mit `bridge` im Repeater (s.u.) meldet HA jetzt „Automatic discovery is working, hosts not needed"
— die statischen Hosts sind also Redundanz (Gürtel+Hosenträger), bleiben aber als Absicherung.
- ✅ **VLAN 1 (temp) + GUEST (perm) Discovery/Reach (User-Wunsch 2026-07-03):**
- mDNS-Repeater erweitert → `mdns-repeat-ifaces=vlan10,vlan20,vlan40,vlan30,vlan90,bridge` (VLAN-1-untagged=`bridge`).
- Firewall: `input accept udp/5353 vlan90` + `forward accept vlan90↔vlan40` (ganzes MEDIA, beide Richtungen; User-Wahl).
VLAN 1/`bridge` brauchte nichts (schon via `TP10-seal-lan` + permissiver Forward).
- **Bewusst in Kauf:** flacher Repeater → Guests sehen auch MAIN/INFRA/IOT-mDNS-Announcements (Discovery-Leak;
echte Verbindungen regelt TP5-Deny-by-default).
- **Offen/später:** HA→VLAN 20 (IP-Migration nach Omada-PVID-Fix) → dann nur `advertise_addr` auf neue IP; macOS-Desktop-App
weiter via SSDP-Reflector TP7.1. Abh.: TP7 ✅, TP5-Holes ✅.
### TP8 — Identity & Access-Control ▸ research-gated
- **RADIUS = FreeRADIUS auf der NAS** (✏️ User-Manager v5 kann **KEIN LDAP** → openldap nur via FreeRADIUS anbindbar).
- **EAP-TLS (Zert) je Gerät + dynamische VLAN-Zuweisung** (RADIUS-Attribut `Tunnel-Private-Group-ID`). **Einfachste Heim-Alternative:** pro VLAN eine SSID mit **WPA3-PSK** (kein RADIUS) — Abwägung Komfort vs. Admin-Aufwand.
- Abh.: TP3. (Research erledigt, s. TP-R.)
### TP9 — Gateway-/Router-HA ▸ ❌ OBSOLET (User-Entscheid 2026-08-08)
- ❌ **Verworfen (User-Begründung 2026-08-08, „ganz klar nein"):** am `crs326-core` führt ohnehin **nie ein Weg vorbei** — er ist der unumgängliche L3-Durchleitungspunkt. Ein zweites Gerät / VRRP **beseitigt diesen SPOF nicht**, sondern baut nur **zusätzliche Sollbruchstellen** ein (2× Config-Sync für DHCP/DNS/Firewall/NAT, Split-Brain-Risiko, VRRP-Eigenfehler) → netto ein Zuverlässigkeits-**Verlust**, kein -Gewinn. Die Frage ist nicht „Aufwand vs. Nutzen", sondern „mehr Fehlerquellen einbauen?" → nein. DR-Weg = Config-Backup-Restore (+ ggf. AP-Reprovisionierung). VRRP-Skizze unten = nur noch historische Recherche.
- **„Was, wenn ap1 ausfällt?"** → **VRRP** löst Gateway-Failover je VLAN ~gratis (kreuzweise Master/Backup).
⚠️ **Haken (nicht Last):** DHCP/Firewall/NAT/DNS müssen auf **BEIDEN** APs **identisch** gepflegt sein, sonst „Gateway up, Internet tot".
**Empfehlung:** VRRP nur für **MAIN/INFRA**, sonst ap2 als **Kalt-Ersatz** + Backup. DHCP/DNS schon redundant (Split + 2 Resolver).
- Abh.: TP4. (Research erledigt, s. TP-R + Config-Skizze im Report.)
### TP10 — Multi-Site VPN ▸ ✅ KERN DURCH (2026-06-27) — beide WG live auf ap1-Hub (`docs/tp10-vpn-migration.md`)
> **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).** Research: `docs/research/vpn-comparison.md`.
- **✅ KERN DURCH (2026-06-27) + EDGE-CUTOVER (2026-07-01):** beide AVM-Tunnel → **2× WireGuard mit Hub auf ap1** migriert. Live: `peer fm`
(`10.101.0.0/24`, bidirektional, myfritz-Endpoint) **und** `peer dw` (`10.95.10.0/24`, CGNAT-Dial-in) mit frischem
Handshake + Traffic. **Public-Edge direkt** — ap1 hört UDP/51821 auf `vlan100` (Edge-Cutover 2026-07-01), keine
FB-Portfreigabe mehr, kein Double-NAT. FM-IPsec gekillt, Gelis alter FB↔FB-WG sauber geswappt.
- **✅ DW/Geli on-site verifiziert (2026-06-27):** Geli auf `10.95.10.0/24` renumbert, FB-WG single `10.95.10.1/24`,
Keepalive 25, Endpoint `bf4…:51821`. End-to-End von macmini/Linz → Gelis 24/7-APs `10.95.10.246/.247` sauber
(echte LAN-IPs, **kein `10.255.255.x`**-NAT-Leak).
- **💡 Root Cause (FM-PoC bewiesen):** FB masqueradet WG, wenn die Interface-`Address` eine **Transfernetz-IP** ist →
muss die **FB-LAN-IP** sein (AVM Proxy-ARP). War der Schlüssel zum sauberen LAN-LAN.
- **Hub:** `wg-hub` (UDP 51821, Key `P/bzt…`, MTU 1412, keine IF-IP). Endpoint = DDNS **`bf4.emsenhuber.net:51821`**
(Cloudflare, DNS-only → Public-IP). Apply gebündelt in `routeros/tp10-cutover-apply.rsc`.
- ✅ **Permanenter Edge-Cutover durch (2026-07-01)** — Details in TP4 (WAN via sw1 vlan100, ap1 = Public-Edge,
WAN-Input-Seal live). BF-FB-eigenen WG-Server 51820 entfällt (FB physisch weg).
- **Offen:** DDNS-Auto-Updater für `bf4.emsenhuber.net` auf NAS/pve, damit der A-Record der Public-IP folgt
(falls Liwest sie tauscht). Aktuell manuell in Cloudflare gepflegt.
### TP11 — Monitoring & Mgmt-Control-Plane ▸ research-gated
- MikroMCP + omada-mcp + **SNMP/icinga-mikrotik**, RoMON, NTP, **E-Mail-Alerts**, Netwatch. → Teil von `docs/research/routeros-capabilities.md`.
- **CF-Tunnel #1033 Track A ✅ live (2026-06-30):** Round-Robin-1033er über 3 asymmetrische Connectors behoben
durch Reduktion auf pve-only Connector (NAS + macmini scheiterten an pve-lokalen/stalen Origins). SSoT:
`docs/cf-tunnel-mapping.md`. **Track B/C läuft über TP14** (Ingress bereits v116 .41→.20 für NAS;
pve-Connector→VLAN20 via TP14 Session B = #1033-Zielbild).
### TP12 — IPv6 (intern + extern, 3 Standorte) ▸ research-gated
- Konzept für FM/BF/DW. → TP-R.
#### TP12.1 — IPv6 im LAN (ULA) + v6-Firewall ▸ 🟠 **KONKRET GEWORDEN (2026-08-22), Treiber: Matter/Thread**
> **Anlass:** der runde IKEA-Fühler **TIMMERFLOTTE** (Matter over Thread) ließ sich nicht anlernen —
> „pairing failed" in der Companion-App. Ursache ist **nicht** die Firewall, sondern dass zwischen
> VLAN10 und VLAN20 **überhaupt kein IPv6 geroutet wird**. Damit ist TP12 vom Konzept-TP zu einem
> Punkt mit einem konkreten, wiederkehrenden Schaden geworden.
- **Befund am `crs326-core` (live gelesen 2026-08-22):**
- IPv6-Adressen: **ausschließlich Link-Local** (`fe80::…`) auf jedem Interface. Keine ULA, keine GUA.
- IPv6-Routing-Tabelle: nur `::1/128` und `fe80::/64` je Interface. **Keine Route** auf den
Thread-OMR-Prefix `fd92:505a:c64e:1::/64` und keine auf `fd03:cace:953f:e13d::/64` (den On-Link-Prefix,
den der OTBR auf VLAN20 wirbt).
- `/ipv6 nd`: Default-Eintrag, `advertise-dns=no` — es gibt aber ohnehin keine Prefixe zu werben.
- 🔴🔴 **Der eigentliche Kostenpunkt ist nicht IPv6, sondern die Firewall:** `/ipv6 firewall filter` ist
**komplett leer**. Heute harmlos, weil ohne Adressen nichts geroutet wird. In der Sekunde, in der
Adressen vergeben werden, steht **IOT→MAIN offen, GUEST→INFRA offen — an TP5 vorbei**. Das gesamte
TP5-Regelwerk muss **vorher** in v6 nachgebaut sein, sonst reißt dieser TP genau die Segmentierung
auf, die TP5 am 2026-08-18 geschlossen hat. **Das ist die Arbeit, nicht die Adressvergabe.**
- 🔎 **Warum Matter das braucht:** Commissioning läuft in zwei Etappen. Etappe 1 (BLE zum Gerät) ist
netzunabhängig. In Etappe 2 muss der Commissioner — das iPhone — das Gerät auf dem **operativen
Thread-Netz** wiederfinden (DNS-SD `_matter._tcp`, dann CASE). Dafür braucht das Telefon eine
IPv6-Route in den OMR-Prefix hinein. iPhone hängt auf VLAN10, OTBR auf VLAN20 → Etappe 2 bricht ab.
Die ~30 bereits laufenden Matter-Knoten widersprechen dem nicht: HA und OTBR sitzen auf **demselben
Host** (`wpan0` ist lokal), im Betrieb quert nichts die VLAN-Grenze. Nur das *Anlernen* tut es.
- ⚠️ **Zwei Fallen, die den naiven Weg verbauen:**
- **(a) Der OMR-Prefix ist generiert und wandert.** Am 2026-08-22 um 17:33 (OTBR-Neustart) sind alle
Knoten von `fd92:505a:c64e:1::/64` auf **Mesh-Local** `fd66:986:170:bb1a::/64` gefallen — und
Mesh-Local ist per Definition *nicht* off-mesh routbar. Eine statische Route auf den OMR-Prefix ist
also nur so stabil wie der nächste Neustart. Ihn im Thread-Dataset festzunageln formiert das Netz
neu — mit 30+ produktiven Knoten daran kein Nebenbei-Change.
- **(b) RouterOS lernt fremde RAs nicht.** Der OTBR wirbt den OMR-Prefix per RA mit Route-Information-Option
auf VLAN20. Der Core müsste die annehmen — als forwardender Router für dieses Interface tut er das
nicht sauber.
- 📡 **Dritter Baustein: DNS-SD über die VLAN-Grenze.** `mdns-repeat-ifaces` am Core (vlan10/20/30/40/90)
ist **IPv4**. Für Matter-Discovery bräuchte es zusätzlich einen v6-fähigen Reflector — der
`mcast-relay`-LXC (108) kann das prinzipiell, aber als **zweite Instanz** mit Beinen in VLAN10+VLAN20
(SSDP-Instanz ist VLAN10+VLAN40, s. Gotcha in CLAUDE.md).
- ✅ **Der Workaround, der das umgeht statt löst:** iPhone zum Anlernen auf **VLAN20** — dann sind Telefon
und Border Router auf demselben L2-Link, der OTBR wirbt dort On-Link-Prefix *und* Route, und iOS wertet
beides aus. `cfg-infra` / SSID `dirtyvader-infra` (`dp-infra`, VLAN20, `sec-infra` mit PSK) existiert am
Manager **bereits**, ist nur in **keiner** `provisioning`-Zeile als Slave hinterlegt. Runbook:
[`docs/matter-pairing-infra-ssid-runbook.md`](matter-pairing-infra-ssid-runbook.md).
- ✅ **Der Weg, der das Problem dauerhaft erledigt:** Matter-Server-Option **`ble_proxy: true`** (ab HA
2026.06, hier 2026.8.3). Dann kommissioniert **HA selbst** über seinen Bluetooth-Stack; das Telefon ist
raus und die VLAN-Grenze wird nie berührt. Braucht eine **connectable** BLE-Quelle — Shelly-Proxies sind
reine Advertisement-Scanner (kein GATT), ein **ESP32 als ESPHome-BT-Proxy** (`active: true`) reicht.
Der **ESPHome Device Builder ist bereits installiert**. Damit wird TP12.1 für Matter **optional**.
- **Abh.:** hängt an nichts. **Blockiert nichts** — außer künftige Thread-Geräte, solange weder
`ble_proxy` noch die `-infra`-SSID steht. Reihenfolge innerhalb des TP ist zwingend:
**v6-Firewall nachbauen → dann erst Adressen vergeben.** Gehört unter TP12 (Gesamtkonzept 3 Standorte),
ist aber allein für BF ausführbar.
### TP13 — „Botschafts-WLANs" ▸ 🔜 **AUSFÜHRBAR (Runbook 2026-08-19)** — `docs/tp13-botschafts-wlan-runbook.md` · fun/low-prio
> 📌 Am 2026-08-19 überarbeitet: der Technikteil beschrieb noch Standalone-APs (SSIDs entstehen seit
> TP28 am **Manager**, also ein Deploy statt zwei), und der Entwurf „nur `ssid=`, kein Security" war ein
> **Denkfehler** — eine SSID ohne Security *ist* ein offenes WLAN, also genau der Fall, den der
> Rechtsvorbehalt ausschließen soll. Richtig: PSK, den niemand kennt. Dazu die Datapath-Falle
> (Slave-VAP ohne `vlan-id` landet ungetaggt in der Bridge — dieselbe Mechanik wie der 14-h-Ausfall).
- **Idee:** eine/mehrere **Broadcast-only-SSIDs als „schwarzes Brett"** an die Funk-Nachbarschaft — der SSID-Name *ist* die Botschaft (z.B. `Kanal-11-bitte-freihalten`, `WLAN-Tuning-siehe-Portal`). Optional ein **offenes WLAN mit Captive-Portal** auf eine **statische Info-HTML** (Kanalabsprache, Auto-Kanalwechsel-Bitte). Leitgedanke: wer die SSID sieht, kann auch stören → die Infoseite erreicht genau die Zielgruppe.
- ~~**Technik (jetzt einfach, da WLAN standalone seit TP3-Rebuild):** reine Namens-SSIDs = zusätzliche `/interface/wifi` Slave-VAPs auf wifi1/wifi2 mit eigener `configuration` (**nur `ssid=`, kein Security/Datapath**)~~ ⚠️ **Überholt 2026-08-19, in zwei Punkten:**
- **(a) WLAN ist seit TP28 nicht mehr standalone.** SSIDs entstehen am **Manager `crs326-core`** (9 `configuration`-, 6 `datapath`-Objekte, 4 `provisioning`-Zeilen — live gelesen 08-19). Das ist für TP13 sogar einfacher: **ein** Deploy statt zwei.
- **(b) 🔴 „kein Security" *ist* ein offenes WLAN** — genau das, was der Rechtsvorbehalt unten ausschließen will. Richtig ist eine SSID **mit** WPA2/WPA3-PSK, den niemand kennt: sie beacont den Namen, ist aber nicht beitretbar.
- Captive-Portal-Variante (RouterOS-**Hotspot**, isoliertes VLAN, `walled-garden`) bleibt als Option bestehen — s. Rechts-Gate unten.
- ~~**Umsetzungs-Skizze:** … Beacon-SSIDs in `routeros/.secrets/ap{1,2}-prov-base.rsc` ergänzen …~~ → diese Dateien sind für den WLAN-Teil **Historie** (die CAPs sind auf die `cap`-Zeile eingedampft). **Aktuelle Umsetzung: `docs/tp13-botschafts-wlan-runbook.md`** (2026-08-19).
- 🔴🔴 **Die Falle, die dort ausführlich steht:** ein Slave-VAP **ohne explizite `vlan-id` am Manager** landet ungetaggt in `bridge` — weil der CAP vom Manager-Datapath **nur die `vlan-id`** übernimmt. Das ist dasselbe Leck wie TP3.1 (`cfg-main-2g` ohne datapath) und dieselbe Mechanik wie der 14-h-Ausfall vom 08-18. Die Botschafts-SSID braucht deshalb einen Datapath mit **totem** `vlan-id` — PSK **und** toter Datapath, nicht eines von beidem.
- ✅ **VAP-Budget geprüft (live 08-19), unkritisch:** `max-interfaces=16` je Radio, belegt sind 4 (5 GHz) bzw. 5 (2,4 GHz). **Die Grenze ist Airtime, nicht die Zahl** — jeder VAP beacont eigenständig. Empfehlung: **genau eine SSID, auf 2,4 GHz** (mehr Reichweite = mehr Nachbarn, und 5 GHz bleibt für Nutzverkehr frei).
- ⚠️ **Recht vor Scharfschalten prüfen (User privat, AT):** offenes WLAN + Portal berührt Impressumspflicht/Störerhaftung — **reine Namens-SSIDs ohne offenen Zugang sind unkritisch** (kein Fremd-Datenverkehr); im Zweifel nur die Beacon-Variante.
- **Abh.:** ~~TP3-Rebuild ✅ (Standalone-APs — SSID-Änderung = 1 Deploy/GC-Job)~~ → **TP28 ✅** (CAPsMAN auf dem Core) + **TP22 ✅** (GC-Deploy-Pipeline). SSID-Änderung = **ein** Deploy auf den Core; Direkt-Edit am Gerät ist durch den Governance-Entscheid ausgeschlossen.
### TP14 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) ▸ ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/`
- ✅ **ABGESCHLOSSEN — Migration komplett** (Live-Check 2026-08-08 vom core: NAS `.20.10`, pve `.20.100`, Docker-LXC `.20.126`, platform-pg `.20.20`, Nautobot `.20.30` alle 0% Loss). Alle Hosts + Container auf VLAN20. **TP14.4** (CF-Access-Token-Rotation) = kleiner, nicht-blockierender Standalone-Follow-up (CF/TP11-Umfeld); **TP14.5** war nie ein eigener Change (nur ein Reihenfolge-Gate) → erfüllt, TP5 damit entblockt.
- ✅ **NAS (DS923, ep14) migriert (2026-07-09/10):** Docker von `default_external` (macvlan .41) auf **ein** `vlan20`-Macvlan (10.40.20.0/24, gw .1, parent `ovs_eth0`); DSM-Cutover `.20.10`; Portainer-Endpoint `.20.10:9001`. **Root-Cause der Dauerfehler** = Portainer **Config-Only-Macvlan-Falle** (`Driver:null`, kein Container-Attach) + Macvlan-Parent-Exklusivität (`default_external` muss weg für `vlan20`) + `default_internal` (172.18) weg. Container laufen auf `.20.x`.
- ✅ **CF-Ingress v116 (2026-07-10):** 6 NAS-Origins .41→.20 (SSoT `docs/cf-tunnel-mapping.md` §8); `vw` 200 via Tunnel = pve→VLAN20-Routing steht (502 direkt nach Ingress-PUT = transient).
- ⚠️ **Abh./Reihenfolge:** verzahnt mit Omada-PVID-Fix/IP-Migration (Reihenfolge #3); Sessions = Sub-TPs **TP14.1–14.5**, sequenziell A→B→C→(D jederzeit)→E, **keine parallelen Branches** (geteilte Live-Infra). Anschlussstatements: `docs/nas-vlan20/handoff/README.md`.
- ✅ **TP14.1 (Session A) durch 2026-07-10:** alle ep14-Container auf vlan20 + pve-mcp neu; nur Omada-Inform-Adresse offen (Controller-UI-Task, s.u.).
- ✅ **TP14.2 (Session B) durch 2026-07-10:** pve-LXC (400/`docker`) auf VLAN20 (.20.126, ipvlan `parent=veth20`); omada-mcp/adguard→.20.x; CF-Connector jetzt VLAN20 = **#1033 gelöst**. Nächste Session = **TP14.3 (Session C, dormante Stacks + Referenz-Sweep ap1-DNS)**.
- ✅ **Netz-Abgleich 2026-07-11 (live via MikroMCP/pve-mcp):** **NAS `.20.10`, Mac mini `.20.167`, pve-LXC `.20.126`** alle reachable auf VLAN20 (+ Container `.20.x` populiert; `iobroker .107`/`ccu .232`/`z2m .106`/`vw .127` jetzt auch VLAN20). Sonst in VLAN1 (bridge) nur noch: sw1 `.11`/sw2 `.12`/ap2-Mgmt `.2` (Switch-/AP-Mgmt, interim bis MGMT-VLAN99) + Streu-Clients übers cudy-Alt-WLAN (MacBook `.182`, iPhone `.186`, SwitchBot `.184`, Sonoff) + die 16 VLAN1-Shellys (TP17). `defconf`/pool-v1-DHCP auf `bridge` läuft noch (VLAN1 aktiv).
- ✅ **pve-Host-Mgmt → VLAN20 (2026-07-13):** letzter verbliebener Host-Umzug. `vmbr0` bleibt VLAN1 `.41.100` (ohne Default-Route, interim), neues `vmbr0.20` (bereits vorbereitet, aber mit kaputter `address 10.40.20.0/24` vorgefunden) korrigiert auf `10.40.20.100/24` → `ifreload -a` live (ifupdown2, kein Reboot, LXC 400 durchgehend unbeeinflusst) → Default-GW auf `10.40.20.1` geflippt. SSH-Key-Auth bestätigt auf `.20.100`. Nachgezogen: ap1-DNS (`pve.bf.lan`→`.20.100`; dabei Fund+Fix eines Alt-Bugs: `pve-mcp.bf.lan` zeigte noch auf `.41.173` statt `.20.173`), CF-Tunnel `bf-pve` Ingress (`pve`+`j44d6tua`-SSH-Hostname .41.100→.20.100, PUT via `CF_API_TOKEN`, Smoke 200/200), CLAUDE.md, `docs/ip-plan.md`. `~/.claude.json` war schon sauber.
### TP14.1 — Session A: NAS fertigstellen ▸ ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT
- ✅ **Alle ep14-Container auf vlan20 redeployed + verifiziert running/.20.x** (via Portainer-Stack-Redeploy, Env preserved, Volumes intakt): oc-stack(112 → prometheus `.151`/node-exporter `.154`/grafana `.109`), default(72 → hms-mqtt `.124`), nginx(101 → dashboard-web `.148`), oc-central(160 → qdrant `.159`), oc-charlie(158 → `.162`+cf), oc-johnny2(150 → `.161`+cf), gitea(111 → `.157`, DB `.20.23:3306` erreichbar ✓), drawio(107 → `.139`, intern; CF-CNAME war eh gelöscht).
- ✅ **pve-mcp NEU** als Stack (id 202, vlan20-only `.20.173`), verbunden mit Proxmox `.41.100` (v9.0.11) — ⚠️ **StreamableHTTP am Root-Pfad `/`, NICHT `/sse`** (mcp-pve v0.4.8, `MCP_TRANSPORT=http`). `~/.claude.json` `mcpServers.pve-mcp` NEU angelegt `{type:http, url:http://10.40.20.173:3000/}` (war vorher gar nicht drin) → wirkt nach Claude-Code-Restart.
- ✅ **Tunnel-Smoke** (CF-Ingress v116 zeigte schon auf `.20`): gitea 200, grafana 302, vw 200 — Origins existieren jetzt real.
- ⚠️ **alertmanager `.153`**: korrekt an vlan20, aber **crash-loop wegen PRE-EXISTING Config-Bug** (`${TELEGRAM_CHAT_ID}`-Literal in `/etc/alertmanager/alertmanager.yml` → int64-Parse-Fehler; Alertmanager macht kein Env-Substitution). NICHT migrationsbedingt, braucht echte chat_id. Out-of-scope.
- ⚠️ **openclaw-DB `10.40.41.122:3306` down** (shared MariaDB, pre-existing; Bots kamen trotzdem hoch). `DB_HOST`-Env unverändert gelassen. `OLLAMA_BASE_URL=.41.167`=Mac-mini bleibt korrekt `.41` (migriert nicht → **kein Blanket-Env-.41→.20!**).
- ⚪ **OBSOLET (2026-07-16) — ~~omada-controller Inform-Adresse + sw2 re-adopt~~:** ursprünglich vertagt (2026-07-10, Controller-Login schlug fehl). Gegenstandslos seit TP2.1: der Controller-Login-Ausfall eskalierte zum Werks-Reset von sw2, User hat daraufhin entschieden, sw2 **dauerhaft standalone ohne Controller** zu betreiben — und den Omada-Controller-Container samt omada-mcp am 2026-07-16 **komplett gelöscht** (overkill für 1 Switch auf der Abschussliste, s. TP15/CRS326). Kein Re-Adopt mehr vorgesehen.
- 🐞 **Gotcha gefunden:** Portainer-Stack 111 (gitea) hatte den `vlan20`-Networks-Block **auskommentiert** (Vorsession-Artefakt) → Container landete auf `gitea_default`/172.18. Fix = Redeploy mit Disk-File `docs/nas-vlan20/stacks/111-gitea.yml` als Override.
### TP14.2 — Session B: pve-Docker-LXC (.126) → VLAN20 ▸ ✅ DURCH (2026-07-10) (ipvlan)
- ✅ ep2-Container auf `vlan20`: **omada-mcp `.20.176`** (OMADA_BASE_URL→`.20.175`), **adguard_bf1 `.20.16`**, **adguard_fm2 `.20.17`** — alle running/reachable (health 200, adguard 302). Transf. Files `docs/nas-vlan20/stacks/{195-adguard,200-omada-mcp}.yml`.
- ✅ LXC 400 (`docker`) net0 → `tag=20, ip=10.40.20.126/24, gw=10.40.20.1` (⚠️ **Retag brauchte Christoph** — Token `claude@pam!claude` hat kein `VM.Config.Network`; UI-Edit, dann Reboot via API mit `VM.PowerMgmt`). Portainer wandert auf `https://10.40.20.126:9443`.
- 🐞 **Gotcha: in-Container-Iface heißt jetzt `veth20`, NICHT `eth0`** (UI setzte Name analog net1=veth16) → Docker `vlan20`=ipvlan **`parent=veth20`** (nicht eth0 wie im Runbook). cloudflared(host)/portainer(bridge) unberührt.
- ✅ **CF-Connector `db713b30` jetzt in VLAN20** (Tunnel `bf.emsenhuber.net` healthy/8 conns) = **löst TP11-Track-B/C** (#1033-Zielbild). Ingress `portainer` .41.126→.20.126. Smoke: portainer/gitea/vw 200, grafana 302.
- ✅ Refs nachgezogen: `mcp/portainer/.env` (PORTAINER_URL), `~/.claude.json` (omada-mcp `.20.176`), CLAUDE.md, `docs/ip-plan.md`, `docs/cf-tunnel-mapping.md`. Next = **TP14.3 (Session C, dormante Stacks + Referenz-Sweep ap1-DNS)**.
- ✅ **Live-Smoke re-verifiziert 2026-07-10:** alle 6 VLAN20-IPs ping OK + dienstseitig grün — Portainer `.20.126:9443` HTTP 200 (LXC 400 running, net0 `veth20 tag=20 gw/ns=.20.1`), omada-mcp `.20.176`→Controller `.20.175` v6.2.10.17 (HTTPS :8043 200), pve-mcp `.20.173` liefert Proxmox-Config, adguard `.20.16`/`.20.17` HTTP :80 302→login (nicht HTTPS/:3000).
### TP14.2a — pve-mcp Single-Session-Fix + VLAN20-Host-DNS ▸ ✅ (2026-07-10)
- **pve-mcp „Failed to connect":** Ursache = **mcp-pve v0.4.8** kann im HTTP-Transport nur **eine** MCP-Session (ein globaler `StreamableHTTPServerTransport`, `sessionIdGenerator` gesetzt) → erster Client nach Boot belegt den Slot, jeder weitere Init (neue Claude-Code-Session, `claude mcp list`-Healthcheck, Reconnect) = `400 "Server already initialized"`. **omada-mcp NICHT betroffen** (frische Session-ID pro Init → Per-Session-Transport). **Fix = Upgrade auf 0.6.1** (Per-Session-Map, `serverFactory()` je Session, gleiche Env-Vars = Drop-in); Stack 202 neu deployed, Container `.20.173` = 0.6.1, `Connected to Proxmox VE 9.0.11`, `tools/list` liefert `pve_*`, **Claude Code ✔ Connected**. 3× init hintereinander = 3× 200 mit verschiedenen session-ids (verifiziert).
- **Kollateral-Fund — VLAN20-Host-DNS kaputt (beide Docker-Hosts):** nach dem VLAN20-Umzug zeigten **NAS** *und* **pve-LXC** `/etc/resolv.conf` noch auf totes `.41.254` → `docker pull` ghcr.io scheitert (`EAI_AGAIN` / 5s-Resolver-Timeout; Egress zu ghcr-IP `140.82.x:443` = OK 31 ms, also **nur Resolver tot**, nicht Egress). Fixes: **LXC** `.20.126` → `pct set 400 --nameserver 10.40.20.1` (Christoph). **NAS** → DSM-„Allgemein"-DNS war `.20.1`, ABER die **Netzwerkschnittstelle zog DNS per DHCP** und überschrieb `resolv.conf`; Fix = Schnittstelle „DNS manuell" `10.40.20.1` (Christoph) → NAS pullt wieder direkt. **Merkregel Synology:** GUI-DNS ≠ was `dockerd` liest (Quelle `/etc/resolv.conf`, per DHCP überschreibbar). **Workaround solange NAS-DNS tot:** `docker save`(LXC, hat DNS) → **gzip** (191-MB-tar reißt am Portainer-Agent-Socket-Proxy „context canceled"; ~70 MB gz geht) → Portainer `POST /endpoints/14/docker/images/load` → Stack-Redeploy `pullImage:false`. Gotcha: laufende Container frieren den `127.0.0.11`-Upstream zum Erstellzeitpunkt ein (Recreate nötig; für pve-mcp egal — Proxmox per IP `.41.100`). Memory `ref_pve_mcp_singlesession_and_vlan20_dns`.
### TP14.3 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep ▸ ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt
- ✅ **Dormante Stacks bereitgestellt (2026-07-11):** `transform.py` auf core(43)/openclaw(92)/openclaw2(97)/oc20260307(119)/openspeedtest(74)/speedtest_tracker(80)/technikscheiss(42) → **`.new.yml` + `.orig.yml`** in `docs/nas-vlan20/stacks/` (gitignored). **Nicht deployt** (User: „im Bedarfsfall später", Container-Zukunft ungewiss). 42/43 waren `default_internal` → jetzt vlan20-only (DHCP-.20.x). ⚠️ **Deploy-Caveats:** externe Refs bewusst NICHT umgeschrieben (`DB_HOST=.41.122` [pre-existing tote openclaw-DB, in 80+119], `OLLAMA_BASE_URL=.41.129`, `REDIS_URL=.41.111` in 119); **Kollision** `43-core` will `.20.23` = gitea-DB-IP (nicht gleichzeitig deploybar).
- ✅ **Referenz-Sweep `.41→.20` (2026-07-16, im Rahmen der TP19-Backup-Vorbereitung live gefunden + gefixt):** ap1 `/ip dns static` hatte noch `nas.bf.lan→.41.10` und `docker.bf.lan→.41.126` **stale** — korrigiert auf `.20.10`/`.20.126` (MikroMCP `manage_dns_entry`, dry-run+apply). Zusätzlich per User-Entscheid **Omada-Controller + omada-mcp komplett gelöscht** (overkill für 1 Switch auf der Abschussliste) → DNS-Einträge `omada.bf.lan`/`omada-mcp.bf.lan` entfernt statt migriert. `fritzbox.bf.lan` (totes Gerät, physisch weg seit 2026-07-01) ebenfalls entfernt. `docs/ip-plan.md` + CLAUDE.md nachgezogen.
- ✅ **ERLEDIGT/GEGENSTANDSLOS (live geprüft 2026-08-16):** ~~`~/.claude.json` (omada-mcp-Eintrag verwaist), CF-Ingress-Rest (`omada`/`omada-mcp`-Hostnames, ungeprüft)~~
- **`~/.claude.json`:** `omada-mcp` ist **nicht mehr als MCP-Server registriert** (`mcpServers` enthält ihn nicht). Er steht nur noch in `disabledMcpServers` einiger Projekt-Scopes — ein reiner Merkposten „bewusst aus", keine Verbindungsversuche, kein Handlungsbedarf.
- **CF-Ingress:** alle 9 Tunnel durchgesehen (`cfd_tunnel` + `/configurations`) — **kein einziger `omada`-Hostname mehr**. Sauber.
- ↪️ **Shelly-Geräte-Übersiedelung ausgegliedert → TP17** (die ~26 IoT-Shellys sind eine eigenständige WLAN-Migration, nicht Teil des Docker-Ref-Sweeps).
### TP14.4 — Session D: CF-Access-Profil für Claude ▸ 🔜 **AUSFÜHRBAR (Runbook 2026-08-19)** — `docs/tp14.4-cf-access-token-runbook.md` · nicht cutoff-kritisch
> 📌 Ist-Stand geprüft: in `mcp/cloudflare/.env` stehen **weder** `CF_ACCESS_CLIENT_ID` **noch** `_SECRET` —
> es ist also nichts halb erledigt. ⚠️ Das Secret existiert **nur im Moment der Rotation**.
> Abnahme braucht **beide** Fälle (mit Headern antwortet / ohne Header 403), sonst ginge ein
> bypass-Hostname wie `vw` fälschlich als Erfolg durch. Kein CF-Write erfolgt.
- CF-Token „claude code mcp" hat jetzt `Access: Service Tokens: Write`+`Read` → `claude-mcp` rotieren (`POST …/access/service_tokens/{id}/rotate`) → `CF_ACCESS_CLIENT_ID`/`_SECRET` in `mcp/cloudflare/.env` (chmod600). Memory `ref-cf-access-claude-profile`.
- 📋 **Runbook: `docs/tp14.4-cf-access-token-runbook.md`** (2026-08-19). **Ist-Stand verifiziert:**
`mcp/cloudflare/.env` enthält bisher weder `CF_ACCESS_CLIENT_ID` noch `_SECRET` — es ist nichts halb
erledigt. 🔴 **Das Secret wird nur bei der Rotation ausgegeben**, wer es nicht sofort schreibt, muss
erneut rotieren. Abnahme = ein Aufruf **mit** den Headern (antwortet) **und** einer **ohne** (403) —
der Erfolgsfall allein beweist nichts, der Hostname könnte auf `bypass` stehen.
### TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ ✅ ERFÜLLT (2026-08-08) — kein eigener Change
- Gate „TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20)" ist erfüllt → **TP5 (Deny-by-default) ist entblockt.** Reines Reihenfolge-Gate, kein eigener Change.
### TP15 — HW-Refresh: Switching + Router ▸ ✅ **ABGESCHLOSSEN (2026-08-08)** — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) **und** Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc`
> 🔌 **Scope-Änderung 2026-08-16 (User-Entscheid): die DC-USV ist aus diesem TP herausgelöst und heißt jetzt **TP24**.** Sie war der letzte offene Rest hier, hat mit Switching/Routing sachlich nichts zu tun und trägt eigene Entscheidungen (Elektrik, Batterie, Standort). TP15 ist damit inhaltlich zu.
> *Vorherige Header-Fassung (Stand 2026-08-05, überholt): „🟢 CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23); Core-Tausch/L3-Umzug **vorbereitet, Cutover-Config materialisiert (2026-08-05)**, Cutover = nächstes Wartungsfenster."*
- ✅ **BRING-UP BEIDER CRS326 + BRANCH-TAUSCH gegen sw2 — DURCH (2026-07-23, Claude via REST unter Governance-Ausnahme #2).** Ablauf/As-built:
- **Zugriffsweg (Henne-Ei gelöst):** Erst-Reihenfolge war **core zuerst** (User-Korrektur — core hängt an sw1 und ist der L2-Pfad, über den branch überhaupt erreichbar wird; branch war isoliert). Zugriff auf die Werks-`192.168.88.1` über **temporäre ap1-Zweit-IP `192.168.88.2/24` + srcnat-Masquerade `192.168.88.0/24`** (beide `TP15-temp`, nach Bring-up wieder entfernt — Rückweg nötig, weil die Werks-Box keine Route zu VLAN10 hat). Config dann komplett per **REST** (`/system/script` PUT+run), kein Winbox-Paste. **Winbox-MAC-Connect scheiterte reproduzierbar** („macconnection sync timeout" über WLAN/quer durch den Fabric — braucht selben Draht).
- **Identitäten am Gerät verifiziert (nicht nur Doku):** `core` = sw-id **4W04-URHT**, MAC `04:F4:1C:B4:17:76`, Serial `HKA0AREXJFM`, PW `X9…`; `branch` = sw-id **RGZJ-UK0T**, MAC `04:F4:1C:B4:15:58`, Serial `HKA0AJJA52F`, PW `PHR…` (ap2 hängt an branch **ether24** — bridge-host bestätigt = definitiv branch). **PWs in `~/.secrets/credentials.env`.**
- **branch = produktiv, volle SOLL-Portbelegung** (User-manuell erhoben, maßgeblich): ether1 Uplink-Trunk→sw1 #8 (native VLAN1 + tagged 10/20/30/40/50/90/99, **kein 100**), ether2+ether8 = **NAS 2×tagged-VLAN20** (zwei unabhängige Links), ether3 Drucker/VLAN10, ether4 Dock/VLAN10, ether5+ether6 Sonos/VLAN40, ether7 Mac mini/VLAN20, ether24 ap2-Trunk; ether9–23+sfp disabled. vlan-filtering=yes. Mgmt **`10.40.99.13` (VLAN99)** erreichbar über sw1 #8 (+ VLAN1-Temp `.41.213` während Bring-up). **Post-Swap-Abnahme grün:** NAS `.20.10`, Mac mini `.20.167` (VLAN20), Dock `.10.194` (VLAN10), Sonos `.40.191`/`.40.192` (VLAN40).
- **core = grundkonfiguriert, NICHT produktiv** (bleibt an sw1 #12): identity/vlan-filtering/volle VLAN-Tabelle, ether1 Uplink, ether2 Temp-Downlink (war Config-Pfad für branch), Mgmt **`10.40.99.11`** (VLAN99, erreichbar erst wenn sw1 #12 99T trägt) + VLAN1-Temp `.41.211`. Port-Layout ab ether3 tentativ (final in Core-Swap-Session).
- **Beide in MikroMCP `routers.yaml`** (crs326-core/-branch, ENV `ROUTER_CRS326_*` in `~/.claude.json` — **live erst nach MCP-Neustart**, diese Session lief alles per REST-curl). **Nautobot** modelliert + GraphQL-verifiziert (`nautobot/ingest-crs326-ports.py`): branch Active + volle Ports + primary_ip4 `.99.13`; core Planned + tentatives Layout; **sw1 verifizierte 16-Port-Belegung** (löst „UNVERIFIZIERT"); **sw2 → Decommissioning**.
- 🐞 **Gotchas festgehalten:** (1) **Kabel ether3↔ether5 waren beim Umstecken vertauscht** → ein AZ-Sonos landete als Access-VLAN10 in `10.40.10.190` **und** blutete via SonosNet nach VLAN40 (= Dual-Homing-Muster, `project_dual_homing_slowness_incident`) → Kabeltausch gefixt, beide AZ-Sonos jetzt sauber VLAN40 (`.191`/`.192` bound). (2) RouterOS-Script-`policy`-Feld per REST wählerisch → **weglassen** (admin-Default = volle Policy). (3) Disable-Regex mit `^…$`-Ankern griff nicht → `[find name!="ether1" and …]` nutzen. (4) REST-`PUT`-Antwort mit mehrzeiligem `source` ist kein sauberes JSON → ID separat via `?.proplist=.id` holen. (5) Claude-Code-Auto-Mode-Classifier blockt Device-Config-*Writes* per Bash (Reads ok) → `autoMode.allow`-Regel in `.claude/settings.local.json` nötig (kann Claude sich nicht selbst setzen).
- ✅ **ERLEDIGT 2026-08-08 (Durchführung + Recovery s. TP21.1).** ~~🔜 Als Nächstes (eigene Session, Übergabe): Core-Tausch = L3-Umzug~~ (Routing/DHCP/DNS/CF-Mesh von ap1 → crs326-core) — **Vollausfall-Fenster**, s. `docs/tp15-core-swap-session-handoff-prompt.md`. Danach WLAN-Neuprovisionierung (TP3, CAPsMAN-Abbau). ~~Finaler Mgmt-Renumber dann: branch `.99.13`→`.99.12`, core bleibt `.99.11`.~~ ⚠️ **Anders gekommen (Ist seit 2026-08-08, live bestätigt): core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`.**
- ✅ **Core-Swap VORBEREITET (2026-07-23, reine Prep-Session, kein Netz-Eingriff):** ap1s Live-L3-Config vollständig gelesen (MikroMCP) und als **staged Config** materialisiert → `routeros/tp15-core-l3-staged.rsc` (alles Betriebsumlenkende `disabled=yes`, im Cutover in einem Rutsch `enable`; ~50 Reservierungen + WG-Privkey bewusst erst im Fenster maschinell von ap1 geholt statt abgetippt). Runbook/Rollback/Verifikation im Plan. **3 Fenster-Start-Entscheide festgelegt:** (1) **WG-Hub (TP10 FM/DW-VPN) migriert MIT** — war im Handoff-Prompt übersehen, hängt an ap1s Public-Edge und kann dort nicht bleiben; (2) Governance = **frische Direkt-Ausnahme #3** (REST/MikroMCP), danach intended→Nautobot; (3) Rettungspfad = **iPhone-Hotspot**.
- ✅ **Nautobot-Modell + Golden-Config-Template für L3-Cutover vervollständigt (2026-08-05):** `crs326-core` jetzt Rolle *Router*, alle VLAN-SVIs (`vlan10/20/30/40/50/90/99/100`) + Gateway-IPs (`.1`) modelliert; `nautobot/mikrotik_routeros.j2` (Gitea `bf-golden-config`) erzeugt für `crs326-core` die vollständige staged L3-Konfig (Interface-Lists, GW-IPs, WAN-DHCP, DHCP-Server/Pools, DNS, NAT, Firewall, WG-Hub) und für alle anderen MikroTik-Geräte nur die Identität. Git-Sync + Generate Intended Config erfolgreich getestet; intended config für `crs326-core` liegt in Nautobot vor.
- ✅ **ap1-Cutover-Daten exportiert + in Cutover-Config eingebaut (2026-08-05):** 54 statische DHCP-Leases aus ap1 (`dhcp-v10`/`v20`/`v30`/`v40`) nach `routeros/ap1-leases-to-core.rsc` überführt; WG-Hub-Privkey von ap1 geholt und in die lokale Cutover-Config `routeros/tp15-core-cutover-intended.rsc` eingesetzt (der **WG-Privkey landet NICHT im Git-Template** — Secret).
- 🐞 **Prep-Gotchas:** (a) **MikroMCP kann core noch nicht schreiben** — `ROUTER_CRS326_CORE_*` im MCP-Prozess nicht geladen (jeder Zugriff „Missing environment variable") → vor dem Fenster MCP neu starten oder REST-curl gegen `http://10.40.41.211/rest`. (b) Mgmt-`.99.11` tot (sw1 #12 trägt kein 99T), nur VLAN1-Temp `.41.211` erreichbar. (c) **WG-Hub:** Privkey von ap1 mitnehmen (sonst FM/DW-Reconfig nötig). (d) core-vlan99 wird GW `.1`; Bring-up-`.11` dadurch redundant (Fenster-Reconcile). (e) VLAN1 raus: Hybrid-VLAN1-Drops sw1 GE3/7/10 beim Umstecken einem echten VLAN zuordnen oder stilllegen.
- ✅ **WG-Endpoint-Frage geklärt (2026-07-23):** Spokes adressieren den Hub über `bf4.emsenhuber.net:51821` = CF-A-Record (proxied=false, TTL 120, aktuell `90.146.35.38`). **Kein Updater auf ap1** → nichts zu migrieren; `bf4` folgt der IP manuell (offener TP10-Follow-up). Zieht core am WAN eine neue IP (Lease sticky, eher nicht), via `CF_API_TOKEN` nachziehen: `bf4`-Record + Access-Policy `bf4-home-ip` (`90.146.35.38/32`). **Chance:** core ist nicht `device-mode=home` → scheduler/fetch gehen → `bf4`-DDNS-Auto-Updater dort bauen (schließt TP10-Follow-up).
- 📋 **Session-Handover** für die Cutover-Session: `docs/tp15-core-swap-session-handover.md` (Einstieg + Pre-Window-Checkliste + Kurz-Runbook; Original-Auftrag bleibt `docs/tp15-core-swap-session-handoff-prompt.md`).
- 📦 **2× CRS326-24G-2S+RM werden morgen (2026-07-17) geliefert, Produktivsetzung spätestens Montag 2026-07-20 (User-Entscheid 2026-07-16).** Sequenzierung ggü. Nautobot geklärt: **TP20+TP21 werden bis dahin fast-getrackt** (TP18/Komodo als Blocker entfernt, s. TP20) — Ziel ist, dass Nautobot **vor** der CRS326-Produktivsetzung steht und die Switches **direkt als Nautobot-verwaltete Geräte adoptiert** werden, statt erst von Hand konfiguriert und später importiert. sw1/sw2-VLAN99-Umzug (TP19.1) läuft parallel dazu on-demand, unabhängig davon.
- **🧭 Grundsatzentscheid (2026-07-14, korrigiert 2026-07-15): reines MikroTik-Ecosystem — Routing + Switching + WLAN.** **Omada bekommt KEINE Rolle** (weder Switching noch WLAN — der ursprüngliche „Omada=WLAN"-Satz war ein Fehlschluss beim Dokumentieren, User-Korrektur 2026-07-15: „WLAN war NIE von Omada die Rede"). Begründung Switching: Projektziel #1 **CAKE** nur RouterOS/OpenWrt/OPNsense (Omada-Gateway kann kein CAKE) → Switching = MikroTik CRS. WLAN bleibt auf hAP ax² (~~/CAPsMAN~~ — **CAPsMAN am 2026-08-08 abgeschafft**, Standalone aus der Golden Config; die Hardware-Aussage gilt), kein EAP-Kauf, Coverage-Fix bleibt Platzierungsfrage (TP3.3). Verworfene Vollvarianten (Omada-only, MikroTik-only) samt Begründung + Ansible/Nautobot-Recherche → Memory. **Anlass der Korrektur:** Omada-Controller↔sw2-Link beim VLAN20-Umzug (TP14.1) zerbrochen, versehentlicher Werks-Reset bei Rettungsversuch, fehlerhafter Wiederherstellungsversuch → sw2 wird standalone neu eingerichtet, s. **TP2.1**.
- **🛒 Switches: 2× CRS326-24G-2S+RM bestellt (2026-07-14 — CSS326-Fehlbestellung storniert → CRS).** 24×1G + 2×SFP+, lüfterlos, DC-input (USV-tauglich). Lösen ALLNET-Core (sw1) + Omada-ES208G (sw2, s. TP2.1 — Solo/Interim bis CRS-Tausch) + Cudy ab.
- ✅ **CRS = RouterOS-fähig → in-model:** MikroMCP-Write + Nautobot Golden Config (TP21) managen auch die **Switches**, nicht nur den Router → config-as-code deckt den **ganzen Kabel-Fabric**. (SwOS-Boot bliebe als Fallback möglich, aber nicht genutzt.)
- ⚠️ **kein PoE** (Sonoff/PoE-Verbraucher via Injektor, falls nötig).
- **📶 WLAN bleibt hAP ax²** — kein Hardware-/Ecosystem-Wechsel; die Hardware-Aussage gilt unverändert.
⚠️ **Die Betriebsart hat zweimal gewechselt, letzter Stand ist CAPsMAN:** CAPsMAN abgeschafft
(2026-07-22/08-08, TP3-Rebuild → Standalone-APs) → **wieder eingeführt am 2026-08-17 (TP28), Manager
ist `crs326-core`, ap1/ap2 sind reine CAPs.** Wer hier nur bis zum ersten Pfeil liest, baut auf einem
überholten Stand auf. Coverage-Schwachstellen (P750W-Fall, Terrasse/Kanten) bleiben Platzierungs-/Roaming-Thema von **TP3.3**, nicht durch neue Hardware gelöst.
- **🧭 Router-HW final (User-Entscheid 2026-07-22): kein RB5009.** Kein Zukauf einer dedizierten Router-HW.
- **🔄 KORREKTUR (User-Entscheid 2026-07-22, später am selben Tag): der CRS326-Core WIRD der neue L3-Router.** Die frühere Fassung dieser Zeile („Router bleibt dauerhaft ap1/ap2, L3/CAKE/Firewall bleiben auf ap1") ist damit **überholt**. **Routing, DNS und DHCP wandern vollständig von ap1 auf `crs326-core`**; ap1/ap2 werden zu **reinen Access Points**. Auch der CF-Mesh-Endpoint terminiert dort (s. TP16.1, war schon vorher auf CRS326 retargetet — passt jetzt konsistent zusammen). Damit entfallen die `device-mode=home`-Einschränkungen des hAP (kein scheduler/fetch) für die Router-Rolle. **Reihenfolge (User): erst Switches aufsetzen und in Betrieb nehmen, danach WLAN-Neuprovisionierung der APs.** Betroffen/nachzuziehen: TP4 (Router-Rolle), TP5/TP6 (Firewall + CAKE landen auf dem CRS326), TP21 (Scope-Grenze).
- **📍 CRS326 Live-Stand (2026-07-22, via MNDP von ap1):** **beide Geräte sind bereits am Netz und laufen** — RouterOS **7.19.5**, Werkszustand. `crs326-core` = MAC `04:F4:1C:B4:17:76` (software-id `4W04-URHT`), `crs326-branch` = MAC `04:F4:1C:B4:15:58` (`RGZJ-UK0T`). ⚠️ **Beide auf Werks-IP `192.168.88.1` im selben L2-Segment = IP-Konflikt** → beim Bring-up nur eines nach dem anderen adressieren, Unterscheidung ausschließlich per MAC. Die frühere Notiz „Uplink-Port bislang ungenutzt/down" ist überholt. **Standorte: `crs326-core` = „Technik UG" (virtueller Technikbereich im UG/Diele, KEIN Keller), `crs326-branch` = „Technik OG".**
- **🔌 PoE-Anschluss der APs (User-Vorgabe 2026-07-22):** Verkabelung/Portbelegung bleibt sonst **unverändert**; ap1/ap2 sollen künftig aber am **PoE-Port** hängen. PoE selbst ist noch kein Thema, die Config soll es aber schon vorsehen.
- ✅ **Port = `ether1`.** RouterOS nummeriert Interfaces ab **1** (`ether1`…`ether5`), nie `ether0` — die Unix-Konvention `eth0` gilt hier nicht. Auf dem hAP ax² ist `ether1` der PoE-Port (live bestätigt: `/interface/ethernet/poe` listet ausschließlich `ether1`).
- ⚠️ **Aber: passives PoE 18–28 V, NICHT 802.3af/at.** Ein normaler 802.3af/at-Switch oder -Injektor speist den hAP ax² **nicht**. Es braucht einen **passiven PoE-Injektor** (MikroTik-Standard). Dazu passend: **der CRS326 hat gar kein PoE** (bereits dokumentiert) → Strom kommt ohnehin aus einem Inline-Injektor zwischen Switchport und AP, nicht aus dem Switch.
- ⚠️ **Config-Konsequenz:** ap1s Trunk liegt heute auf **`ether2`** → wandert bei der Neuprovisionierung auf **`ether1`**. Und `ether1` steht aktuell auf **`poe-out=auto-on`** (der hAP kann über denselben Port auch Strom *abgeben*) — beim Umbau auf „ether1 = versorgter Uplink" muss `poe-out` auf diesem Port **`off`** gesetzt werden, sonst speisen Injektor und AP gegeneinander.
- **🔋 DC-USV → ausgelagert nach **TP24** (2026-08-16).** Inhalt unverändert gültig (MeanWell LRS-150-48 + DDR-120-48 + LiFePO4 20 Ah, ~€190, 48-V-Bus; die CRS326 haben passenden DC-Input) — steht ab jetzt dort, samt der offenen Entscheidungen (Mac mini/Inverter, Bus-Standort nach dem Core-Umzug, Batteriegröße, Shutdown-Signalisierung).
- ✅ **Standort-/Ablösungs-Mapping final (User 2026-07-22) — sauberes 1:1:**
| Bereich | heute | wird abgelöst durch |
|---|---|---|
| **Technik UG** (virtueller Bereich in der Diele, **kein Keller**) | sw1 (ALLNET, 16 Ports) | **crs326-core** (= neuer L3-Router) |
| **Technik OG** | sw2 (ES208G, 8 Ports) + ap2 + cudy2 | **crs326-branch** |
| **Wohnzimmer Sideboard** | ap1 | — (bleibt, wird reiner AP) |
Kapazität unkritisch: 16+8 belegte Ports heute gegen 2× 24 GbE + 2× SFP+ künftig. Vollständig in Nautobot gepflegt (alle 8 Devices haben einen Bereich).
- **📻 cudy-Hardware (User 2026-07-22):** beide sind **Cudy WR3600E V1.0**, Firmware `2.3.15-20250905-191956`. `cudy1` = Technik UG, am Core `#11`, aktiv als Übergangs-WLAN (`elektrosmog.c`, `10.40.10.246`) — **und Christophs aktueller WLAN-Pfad**. `cudy2` = Technik OG, außer Betrieb (Nautobot-Status `Decommissioning`), wird nicht reaktiviert.
- ⚠️ **Etappe 1 (Erstzugriff auf die CRS326) ist NICHT kritisch** (User 2026-07-22): Werkszustand, Gerät steht griffbereit neben ihm → der IP-Konflikt auf `192.168.88.1` wird physisch/seriell aufgelöst, kein Lockout-Szenario.
- ✅ **Portplan-Widerspruch AUFGELÖST (User 2026-07-22): am Core ist `#15 = ap1` und `#11 = cudy1`.** Damit gilt die TP4-Variante (`gi16` Arris/VLAN100 → `gi15` Trunk → ap1 `ether2`); **`switch/cli/03-trunk-ap-ports.cli` (gi1 = ap1 / gi2 = ap2) ist STALE** — die Datei bezeichnet sich selbst als „provisorisch, 2026-06-09" und wurde vom Edge-Cutover am 2026-07-01 überholt. Nicht mehr als Quelle verwenden.
- **Zielport nach dem Tausch: ap1 wandert auf `crs326-core` Port 4** (User-Vorgabe). Gleichzeitig wandert ap1s Uplink geräteseitig von `ether2` auf `ether1` (PoE-Port, s.o.).
- ⚠️ **Nach wie vor NICHT vom Gerät gelesen:** die restliche sw1-Belegung. `switch/state-backups/sw1-running-config-20260716-*.txt` sind 214-Byte-Fehlschläge (`Incorrect User Name!!`, toter `claudecode`-Login), `switch/cli/running-PRE.txt` ist Pre-TP1. `gi16` (Arris) und `gi8` (Trunk → sw2) stehen in Nautobot daher **explizit als „UNVERIFIZIERT"** markiert.
- 🔴 **Betriebliches Kernrisiko (Analyse 2026-07-22): der Core-Tausch ist KEIN Switch-Tausch, sondern ein Vollausfall.** `crs326-core` ersetzt sw1 **und** wird gleichzeitig L3-Router — und an sw1 hängt auf `#15` ap1, also Gateway, DHCP, DNS und der WAN-Trunk fürs gesamte Netz. Core-Tausch und L3-Umzug sind damit **faktisch dasselbe Ereignis** und brauchen ein geplantes Ausfallfenster. **→ Reihenfolge-Entscheid (User 2026-07-22): `crs326-branch` zuerst (gegen sw2, Technik OG), `crs326-core` in einer eigenen, späteren Session.**
- 🛟 **Rettungspfade liegen günstig auf verschiedenen Switches:** Christophs Notebook hängt per **WLAN über cudy1 → Core `#11`**; der kabelgebundene Plan B (Dock/Monitor Arbeitszimmer) hängt am **sw2/Branch (P4)**. Beim **Branch**-Tausch stirbt also der Kabelweg, das WLAN überlebt. (Beim Core-Tausch stirbt beides — s. Kernrisiko oben.)
- **Hinweis:** die A/B/C/D-Switch-Varianten in `hw-refresh-requirements.md` sind durch die CRS326-Beschaffung überholt (Doc = Recherche-Archiv + USV/Energie-Referenz).
- Abh.: TP14 ✅ (IP-Migration). Verzahnt: **TP20** (Platform-DB), **TP21** (Nautobot GC), **TP2.1** (sw2-Standalone-Recovery).
### TP16 — CF-Tunnel: Round-Robin killen + reziprokes HA ▸ ✅ KERN UMGESETZT (2026-07-10) — ⚠️ **HA aktuell degradiert (Live-Befund 2026-08-16)** — `docs/cf-tunnel-mapping.md §10`, #1033
- 🟠 **BEFUND 2026-08-16 (beim Doku-Aufräumen live über die CF-API erhoben, kein Eingriff): das reziproke HA läuft nur noch auf einem von drei Tunneln.**
- `bf-mm` = **2 Connectoren** (je 4 conns) ✅ · `bf-nas` = **1 Connector** (3 conns) 🟠 · `bf-pve` = **1 Connector** (4 conns) 🟠.
- **Bedeutung:** die Dienste hinter `bf-nas` (gitea, grafana, pve-mcp, **vw**, ds-bf, nautobot) und `bf-pve` (pve, ccu, iobroker, z2m25, portainer, **hass**, ha-mcp) hängen jeweils an **einem einzigen** cloudflared-Prozess. Fällt dessen Host aus, sind sie weg — genau der Zustand, den TP16 beseitigt hat. **Kein `530`/Round-Robin-Problem** (das bleibt strukturell gelöst), sondern **Redundanzverlust**.
- **Bekanntes Muster:** identisch zum Incident vom 2026-07-16 (macmini-Connectoren flappten unter QUIC → Fix `TUNNEL_TRANSPORT_PROTOCOL=http2`). Diesmal sind die *macmini*-Connectoren oben (bf-mm hat 2), es fehlt je **ein** Gegenstück auf NAS/pve. **Nächster Schritt (eigene Session):** auf NAS und pve nachsehen, welcher der beiden cloudflared-Container nicht läuft, und ob es der `http2`-Konventionsfall ist.
- 🧹 **Nebenbefund beim selben Sweep — Alt-Tunnel mit VLAN1-Origins (alle `down`, 0 Connectoren, daher aktuell harmlos):** `openclaw` (→ `10.40.41.147/.148/.139`), `coolify` (→ `10.40.41.133`), `johnny2`, `TeilzeitCharlie`. ⚠️ **`openclaw` beansprucht `n8n.emsenhuber.net`, das produktiv auf `bf-mm` läuft** — käme der Tunnel je wieder hoch, entstünde exakt das alte Round-Robin-Muster auf diesem Hostname. **Empfehlung: die vier toten Tunnel löschen** (user-gated, Löschen ist nicht umkehrbar) — gehört fachlich zu **TP19** (VLAN1-Retirement: `.41.133` war eines der „unidentifizierten VLAN1-Geräte" aus TP19 B1.2 — hiermit als coolify-Origin identifiziert).
> **✅ Umgesetzt (2026-07-10, eigene Session):** reziprokes **3-Tunnel-HA** live (bf-nas `70d6a4cf` / bf-pve `f7fdf3a6` / bf-mm `7a322261`), 6 Connectoren (2/Host, je Fremdhost), 19 FQDNs Loop-Smoke **0×530**, HA per Host-Ausfall-Sim bewiesen. Round-Robin-#1033 **strukturell weg**. As-Built = `docs/cf-tunnel-mapping.md §10`. **Schritt 5 Access-Strang: ~~`vw` bypass→geschützt via WARP~~ — 🔴 GESTRICHEN (User-Constraint 2026-08-14, s. TP16.2). Verbleibend offen: `hass`/`ha-mcp` 400/403; `gateway-dw` (WG→404) klären.**
- **Symptom (User):** FQDNs über CF werden **sporadisch / „sehr unsicher"** aufgelöst = #1033-Muster zurück („jeden 3. Schuss eine Niete"). User hat alle 3 cloudflared-Images + Container aktualisiert/neu gestartet.
- **✅ Root-Cause endgültig (User-Diagnose bestätigt): macvlan/ipvlan-Host-Isolation.** Ein cloudflared-Connector ist **blind für macvlan/ipvlan-Container auf seinem EIGENEN Host** (Linux-Kernel-Regel), erreicht aber Fremdhost-L2 + co-lokierte Bridge-Container. Mehrere Connectoren auf EINEM Tunnel = geteilter Ingress + CF-**Round-Robin** → jede Origin, die auf dem Host *eines* Connectors liegt, fällt bei ~**1/N** aus. **Strukturell**, nicht durch „Origins fixen" heilbar. Bsp: `omada .20.175`/`pve-mcp .20.173`/`gitea .20.157`/`grafana .20.109` = NAS-macvlan → vom NAS-Connector NICHT erreichbar.
- **Invariante:** innerhalb EINES Tunnels ist **kein Route→Connector-Pinning** möglich (alle Connectoren teilen Ingress, CF round-robined). „Ausgewählte Routen für ausgewählte Connectoren" = **getrennte Tunnel**.
- **Ziel-Design (HA): reziproke Per-Host-Tunnel** — jeder Host-Dienst wird von den Connectoren der ANDEREN Hosts bedient (nie vom eigenen):
| Tunnel (= Dienste eines Hosts) | Connectoren auf | HA |
|---|---|---|
| pve-Dienste | NAS + macmini | 1 abdrehbar |
| NAS-Dienste (omada/pve-mcp/gitea/grafana/…) | pve + macmini | 1 abdrehbar |
| macmini-Dienste (`.20.167`) | pve + NAS | 1 abdrehbar |
3 Tunnel, jeder Host fährt 2 cloudflared → Host-Ausfall ⇒ jeder Tunnel ≥1 Connector = **voll HA**. Voraussetzung: jede Origin L2-erreichbar.
- **Ist-Inventory (live 2026-07-10, s. `cf-tunnel-mapping.md §9`):** Ingress v119, 24 Hostnames, 2 live Connectoren. **DOWN/stale:** `pbs .20.135`, `nginx-bf .41.123` (noch VLAN1!), `hermes`+`hermes-geli .20.167`. **Zu klären (Host/Netz):** `vw .20.127`/`ccu .20.232`/`iobroker .20.107`/`z2m25 .20.106` — ggf. `vw`/`ccu` auf VLAN20-macvlan heben. `gateway-dw` (192.168.178.1) = WG-Sonderfall.
- **CF-Access-Strang (verzahnt, s. TP11 Track-B):** `hass`-**400 in der HA-iPhone-App = CF-Access davor** (Companion-App kann Access-Login nicht) → Fix **ohne Passthrough**: iPhone in **WARP** enrollen (Device-Identity erfüllt Access; „Warp Login App" existiert bereits `allow:christoph`). ~~⚠️ **`vw` = aktuell `bypass` (öffentlich!)** → von bypass auf geschützt heben.~~ 🔴 **Überholt (2026-08-14): `vw` BLEIBT bypass — harter User-Constraint, Härtung stattdessen über TP16.2.** „Passthrough-Hosts sind die kritischen" (User).
- **Umsetzungs-Reihenfolge (für die eigene Session):** (1) **Reachability-Matrix je Host** (pve/NAS/macmini → jede Origin, read-only) BEVOR gruppiert wird; (2) **Interim-Sofortfix:** bf-Tunnel auf **pve-only** (pve erreicht alles außer gateway-dw) → 0×1033, reversibel; (3) reziprokes 3-Tunnel-HA bauen (CNAMEs re-pointen, 6 Connectoren, Token je Tunnel; NAS/macmini-Alt-Token entfernen); (4) tote Origins bereinigen; (5) Access-Strang (WARP + vw); (6) Loop-Gegentest.
- Abh.: TP14.1/14.2 ✅. Verwandt: **TP11** (CF-Tunnel-Track-B/C + Access), #1033. SSoT: `docs/cf-tunnel-mapping.md`. Memory `project_cf_tunnel_1033`.
- ✅ **Incident 2026-07-16 (User unterwegs, WARP „spinnt", keine BF-IPs mehr) ▸ Public-FQDN-Teil gefixt:** macmini-Connectoren (`cloudflared-bfnas`/`-bfpve`) flappten live unter QUIC/UDP (Docker-Desktop-Bridge-NAT) → `bf-nas`/`bf-pve` von 2 auf 1 Connector degradiert (HA faktisch weg, nicht down). Fix: beide auf `TUNNEL_TRANSPORT_PROTOCOL=http2` umgestellt (rollend, 0 Downtime), wieder voll HA. Konvention: macmini-Connectoren künftig immer `http2`. **ICMP-Befund (getrennt, strukturell):** TCP über die WARP-Route ging überall, ICMP nicht (fm 36 %/bf 100 %/dw 100 % Loss) — cloudflared ist QUIC-Proxy, kein WireGuard. **Kurz als „bf-pve-Route wegen USV" notiert (Commit `1c1a676`) → REVIDIERT** (Begründung invertiert: bf-pve-Connectoren laufen auf NAS+macmini, nicht pve). Details: `docs/cf-tunnel-mapping.md §13`. **→ Richtungswechsel für Private Access = TP16.1.**
### TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` ▸ ✅ **ABGESCHLOSSEN (2026-08-17)** — `docs/tp16.1-warp-mesh-native-crs326.md`
- ✅ **Kernergebnis: ICMP ist repariert.** Privater Zugang unterwegs läuft über **natives WireGuard auf `crs326-core`** statt cloudflared-QUIC — **0 % Ping-Loss auf alle BF-VLANs, vorher 100 %**. Genau das Symptom aus dem Incident 2026-07-16, das dieses TP ausgelöst hat. Öffentliche FQDNs unberührt auf dem 3-Tunnel-HA (TP16). Compliance **68/68**.
- ✅ **§4.0 erledigt 2026-08-17: Mesh-Node angelegt.** `bf-core-mesh` = **`d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env` (`CF_MESH_REGISTER_TOKEN`, gitignored). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.**
- 🔑 **Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe).** Der alte User-Token hatte **zwei** fehlende Permissions (§4.0 *und* §4.1 — `devices/settings` war schon lesend `403`). Ersetzt durch einen **account-owned** Token (`9984078812bc…`, ⏰ **gültig bis 2027-01-09**). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an `/accounts/{id}/tokens/verify` — am alten `/user/tokens/verify` melden sie `1000 "Invalid API Token"`, **während alle echten Endpoints 200 liefern**; (2) Dashboard-Fehler `"Account tag in access policy must match tag in request uri"` = eine **Zone**-Permission (`DNS`) steckt in der Policy-Zeile mit **Account**-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in `docs/tp16.1-…md` §4.0. **Merke: Dashboard sagt „Edit", API sagt „Write".**
- ✅ **§4.1–§4.4 durchgezogen 2026-08-17 (User-Freigabe „jetzt durchziehen").** Prereqs: `use_zt_virtual_ip=true`, Device-Profil „Cloudflare Mesh" (`4d3c0f57…`), Split-Tunnel auf **beiden** Profilen korrigiert (`100.64.0.0/10` → `/11` + `100.112.0.0/12`). Gerät: `cf-mesh` auf `crs326-core`, Mesh-IP **`100.96.0.6/32`**, Peer `162.159.193.2:2408`, **Handshake steht**, Route `100.96.0.0/12 → cf-mesh` aktiv; Backup `pre-tp16.1-mesh-20260817.backup` vorher (Auflage #4 ✅). CF meldet Node **healthy**. **§4.4: Weg A hat funktioniert — der befürchtete vnet-Gotcha trat NICHT ein**, `10.40.0.0/16` zeigt jetzt auf `bf-core-mesh` statt `bf-pve` (Rollback = 1 PATCH zurück, `bf-pve` unangetastet).
- ⚠️ **Zwei Werkzeug-Fallen, real getroffen (gehören in jede künftige WG-Arbeit):** (1) **MikroMCPs `manage_wireguard_interface` kann grundsätzlich keinen privaten Schlüssel annehmen** — RouterOS erzeugt ihn selbst; bei CF Mesh kommt er aber von CF → Key per SSH mit Interpolation aus der gitignoreten `.env` setzen, nicht über das Tool. (2) **`manage_wireguard_peer` schreibt `endpoint: "IP:Port"` komplett in `endpoint-address` und lässt `endpoint-port=0`** → **kein Handshake**, sieht aus wie ein Firewall-Problem, ist ein Parameter-Bug. Nach jedem Peer-Add `print detail` gegenlesen. Auch `persistent-keepalive` fehlt dem Tool.
- ✅ **§4.5 ABNAHME BESTANDEN 2026-08-17** (MacBook am Hotspot, WARP connected): **Ping 0 % Loss** auf `10.40.10.1` / `10.40.20.105` / `10.40.99.12` / `10.40.20.10` / `10.40.99.1` (47–63 ms) — **vorher 100 %**. TCP mit echtem Datenfluss (pve 8006 → 200, Nautobot → 302, `ssh macmini` → ok). FQDN-Smoke gitea/hass/pve/nautobot/vw → 200/200/302/302/200 ⇒ **cloudflared-Public-Teil unberührt.**
- ✅ **Auflage Ausnahme #4 erfüllt:** Ist-Stand als intended config zurück nach Nautobot → zuerst **65/68** (drei `extra`-Drifts = die Mesh-Zeilen), Zeilen **byte-genau aus dem Backup** ins Jinja-Template, Push nach `bf-golden-config`, Repo-Sync → **68/68, keine Drifts**. Privkey bleibt draußen (`<>`-Maske wie bei `wg-hub`). ⚠️ **Push-Gotcha:** Nautobot schreibt Backups+intended in **dasselbe** Repo → `git pull --rebase` vor jedem Push, sonst `rejected (fetch first)`.
- ✅ **Mgmt-ACL via GC ausgerollt 2026-08-17 (Nachlauf, alle 4 Geräte) — `72/72 compliant`.** Neue Compliance-Feature **`ip-service`** (`match_config=/ip service`, `config_ordered=false`) → 17→18 Regeln, 68→**72 Checks**. Sollzustand einheitlich im Template: `ssh`/`www`/`winbox`/`api` = `address=10.40.0.0/16,100.96.0.0/12`, `ftp`/`telnet`/`api-ssl` = `disabled=yes`. Ablauf regelkonform: Backups auf allen Zielen → `--job mgmt-acl --apply --no-deploy` → **gestaffelter** Deploy (ap2 zuerst, am Gerät gegengeprüft, dann ap1/branch/core) → Geräte-Gegenprobe → Compliance.
- 🔎 **Der Befund war das Gegenteil der Erwartung:** ich hatte „branch/ap1/ap2 sperren den Mesh aus" befürchtet — tatsächlich hatten sie **gar keine** Address-Beschränkung (ssh/www/winbox/api/ftp/telnet **komplett offen**), und die dynamischen Einträge zeigten `REMOTE 100.96.0.1`, waren also längst per Mesh erreichbar. **`winbox` war auf ALLEN vier unbeschränkt**, auch am Core. Aus dem Erreichbarkeits-Fix wurde damit eine Härtung.
- ⚠️ **Scope-Erweiterung, bewusst und meldepflichtig:** `ftp`/`telnet`/`api-ssl` wurden auf branch/ap1/ap2 **abgeschaltet** (Klartext bzw. ungenutzt; der Core hatte das bereits). Begründung: die Alternative wäre gewesen, „telnet aktiv" als Sollzustand in die SoT zu schreiben. Nichts hier nutzt telnet/ftp — der ALLNET-Telnet-Runner ist Archiv, das Gerät ausgebaut.
- 📌 **Schreibweise (Falle):** `/ip service set ssh …` mit dem **Dienstnamen**, NICHT `[find name=ssh]` — letzteres trifft auch den dynamischen Verbindungs-Eintrag und quittiert mit `failure: this is configured elsewhere`.
- 🔴🔴 **DIE FALLE DIESES TPs — `/ip service` sperrte das Router-Mgmt aus dem Mesh aus** (Core, vor dem Rollout oben): Direkt nach dem WARP-Connect war `crs326-core` mgmt-tot: MikroMCP `Connection lost before handshake`, SSH `kex_exchange_identification: Connection reset`, REST **502** — **bei gleichzeitig verlustfreiem Ping auf dieselbe IP**, und alle Hosts *hinter* dem Core normal. Ursache: `/ip service ssh,www address=10.40.0.0/16`, der Mesh-Client kommt aus `100.96.0.0/12`. ICMP kennt keine Service-ACL. **Und `nc -z` meldet „offen", weil der CF-Gateway-Proxy den TCP-Handshake terminiert — ein TCP-Connect ist über WARP KEIN Reachability-Beweis.** Fix: `address=10.40.0.0/16,100.96.0.0/12`. **Merkregel: wer per Mesh ans Netz kommt, ist noch lange nicht am Router.** ⚠️ **Für `crs326-branch`/`ap1`/`ap2` noch OFFEN** — deren eigene `/ip service`-ACLs sind ungeprüft. Entscheidender Diagnose-Schritt war der **Banner-Grab vom macmini** (`nc 10.40.99.1 22` → `SSH-2.0-ROSSSH`): von innen gesund ⇒ Pfad/ACL, nicht Gerät. Fehlspuren, die man sich sparen kann: MTU (die großen Pings scheitern am **WARP-Client**-`utun` MTU 1380, nicht an `cf-mesh`) und SSH-Session-Erschöpfung.
- ✅ **Schalter „Allow all Cloudflare One traffic to reach enrolled devices" — recherchiert 2026-08-17, ENTSCHIEDEN: bleibt AUS.** Laut CF-Settings-Referenz ist es ein **globales** Device-Client-Setting („Global device client settings"), alle Pläne, alle OS, Client-Mode „Traffic and DNS". **Funktion: es lässt Traffic aus Mesh/WAN *zu den eingebuchten Geräten hin* — also Zugriff auf Device-IPs.** Das ist die **Client↔Client**-Richtung und für unseren Fall (Gerät → Mesh-Node → `10.40.0.0/16`) nicht nötig; der Mesh läuft bewiesenermaßen ohne. **Kein API-Feld** (in Spec, `devices/settings`-PATCH *und* Profil-GET gegengeprüft), und die CF-Docs nennen **keinen** Menüpfad. ⚠️ **Die Docs widersprechen sich:** die Mesh-Get-Started-Seite nennt es eine *Profil*-Einstellung, die Settings-Referenz eine *globale*. Nach User-Vorgabe („im Zweifel ohne Schalter") **nicht gesetzt** — nichts gebaut, nichts geklickt. Erst relevant, wenn ein Zugriff **auf ein Endgerät** (statt auf das Netz) gebraucht wird.
- 📌 **Bleibt gültig:** die HA-Degradierung der öffentlichen Tunnel (TP16, `bf-pve` fährt nur einen Connector) wird davon **nicht** geheilt — eigener offener Punkt. Und für **TP5** vormerken: bei deny-by-default braucht der Mesh explizit `forward accept 100.96.0.0/12 ↔ 10.40.0.0/16`.
- ✅ **Gate erfüllt (2026-08-16):** die Bedingung „gated bis TP21 den CRS326 abdeckt" ist eingelöst — `crs326-core` ist seit 2026-08-08 der produktive L3-Router und seit 2026-08-13 in Nautobot adoptiert (68/68 compliant). Runbook §3 neu gemessen, §4.3 von Platzhalter auf **echte Werte** gezogen.
- 🧭 **Erwartung geraderücken (wichtig, weil leicht zu verwechseln): das Mesh ersetzt die CF-Tunnel NICHT.** TP16.1 löst ausschließlich den **privaten** Zugang (unterwegs → `10.40.0.0/16` per IP, **inklusive ICMP**) und tauscht dort den cloudflared-QUIC-Proxy gegen natives WireGuard auf dem Core. Die **öffentlichen FQDNs bleiben** auf dem 3-Tunnel-HA aus TP16. Beide Stränge laufen nebeneinander — die HA-Degradierung aus TP16 (2026-08-16) wird davon **nicht** geheilt und bleibt ein eigener offener Punkt.
- 📌 **Live-Befunde 2026-08-16 (read-only erhoben, kein Eingriff):**
- `warp_connector` = `[]`, aber der **GET antwortet** (kein 403) → die Free-Plan-Frage entscheidet sich erst am `POST` (§4.0). `CF_API_TOKEN` ist `active`; ob er die separate Permission **„Cloudflare One Connector: WARP"** trägt, zeigt genau dieser POST — dort schlug es am 2026-07-17 mit `10000` fehl.
- `use_zt_virtual_ip: false` → Unique-Device-IP muss weiterhin eingeschaltet werden. Gateway-Proxy TCP+UDP sind an.
- **Nur EIN Device-Profil** (Default). Der „auf beiden Profilen"-Hinweis im Runbook meint also: Default jetzt, Mesh-Profil sobald es existiert — es gibt kein zweites, auf dem man etwas übersehen könnte.
- Split-Tunnel-Exclude enthält unverändert `100.64.0.0/10` (verschluckt die Mesh-Overlay-Range) → der §4.1.4-Schritt bleibt nötig.
- Die vnet-Route `10.40.0.0/16` (`5546dcb6…`) zeigt weiter auf `bf-pve` — der Fallback steht. ⚠️ **Aber `bf-pve` fährt derzeit nur einen Connector**, der private Pfad hängt also an einem einzelnen cloudflared-Prozess.
- ✅ **Die letzte inhaltliche Unbekannte ist weg:** „Rückweg-Routing TBD" hat sich erledigt, weil der Core L3-Gateway **aller** VLANs ist — es braucht keine Zusatzroute. Und `wg-hub` (TP10) liegt auf **demselben** Gerät, ist also direktes Vorbild statt fernes Muster.
- ✅ **GOVERNANCE-AUSNAHME #4 (User, 2026-08-16): die WG-Zeilen für den Mesh-Bring-up auf `crs326-core` dürfen DIREKT gesetzt werden** (MikroMCP `plan_changes` dryRun → `apply_plan`). **Begründung — es ist kein Bequemlichkeits-, sondern ein struktureller Grund:** TP22-Regel 1 schließt jede Sektion, die `<>` enthält, generell vom GC-Push aus, und `/interface wireguard` ist genau so eine Sektion. Ein privater Schlüssel ist über Golden Config also **gar nicht deploybar** — die frühere Zeile „CRS326-Zeilen laufen regulär über Nautobot GC" trägt nicht mehr. **Auflage (wie Ausnahme #2):** Ist-Stand danach als intended config zurück nach Nautobot; vorher `/system backup` (Regel 3). **Scope-Grenze:** nur der Mesh-Bring-up auf `crs326-core` — nicht spätere WG-Änderungen, nicht andere Geräte.
- 🔑 **Damit die Ausnahme ein einmaliger Bootstrap bleibt — Secret-Handling-Design (Runbook §4.3):** ein privater Schlüssel ist **Geräte-Identität**, keine Konfiguration (wie ein SSH-Hostkey). Regel: *der private Teil entsteht am Gerät und verlässt es nie; nur der öffentliche geht in die SoT und wird in die **Peer**-Configs gerendert.* RouterOS kann das nativ (`/interface wireguard add` erzeugt den Privkey selbst). Compliance bleibt grün, weil die `ConfigReplace`-Maske aus TP22 Schritt 0 byte-identisch zu der im Template ist. ⚠️ Vorbehalt: bei CF Mesh erzeugt **CF** das Schlüsselpaar — dann fällt die On-Device-Erzeugung weg, das Prinzip („nie in Git, nie in der intended config") hält trotzdem. **Stufe 2** (`nautobot-secrets-providers` + Vault, Wert erst bei der Plan-Erzeugung einsetzen) lohnt bei 4 Geräten und einem Key nicht — eigener TP nach TP18/TP20.1.
- 🧹 **Sofort fälliger Modellierungs-Nachzug, unabhängig vom Mesh:** die WG-Peers `fm`/`dw` stehen mit hartkodierten `public-key="…"` im Jinja-Template. **Public Keys sind Daten, keine Templatezeilen** — gehören nach Nautobot und von dort gerendert.
- ⚠️ **Für TP5 vormerken:** sobald deny-by-default greift, braucht der Mesh-Verkehr explizit `forward accept 100.96.0.0/12 ↔ 10.40.0.0/16` — heute passiert er nur, weil die `forward`-Kette ohne Drop endet.
- ⏭️ **Was die Scharfschalt-Session macht:** `POST warp_connector` (Plan-Gate) → account-weite Prereqs (Unique Device IP, Split-Tunnel auf beiden Profilen) → `wgcf-connector`-Token → 4 WG-Zeilen auf dem Core → Route `5546dcb6…` umhängen (vnet-Gotcha, Weg A/B) → Ping-Abnahme. **Trifft alle Live-WARP-Geräte mit einem kurzen Reconnect** → Zeitpunkt user-gated.
- ✅ **Update (User-Entscheid 2026-07-22):** Ziel-Device für den WG-Mesh-Node ist jetzt der **CRS326 Core-Switch statt ap1** — konsistent mit dem TP15-Endstand (kein RB5009) — ⚠️ **die damalige Klammer „Router bleibt ap1/ap2 rein für L3/WLAN" ist seit dem Core-Cutover 2026-08-08 überholt: L3 liegt auf `crs326-core`, ap1/ap2 sind reine APs.** Der Retarget auf den Core war damit im Ergebnis richtig, nur anders begründet als hier notiert und TP21s "ganzer Kabel-Fabric via RouterOS"-Modell (CRS326 ist ohnehin RouterOS-fähig und wird Nautobot-verwaltet). **Reine Ziel-Doku-Änderung** — das Scharfschalten bleibt weiterhin eine eigene, spätere Session und bleibt gated, bis TP21 den CRS326 als Device abdeckt. **Keine neue Governance-Ausnahme:** die bestehende ap1-Ausnahme (s. Ist-Stand-Zeile 31) gilt explizit **nicht** automatisch für den CRS326 — CRS326-Konfiguration soll regulär über Nautobot GC laufen, sobald TP21 so weit ist. Die CRS326 selbst sind aktuell noch Werkszustand (physisch bereits mit dem aktuellen Core verkabelt, aber die betreffende Uplink-Port bislang ungenutzt/down) — konkrete Management-IP/Interface-Zuordnung für den CRS326 als L3/WG-Endpoint ist noch **nicht** getroffen (TBD nach TP15-Bring-up).
- **Trennung:** Public FQDNs bleiben auf cloudflared-3-Tunnel-HA (TP16); **Private Access** (unterwegs → `10.40.0.0/16` per IP inkl. ICMP) wandert vom cloudflared-QUIC-Proxy auf einen **nativen WireGuard-Mesh-Node auf dem CRS326 Core-Switch**.
- **Warum CRS326-nativ:** Cloudflare Mesh spricht WireGuard, RouterOS kann natives WireGuard (TP10-Hub auf ap1 *ist* einer, Muster überträgt sich) → Mesh-Node = WG-Peer zu CF-PoP, **kein Container/TUN/LXC/neue HW**. Verlagert die Rolle vom Router (ap1, der weiterhin Public-Edge + WLAN-Anker bleibt) auf den Core-Switch, der als zentraler Kabel-Fabric-Knoten ohnehin alle VLANs sieht. RouterOS-Container-Weg bleibt tot (kein `/dev/net/tun`-Passthrough auf JEDER Router/Switch-HW) — daher weiterhin natives WireGuard, kein Container-Ansatz.
- **Verworfen:** WG-Roadwarrior (User will keine Extra-App/Profil je Gerät), alle Helper-Host-ZT-Anbieter (SPOF-Verletzung).
- **Warum heute nur Konzept:** TCP-Pfad funktioniert (nur ICMP-Komfort fehlt), und die CF-Prerequisites sind **account-weit** (Unique-Device-IP, Split-Tunnel-Exclude, Device-Profile) → treffen Live-WARP-Geräte (1× kurzer Reconnect) → nicht unter Zeitdruck. Zusätzlich jetzt gated durch den Retarget: CRS326-Bring-up (Management-IP, VLAN-Filtering) muss erst stehen.
- **Nächste Session (nach CRS326-Bring-up):** Plan-Probe `POST warp_connector` (Free-Plan-Gate; sonst LXC-Fallback) → account-weite Prereqs → `wgcf-connector`-Token→WG-Config → 4 WG-Zeilen auf dem CRS326 → Subnetz-Route + vnet-Route `5546dcb6…` von bf-pve umhängen (Rollback trivial) → Verifikation (Ping verlustfrei). Team-Domain `emsenhuber.cloudflareaccess.com`. Governance „Config nur via Nautobot" → CRS326-Zeilen regulär via TP21/GC, sobald das Device dort adoptiert ist.
- ✅ **Recherche-Härtung 2026-07-17 (Primärquellen animmouse + offizielle CF-Docs + OpenAPI-Spec, alles verifiziert, gilt unabhängig vom Zielgerät):** (1) Produkt heißt seit 2026-04-14 offiziell **Cloudflare Mesh** (API-Pfade bleiben `warp_connector`, im Spec bestätigt inkl. Token- und sogar HA-Endpoints); (2) ICMP-Proxy ist **bereits AN** (User-Korrektur: fm-Ping `10.101.0.0/24` beweist es — kein To-Do; mein „fehlt" war ein Fehlschluss aus dem API-Spec, das Flag ist Dashboard-only); (3) Split-Tunnel-Exclude auf **beiden** Device-Profilen (Default + Mesh); (4) **vnet-Gotcha** beim Route-Umzug („virtual networks only for cloudflared") → Weg A PATCH, Weg B DELETE+POST dokumentiert; (5) **v4-only-Entscheid** (Mesh-IPv6-site-to-site laut Guide broken, NAT66-Gefrickel nicht nötig); (6) keepalive 1m statt 25s ok. Alles in `docs/tp16.1-warp-mesh-native-crs326.md` §2/§4 eingearbeitet (Config-Block dort als Platzhalter markiert, IP/Interface TBD).
- Abh.: TP16 ✅ (Public-Teil), TP10 ✅ (WG-Grundmuster, jetzt als Vorbild statt Direktziel), **TP15** (CRS326-Bring-up), **TP21** (Nautobot-Adoption des CRS326, Voraussetzung fürs Scharfschalten). SSoT: `docs/tp16.1-warp-mesh-native-crs326.md`, `docs/cf-tunnel-mapping.md §14`.
### TP16.2 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) ▸ 📋 KONZEPT (2026-08-14), Umsetzung offen
- 🔴 **HARTER CONSTRAINT (User 2026-08-14): `vw` wird NICHT WARP-/Access-geschützt.** Gilt dauerhaft, nicht „vorerst". **Begründung (technisch belastbar, nicht Bequemlichkeit):** die Bitwarden-Clients (Browser-Extension, Mobile, Desktop) können den **interaktiven Access-Login nicht durchführen** — dasselbe Muster, das die HA-Companion-App auf `400` laufen lässt. Ein Passwortmanager hinter einem Client, den man im Notfall (fremdes Gerät, Neuinstallation, Handyverlust) nicht installieren kann, ist ein **Aussperr-Risiko gegen den eigenen Schlüsselbund** — und damit gefährlicher als das, wovor die Maßnahme schützen soll. **Die frühere TP16-Schritt-5-Absicht „vw von bypass auf geschützt heben" ist damit gestrichen.**
- **Ziel stattdessen: Schutz erhöhen, ohne Clients zu brechen** — gilt für **alle** bypass-Dienste (`vw`, `hass`, `gitea`).
- ✅ **Schicht 0 — bereits vorhanden, nicht unterschätzen:** keine offenen Ports, **alles ausschließlich über CF-Tunnel** (3-Tunnel-HA, TP16), TLS terminiert an der Edge, DDoS-Schutz. Der Origin ist nicht direkt erreichbar. Das ist bereits mehr als bei den meisten Selfhostern.
- ✅ **Schicht 1 — chirurgisch: Access nur auf die Pfade, die kein Client anfasst.** CF Access wertet **die spezifischere Application zuerst** → man kann eine **zweite Access-App auf `vw.emsenhuber.net/admin*`** legen (echte Policy) und den Host im Übrigen auf bypass lassen. **Das Admin-Panel wird ausschließlich per Browser bedient — dort funktioniert der Access-Login einwandfrei**, während `/api`, `/identity` und `/notifications` (die Client-Pfade) unberührt bleiben. Kein Client bricht, das wertvollste Ziel ist weg.
- Alternativ noch härter: **Admin-Panel ganz aus** (`ADMIN_TOKEN` leer lassen) und nur bei Bedarf temporär aktivieren.
- Analog für **`gitea`**: `/admin*` + Registrierung path-scoped schützen (Git-über-HTTPS mit Token bleibt offen).
- ✅ **Schicht 2 — App-eigene Bremsen (kostenlos, sofort):** Vaultwarden hat Rate-Limits **per Default an** (`LOGIN_RATELIMIT_SECONDS=60`, `LOGIN_RATELIMIT_MAX_BURST=10`, dazu `ADMIN_RATELIMIT_*`) → Werte prüfen/verschärfen; `SIGNUPS_ALLOWED=false`, `INVITATIONS_ALLOWED=false`, `SHOW_PASSWORD_HINT=false`, **2FA erzwingen**. HA hat `ip_ban_enabled` + `login_attempts_threshold`. Gitea: Registrierung aus.
- ✅ **Schicht 3 — Edge-Regeln (CF), wirkt für alle Dienste gemeinsam:** WAF-Custom-Rules + **Rate Limiting gezielt auf die Login-Endpunkte** (`/identity/connect/token` bei vw, `/auth/login` bei HA, `/user/login` bei Gitea); optional Geo-/ASN-Filter. ⚠️ **Free-Plan-Kontingente vorher im Dashboard prüfen** (nicht belegt).
- ✅ **Schicht 4 — ein Mechanismus für alle: CrowdSec (oder fail2ban) mit Cloudflare-Bouncer.** Bans landen dann **an der Edge** statt erst am Origin. Vaultwarden hat ein offizielles Fail2Ban-Setup-Wiki (Log-Muster für Login **und** Admin-Panel).
- 🔴 **Voraussetzung für Schicht 2–4, und der eigentliche Knackpunkt: die echte Client-IP muss am Origin ankommen.** Hinter dem CF-Tunnel sieht die Anwendung sonst **nur die IP des cloudflared-Containers** → jede IP-Sperre bannt den Tunnel statt den Angreifer = **Selbstaussperrung aller Nutzer**, und Rate-Limits greifen ins Leere. **Genau dieser Defekt ist bereits offen dokumentiert:** HAs `trusted_proxies` fehlen `.20.10`/`.20.167` (TP16, `hass`-400-Strang). ➡️ **`X-Forwarded-For`/`CF-Connecting-IP` sauber durchreichen ist Schritt 1 dieses TPs**, alles andere baut darauf auf.
- **Reihenfolge:** (1) Client-IP-Weitergabe fixen (inkl. HA `trusted_proxies`) → (2) `/admin`-Pfade path-scoped schützen → (3) App-Limits + 2FA → (4) Edge-Rules → (5) CrowdSec/fail2ban mit CF-Bouncer.
- **Abh.:** TP16 ✅ (Tunnel-HA). Verwandt: **TP20.1** (vw wandert auf Platform-Postgres — DB-HA ≠ Access, getrennt halten), **TP23** (dessen WARP-Managed-Network-Idee ist durch diesen Constraint **entlastet**: `vw` verlangt keine Device-Identity, also kollidiert „WARP zuhause aus" nicht mehr mit dem Access-Strang).
### TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` ▸ ✅ **KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30** — Geräte-PW (TP17.2) weiterhin bewusst zurückgestellt — `mcp/shelly/`, Runbook `docs/tp17-shelly-go-runbook.md`
- ✅ **Abschluss 2026-08-13 (User-Bestätigt):** auch die zuletzt offenen saisonalen Shellys (s1/s2/s3/s12) sind inzwischen umgeklemmt — **alle Shellys auf `dirtyvader-iot`/VLAN30**, kein VLAN1-Shelly-Rest mehr. Migration-Teil von TP17 damit vollständig abgeschlossen.
- ✅ **GO durchgezogen 2026-07-13 (16 Shellys VLAN1→VLAN30, 0 Fehler):** Pilot s26 + Welle A (s4–s9) + Welle B (s10/11/13/15/17/19/20/21/25) → alle auf `dirtyvader-iot`/`10.40.30.` verifiziert (SSID+IP+rssi). Mit den 6 Vorab-Migrierten (s14/16/18/22/23/24) = **22/26 auf VLAN30**; VLAN1-Sweep `.180-250` = **0 Shellys**. Restliche 4 saisonal ausgesteckte (s1/s2/s3/s12) inzwischen ebenfalls migriert (s.o.).
- 🔧 **Anker-Verfahren (User-Idee, statt sta1-Backup):** je Gerät **vor** dem sta-Write die geräteeigene **AP-Funktion** aktiviert (`WiFi.SetConfig ap.enable` — Firmware verlangt WPA2, kein offenes AP → PSK=IOT-PSK, live/kein Reboot) = Notanker, falls VLAN30-Join scheitert (Gerät bleibt über `Shelly*-`-AP erreichbar, **kein physischer Reset**). Nach Verify alle 22 AP-Anker wieder `deanchor`t. Runner: `mcp/shelly/migrate.py` (`migrate`/`deanchor`/`auth`/`table`, Dry-Run default, stoppt bei erstem Fehler, reused `discover.py`).
- ✅ **HA-Cross-Check nach Batch:** einzige `unavailable` Shelly-Entities = **s12** (saisonal aus) — alle 22 migrierten reconnecten via mDNS/zeroconf (TP7-Repeater vlan30↔vlan20-HA) auf neuer `.30.x`-IP, **Entity-IDs unverändert** (keyt per MAC). Inventar `inventory/bf.csv` .41→.30 nachgezogen (22 Zeilen, Section→IOT).
- ⚠️ **Coverage-Flag:** `s4 kueche_mikrowelle` verbindet mit **rssi −93** (Metallgehäuse), hat aber verifiziert — grenzwertig, im Auge behalten. s7 Tiefkühl −85. Rest gut (−26…−81).
- ✅ **Eco-Mode-Policy (2026-07-13, PROVISORISCH):** eco senkt CPU-Takt + WiFi-TX-Leistung → **eco AN für RSSI ≥ −80** (Energie-Win, TX irrelevant), **eco AUS für < −80** (Uplink-Headroom): s4/−91, s7/−88, s17/−81, s18/−79…−82, s19/−86 → 17× ON / 5× OFF, live via `Sys.SetConfig device.eco_mode` (kein Reboot). s4 (Küchen-Sockel/Metall, −91, fix) bewusst eco-OFF = schwächster Link. ⏳ **2. Eval nötig nach TP3.3** (WLAN-Ausrichtung/Aufbau ändert RSSI → Grenzfälle neu bewerten; User 2026-07-13).
- ✅ **HA-Referenz http vs https geklärt (2026-07-13):** HA speichert `configuration_url` für **alle 22 Shellys bereits `http://10.40.30.x:80`** (Integration korrekt + neue IPs übernommen). Das „https" ist **client-seitig** (Mixed-Content-Upgrade, wenn HA selbst über https/Tunnel geöffnet) — **kein HA-Fix möglich/nötig, Shelly-SSL bewusst NICHT** (CPU/Energie, IoT-Netz). ⚠️ Rand-Fund: 1 HA-Shelly-Device zeigt auf `10.95.10.206` (Fremd-Subnetz, nicht BF) → separat prüfen.
- ⏳ **Rest (nicht mehr Migration, s. TP17.2/TP19):** (1) einheitliches Geräte-PW → **zurückgestellt als [[#TP17.2]]** (Kosmetik). (2) Nicht-Shelly-Mover + cudy-Teardown + VLAN1-Client-Retirement → läuft in **TP19**. (3) DNS/ip-plan Restfeinschliff (mit TP19).
#### TP17.2 — einheitliches Geräte-Passwort (Shelly `SetAuth`) ▸ ⏸️ ZURÜCKGESTELLT (Kosmetik, User 2026-07-13)
- **Was:** je Gerät `Shelly.SetAuth` (User `admin`, realm-individueller `ha1` = sha256("admin::"), einheitliches Klartext-PW) via `mcp/shelly/migrate.py auth --go`. `SHELLY_DEVICE_PW` liegt in `mcp/shelly/.env` (gesetzt, len 13). Runner + Digest-Auth-Fallback in `_rpc` fertig.
- **Warum zurückgestellt:** rein kosmetisch/Härtung; **bricht die HA-Shelly-Integration**, bis pro Config-Entry (22×) das Credential neu eingetragen ist → disruptiv, kein Mehrwert im abgeschotteten IOT-VLAN. **Erst nach** dem Netz-Teardown (TP19) neu bewerten.
- **Ziel:** ~26 Shellys (aktuell untagged VLAN1 `10.40.41.2xx`, weil auf **alt-WLAN cudy**, s. TP3.1/`project_vlan1_dhcp_incident`) auf SSID **`dirtyvader-iot`** (VLAN30, `w2-iot`, WPA2/WPA3-mixed, PSK `74c9L283$669`) umklemmen → DHCP `dhcp-v30`/pool-v30 (live). **Kein netzseitiger Move möglich** (anderes WLAN) → per-Gerät WiFi-Reconfig.
- **User-Entscheide (2026-07-11):** (1) **volles `shelly-mcp`** bauen (reusable, nicht nur Skript); (2) **Pilot 2–3, dann Rest**; (3) **DHCP-Reservierungen je MAC** in VLAN30 (stabile IPs für HA/ioBroker/DNS).
- ✅ **Recon durch (`mcp/shelly/discover.py`, read-only /shelly-Sweep):** 16 live, **alle Gen3/Gen4** (uniforme RPC-API `WiFi.SetConfig`, auth=off — kein Gen1-Sonderfall). 10 „offline" = **weihnachten_\*** (s1/s2/s3, saisonal ausgesteckt Juli) + s12/s14/s16/s18/s22/s23 (aus/DHCP-drift). ⚠️ **Inventar-Fix:** s6 (kueche_kuehlschrank, `.213`) MAC falsch erfasst (`0c:72:74:36:90:9e`) → real **`d0:cf:13:d8:18:fc`**. s24 hat gar keine MAC.
- **Mechanismus je Gerät:** Gen2+ RPC `WiFi.SetConfig {sta:{ssid,pass,enable}}` → Reboot → landet `.30.x`. ⚠️ **Risiken:** falsches PSK → AP-Mode (physischer Reset); Gerät kurz offline; HA/ioBroker-Refs brechen bis re-pointed; WPA3-mixed ggf. zickig auf alter FW; ap1 2,4-GHz-Master `wifi2` `running:false` → Coverage v.a. via ap2 prüfen.
- ✅ **Live-Abgleich 2026-07-11 (go-ready):** (a) `shelly-mcp` (`server.py`) fertig — discover/get/set_sta/verify/reservation_plan, PSK in `.env` gesetzt (noch nicht als MCP registriert, optional). (b) **Alle VLAN30-DHCP-Reservierungen liegen** (`.30.2xx`, Oktett erhalten, außerhalb Pool `.100–.200`); **s6-Reservierung `.30.213` MAC gefixt** (falsch `0C:72…9E` → real `D0:CF:13:D8:18:FC`). (c) **IOT-SSID live verifiziert:** ap1 `w2-iot` `running:true`, 8 Clients −26…−68 dBm (ap1-`wifi2`-„running:false" ist kosmetisch). (d) **Migration bereits ~5/26 durch:** s14/16/18/22/23 seit ~2,5 d stabil auf reservierten `.30.221/.223/.225/.202/.203` (+ `.30.204` unbekannt, evtl. s24) → **Pilot faktisch bestanden**.
- ✅ **HA-Cross-Check 2026-07-11 (alle IPv4-Geräte in HA):** Shellys deckungsgleich; **`.30.204` = Shelly „Bad/Spiegelschrank" (Plus RGBW PM, `14:33:5c:d8:2d:7c`) = vmtl. s24** → 6 statt 5 schon migriert. **Netzweite Aufstellung + VLAN1-Leerstand-Analyse** im Runbook: nach TP17 + pve-Host→VLAN20 + Christophs Endgeräte→VLAN10 ist VLAN1 **endgeräte-frei**; es bleiben nur Switch-/AP-Mgmt (sw1/sw2/ap2 → VLAN99-Cutover, eigener Schritt) + **1 unidentifiziertes Gerät `.41.246`** (morgen klären). **Nicht-Shelly-VLAN30-Mover (User-bestätigt):** ✅ SwitchBot floor-lamp `.41.184` + ✅ Twinkly-Girlande `.30.228` (beide 2026-07-16 übersiedelt, s. TP19) + Creality Ender (fehlt noch, off). **Withings schon übersiedelt; Tasmota existiert nicht mehr** (kein aktives Gerät → stale HA-Eintrag). EOS-Smartwatches/eBook-Reader später + von Christoph selbst (eBook eher VLAN50).
- **Scope-Erweiterung (User 2026-07-11):** je Gerät **3 Änderungen** — WLAN→`dirtyvader-iot` + IP (`.41.2xx`→`.30.2xx`) **+ einheitliches Geräte-Passwort** (`Shelly.SetAuth`, User `admin`, realm-individueller ha1 → Tool `set_auth` in `server.py` nachrüsten). **PW entschieden: eigenes Shelly-Admin-PW** (getrennt vom WLAN-PSK) → `SHELLY_DEVICE_PW` in `.env` (Christoph liefert Wert). **HA-Entities unverändert** (keyt per MAC). **HA-Entities bleiben unverändert** (HA keyt per MAC). **Projektion TP17.1 (experimentell, nachrangig):** Gen4 (~11 St.) auf **Thread/Matter** flashen (Firmware-Verfügbarkeit + Thread-Border-Router offen) — Konflikt mit „Entities unverändert" (Matter = neue Entities) → erst WiFi-Migration stabilisieren.
- ✅ **Reservierungs-/Pool-Konvention (User 2026-07-11):** alle Shellys bekommen **statische IP** (`10.40.30.`, gw/dns `.30.1`) **+ in-pool DHCP-Reservierung** als Backup. `pool-v30` auf `.100-.250` erweitert (Reservierungen jetzt in-pool); `set_sta` auf `static=true` umgestellt; s24-Inventar + `.30.204`-Reservierung angelegt. **Vollständiger HA-MAC-Scan:** 2 verpasste Media-Geräte **Apple TV 4K + Philips TV** (aus, → VLAN40) — **vor cudy-Abschuss** VLAN verifizieren; **Apple TV = vmtl. Thread-Border-Router** (TP17.1). **`.41.246/.247 = cudy1/cudy2`** (Teardown-Ziele). Tasmota bestätigt tot.
- **Offene Schritte (morgen, „nur noch go", Runbook `docs/tp17-shelly-go-runbook.md`):** die **16 verbliebenen VLAN1-Shellys** (s4–s11, s13, s15, s17, s19–s21, s25, s26 auf `10.40.41.2xx`) per `WiFi.SetConfig`→`dirtyvader-iot` umklemmen (Batch, je Gerät `.30.x` verifizieren) → Geräte-PW setzen → HA/ioBroker-Re-Point → SwitchBot/Tasmota/3. Nicht-Shelly → saisonale s1/s2/s3/s12 beim nächsten Einstecken → cudy-Alt-WLAN abbauen → DNS/Inventar/`ip-plan` nachziehen.
- Abh.: SSID/VLAN30 (TP3/TP4 ✅), DHCP-v30 ✅. Verwandt: `project_vlan1_dhcp_incident` (cudy-Rest-Leak), TP5 (IOT-Firewall danach). Memory `project_shelly_migration_tp17`.
### TP18 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier) ▸ 🔜 KONZEPT (2026-07-12)
- **Auslöser:** 9-TB-Syno-Backup-Volume **zu 2/3 voll**; reine Daten ~3 TB, Tendenz steigend, „schlagartiger" Bedarf nicht ausgeschlossen. „Wenn skalieren, dann jetzt Syno-Nachfolger mitdenken."
- **Kriterien (User-priorisiert):** (1) **keine Fremd-Abhängigkeit / kein Cloud-Lock-in** (Cloud-Opex abschreckend); (2) **Multi-Site BF/FM/DW**; (3) **HA** (Standort-Verlust überlebbar); (4) **billig/teuer trennen** — Variante **„kalt = Objektspeicher, heiß = lokal"** genügt (kein Hot/Cold *innerhalb* des Stores nötig); (5) **horizontal + vertikal skalierbar**; (6) **S3-API als Branchenstandard**, den jedes Backup-Tool spricht.
- **Entscheid Engine = Garage** (Deuxfleurs, Rust): einziger Kandidat, der **geo-verteilt über offenes Internet/WAN latenztolerant** ist (CRDT-Metadaten, eventually consistent) → passt aufs 3-Keller-Modell BF/FM/DW. **Ceph verworfen** (will Sub-ms-LAN, Single-Cluster-über-WAN = Anti-Pattern; zu schwer, ~1 GB RAM/TB). **MinIO** (AGPL + Console/Feature-Abbau 2025) und **SeaweedFS** (kann Hot/Cold + Erasure-Coding, aber komplexer, Multi-Site async) = **Fallback**, falls später feingranulares Tiering / EC-Effizienz bei zweistelligen TB dominant wird.
- **Entscheid Disk-Layout = JBOD, KEIN RAID.** Redundanz gehört in **eine** Schicht: Garage RF=3 über 3 Sites *ist* die Redundanz. RAID 1 darunter = Doppelt-Bezahlen (3 Sites × 2 Mirror = **6× roh für 1× nutzbar**, absurd). RAID 0 verworfen (1 Platte tot = ganzer Node-Rebuild über WAN). **JBOD = jede Platte eigenes Garage-Data-Dir** (v1 Multi-HDD): Failure-Domain = 1 Platte, Rebuild nur deren Anteil, kein Kapazitätsverlust. **Merksatz: distributed store = „just a bunch of disks", Redundanz ausschließlich über die Sites.**
- **Was man dafür bezahlt:** Plattentod triggert **Re-Replikation über WAN** (durch Upload-BW gedeckelt). Bei ~3 TB/Site tolerabel; wenn Bedarf „schlagartig" auf zweistellige TB springt → mehr/kleinere Platten (Blast-Radius) oder 4. Node, ggf. Wechsel auf SeaweedFS-EC.
- **Dimensionierung (Planungsanker 10 TB nutzbar; heute 3 TB unique / 6 TB Backup-used):** RF=3 → **30 TB roh gesamt = 1 volle Kopie ~10 TB/Site** (provisionieren ~16 TB roh/Site: 2 Platten = halber Blast-Radius + Headroom). Linear skalierend.
- **Kostenmodell** *(Annahmen: HDD neu CMR ~€18/TB · recert ~€10/TB · Strom €0,25/kWh · Lean-Node Mini-PC/pve 12 W + 2 HDD ~20–28 W)*:
- **Capex kumulativ (3 Sites):** Platten €540 (recert) – €900 (neu) + Nodes €400 (BF=pve €0; FM/DW je Mini-PC ~€200) = **~€600–1.300**. Vergleich: *ein* resilienter Syno-Nachfolger für 10 TB = €1.000–1.600 capex **ohne** Geo-HA → Garage-3-Node gleich teuer/billiger **mit** HA.
- **Opex kumulativ:** ~€135–185/J Strom — aber **getrennt gerechnet:** FM/DW laufen über **fremde Zähler**, auf Christophs Zähler landet nur der **BF-Node ≈ €50–60/J** (Gegenleistung: er hostet deren Kopie, reziprok wie TP16).
- **JBOD-vs-RAID1-Ersparnis:** Strom bloß **€9–22/J** (Christophs Zähler) → *kein* Entscheidungsgrund; der Hebel ist **Capex ~€900** (nicht gekaufte Spiegelplatten) + architektonische Korrektheit. (Reicht nicht für ein BW-Upgrade €120–240/J, taugt aber als Seed für HW-Rückstellungen.)
- **Konzeptioneller Bruch (Syno-Nachfolger):** Kein *großes resilientes NAS* mehr nötig — die kollektive 3-Site-Redundanz *ist* die Garantie. **Syno-Rolle splittet:** Hot/SMB/VM-Disks bleiben lokal (Syno **oder** ZFS auf pve, klein/schnell); **Backup/Cold wandert in Garage über BF/FM/DW** → das 9-TB-Volume wird **aufgelöst** statt ersetzt (aus 1 SPOF werden 3 geo-redundante Kopien). Backup-Tool (restic/Kopia) sprechen S3 nativ.
- **Impacts auf Bestand (Kurz):** (a) **Storage-Redundanz-Philosophie kippt** — weg von SHR/RAID pro NAS, hin zu 3× JBOD; (b) **Backup-Kette** Hyper-Backup/SMB → S3-Bucket umstellen; (c) **Container-Host-System** (TP14) kriegt Garage als LXC/Container je Site, aber auf **rohen Platten**, nicht auf bestehenden SHR-Volumes; (d) **Upload-BW** wird Durchsatz-/Rebuild-Limit; (e) **HW-Planung** verzahnt mit TP15 (Node-HW, pve-Standort) und **TP24** (Stromversorgung je Site), FM/DW brauchen je 1 Node.
- **Startpfad (inkrementell/reversibel):** ① BF zuerst — Garage-Container auf pve, 2 HDDs JBOD, Backups lokal auf Bucket (sofort Nutzen, noch keine Redundanz). ② FM/DW nachziehen (Mini-PC + 2 Platten), RF→3, ab jetzt geo-redundant+HA. ③ Syno entlasten (Backup-Last abgeben).
- **Offen (Modell scharfstellen):** realer Strompreis; ob **FM/DW eigene Nodes stellen** oder Christoph HW hinstellt; Backup-Tool-Wahl (restic vs Kopia); ob heißes Zeug künftig auf ZFS/pve statt Syno.
- Abh.: **TP10** (WG-Multi-Site = Transport, Upload-BW-Constraint), **TP14** (Container-Hosts), **TP15** (Node-HW) / **TP24** (Stromversorgung), Muster von **TP16** (reziprokes Per-Host-HA). Kein Blocker für laufende TPs. SSoT dieser Session.
### TP19 — cudy-Teardown + VLAN1-Client-Retirement ▸ ✅ **GESCHLOSSEN (2026-08-18)** — `docs/tp19-vlan1-cudy-teardown-runbook.md`
- ✅ **Abschluss-Sweep 2026-08-18, alles live gegengeprueft (reine Reads):**
- **VLAN1 ist leer:** `/ip arp` auf `bridge` = **0 Eintraege, 0 reachable**. Nicht Restverkehr,
sondern gar nichts. Der Dual-DHCP-Race aus `project_vlan1_dhcp_incident` hat damit endgueltig
keine Quelle mehr.
- **Prometheus:** kein einziges `10.40.41.x`-Ziel mehr. Aktiv sind `localhost:9090`, n8n
(`.20.167:5678`), grafana (`.20.109:3000`); die uebrigen acht Jobs sind auskommentiert.
Der in der alten Restliste genannte Punkt ist damit **verifiziert** erledigt, nicht nur behauptet.
- **Interim-Kruecken sind weg:** `/interface bridge port` auf ap2 hat **keinen** Port mit
eingeschraenkten `frame-types` mehr — die TP3.1-Haertung `admit-only-vlan-tagged` ist beim
Rebuild verschwunden. Nichts abzuraeumen. `st-main` ist seit TP3.3 fester Templatebestandteil.
- **DNS-Statics** zeigen ausschliesslich auf `.99.x`/`.20.x`, kein `.41.x`.
- **cudy1:** `core ether11` ohne Link seit 2026-08-08 19:33, in Nautobot *Offline*. cudy2 tot.
- 🔜 **Was NICHT mehr TP19 ist** (bewusst dorthin verschoben, wo es hingehoert, statt den TP offen zu halten):
- **Der physische Ausbau von cudy1** — Hands-on-Aufgabe, blockiert nichts, kein Netzeffekt.
- **Die 4 toten CF-Tunnel** (`coolify`, `openclaw` …) — das sind Karteileichen in der
Cloudflare-Config, kein Netzthema → **TP16**.
- **HA-Portainer `setup_retry`** (`docker system df` auf ep14 braucht 33–120 s, struktureller
Plattformnachteil) → Monitoring-Thema, **TP11**. Aussichtsreichster Weg bleibt ein eigener
Portainer-Benutzer ohne Zugriff auf ep14.
- 📌 **Nebenbefund beim Abschluss, sofort behoben: der Standort hatte keine funktionierende Zeitquelle.**
ap1/ap2 zeigten per NTP auf `10.40.99.1`, wo **kein** Server lief (`status=waiting`), branch hatte
gar keinen eingetragen. **ap2 war dadurch 17 Stunden versetzt** — aufgefallen, weil ein Logeintrag
von 04:57 als `2026-08-17 12:19` gestempelt wurde. Der Core ist jetzt NTP-Server
(`routeros/tp19-ntp-core-als-zeitquelle.rsc`, inkl. der noetigen `input`-Regel — ohne die waere der
Server seit Etappe 4 eingeschaltet und trotzdem unerreichbar gewesen), branch ist Client
(`routeros/tp19-ntp-branch-client.rsc`).
- 🔎 **Die Wurzel lag eine Ebene tiefer:** auch der Core hatte **keinen** NTP-Client
(`enabled: no`, keine Server) bei `use-local-clock: no`. Ein Server ohne eigene Synchronisation
liefert Stratum 16, das jeder Client verwirft — die Anfragen kamen also an (die Firewall-Regel
zaehlte) und wurden trotzdem nicht brauchbar beantwortet. Der Standort hatte damit **an keiner
Stelle** eine funktionierende Zeitsynchronisation; dass drei von vier Uhren stimmten, war
Restglueck vom letzten manuellen Stellen. Behoben mit `routeros/tp19-ntp-core-client.rsc`
(`time.cloudflare.com,at.pool.ntp.org`) → Core `synchronized`.
- 🔴 **Und der letzte Schritt war der unauffaelligste: der Server hielt Stratum 16 fest.** Er war
eingeschaltet worden, **waehrend sein eigener Client noch unsynchronisiert war** — RouterOS
bewertet das nicht von selbst neu. Die Anfragen kamen an (die Firewall-Regel zaehlte mit),
wurden aber unbrauchbar beantwortet, und alle drei Clients blieben auf `waiting`.
**Ein `enabled=no` / `enabled=yes` nach der Synchronisation hat es geloest.**
Der entscheidende Test dorthin: branch einmal auf `at.pool.ntp.org` zeigen lassen — es
synchronisierte in 20 Sekunden. Damit war bewiesen, dass der Client heil ist und der Server
schweigt, statt weiter im Nebel zu stochern.
✅ **Endstand: alle vier Geraete `synchronized`.** (ap2s Uhr musste einmalig von Hand gestellt
werden — 17 h liegen ueber der NTP-Panic-Schwelle, so weit springt kein Client von selbst.)
- ✅ **Governance nachgezogen (2026-08-18):** NTP steht jetzt im **GC-Template** (Core als Quelle
mit oeffentlichem Upstream, branch/APs als Clients auf `10.40.99.1`), wird per **DHCP-Option 42**
(`ntp-server=` je Netz, Christophs Vorschlag) an **alle** Clients ausgeliefert — nicht nur an die
MikroTik-Flotte — und ist mit der neuen ComplianceRule **`system-ntp`** drift-ueberwacht.
Das war exakt der blinde Fleck, den **TP26** fuer `/ip firewall filter` geschlossen hat.
- ✅ **Live-Nachprüfung 2026-08-16 (MikroMCP + CF-API, reine Reads):**
- **`cudy1` ist tot, nicht nur „offline gemeldet":** `core ether11` steht auf `running=false`, **letzter Link-Down 2026-08-08 19:33**, seither kein Link. Die Formulierung „aktiver VLAN1-Leak" ist damit endgültig hinfällig — der **Dual-DHCP-Race hat keine Quelle mehr**. Offen bleibt nur der *physische* Ausbau + Inventar/Doku.
- **`crs326-core` hat keine VLAN1-IP mehr:** `/ip/address` listet nur noch die 7 SVIs + die WAN-DHCP-Adresse — die **Bring-up-Altlast `10.40.41.211` ist weg**. Damit fällt einer der 5 Compliance-Drifts aus TP22 raus, *sobald* wieder **Backup → Intended → Compliance** gelaufen ist (die Compliance vergleicht gegen das letzte Backup, nicht gegen das Gerät — s. TP22-Falle).
- ✅ **`crs326-branch`-Interim ERLEDIGT (2026-08-16):** `10.40.10.2` + SVI `vlan10` über einen GC-ConfigPlan entfernt (s. TP22). Mgmt läuft über `.99.2`; `/interface bridge vlan` blieb byte-identisch, es gibt also weder einen Traffic- noch einen Drift-Effekt.
- **`.41.133` identifiziert** (war TP19 B1.2 „unidentifiziertes VLAN1-Gerät"): es ist das **coolify-Origin** aus dem gleichnamigen, seit Monaten toten CF-Tunnel. Ebenso `.41.147/.148/.139` aus dem toten `openclaw`-Tunnel. Keine aktiven Geräte — **Karteileichen in der Cloudflare-Config**, nicht im Netz. Aufräumen = die 4 toten Tunnel löschen (user-gated, s. TP16-Nebenbefund).
- ✅ **VLAN1-Adressen abgeräumt (2026-08-15) — der Weg dorthin** — `docs/vlan1-rueckbau-howto-manuelle-schritte.md`:
- **pve `10.40.41.100` weg** (`/etc/network/interfaces`: `vmbr0` von `inet static` auf `inet manual`, Adresszeile raus; angewendet per `ip addr del` **ohne** `ifreload -a`, damit die Bridge nicht durchgetreten wird). Nachher nur noch `vmbr0.20`, keine `41.0/24`-Route, alle 9 Gäste weiter `running`, Storage aktiv. Backup `/root/interfaces.bak-tp19-20260815-183045`.
- **Core `10.40.41.211` weg** über Nautobot GC (`routeros/tp19-core-vlan1-address-remove.rsc`). Mgmt `.99.1` + `10.40.10.1` intakt. Compliance damals 17/17 grün — der TP22-Backlog-Punkt „`crs326-core 10.40.41.211`" entfällt. *(Zahl inzwischen überholt: nach TP16.1 sind es 72/72 Checks.)*
- **HA zeigt auf `10.40.20.100`** (User), **ioBroker** korrigiert (User).
- **Prometheus bereinigt.** Der pauschale `41`→`20`-Ersatz war eine Falle: nur `grafana` (`.20.109`) und `n8n` (`.20.167:5678`) liefern echte Metriken. `gitea` hat `/metrics` gar nicht aktiviert, `vaultwarden` gibt 404, `portainer /api/status` liefert JSON statt Prometheus-Format, `node`/`alertmanager`/`qdrant` sind exited, `pihole` ist abgewählt → **8 Jobs + `alerting:`-Block auskommentiert**, 3 aktive Ziele, alle `up`.
- 🔴 **NAS-Falle:** `prometheus.yml` gehört `65534:65534`, das Verzeichnis `adm.christoph` → `cat > datei` scheitert still („Permission denied"), während ein `cp` daneben klappt. **Schreiben per `sudo tee` + `md5sum`-Gegenprobe**, nie der Shell-Erfolgsmeldung trauen.
- ⏳ **OFFEN: HA-Portainer-Integration bleibt auf `setup_retry` — `docker system df` auf nas-bf ist zu langsam.** Aus dem HA-Debug-Log: `PortainerTimeoutError … GET /api/endpoints/14/docker/system/df` → `ConfigEntryNotReady`. Die Integration ruft `system df` für **jede** Umgebung bei **jedem** Update auf.
- **Container-Manager-Neustart am 2026-08-15 durchgeführt** (`/usr/syno/bin/synopkg stop|start ContainerManager` — Binary **nicht** im sudo-PATH; DSM meldet den Status danach weiterhin falsch als `stop`). **Alle 14 Container aus der Baseline zurück**, zusätzlich kam `hms-mqtt` hoch (Restart-Policy `always`, lief vorher nicht).
- ✅ **`hms-mqtt` stillgelegt (2026-08-16, User: „wird bald platt gemacht").** Es war bereits beendet; entscheidend war die **Restart-Policy `always` → `no`** (`docker update --restart=no`), sonst wäre es beim nächsten Daemon-Neustart wiedergekommen. Ein bloßes `docker stop` hätte hier nicht gehalten.
- ✅ Der **Hänger** ist damit weg: `system df` beendet sich wieder (vorher rc=124 nach Ausgabe der Tabelle). ❌ **Aber es bleibt viel zu langsam und schwankt: 33 s → 92 s → 3× >120 s.**
- 🔑 **Wo die Zeit hingeht (gemessen):** `images -q` 84 ms · `ps -aq` 95 ms · `volume ls -q` 65 ms · **`system df` 33–92+ s**. Also **vollständig in der Größenberechnung** (du-artiger Durchlauf der overlay2-/Volume-Verzeichnisse auf NAS-Platten) — ein struktureller Plattformnachteil, kein Defekt. Die Mengen sind harmlos (25 Images/13 GB, 10 Volumes/79 MB).
- **Aussichtsreichster Weg: eigener Portainer-Benutzer für HA ohne Zugriff auf ep14** → die Integration sieht die langsame Umgebung gar nicht. User-gated (Benutzer + Token anlegen). Alternativen: nichts tun (stört nichts) · Images aufräumen (6,98 GB reclaimable, Erfolg **offen**) · ep14 aus Portainer nehmen (schlechter Tausch).
- ⚠️ **Zwei widerlegte Zwischenthesen, nicht wieder aufgreifen:** die tote Umgebung `odroid-m1` war es **nicht** (entfernt, Symptom blieb, alle übrigen Endpoints ≤0,16 s); und es ist **kein** Größenproblem der Images.
- ✅ **Restliste aus dem TP5-Log-Lauf — ABGEARBEITET (Abnahme 2026-08-16: `out:bridge` = 0 über 10 min, Rate von ~166 auf ~34 Zeilen/min gefallen; im 43k-Zeilen-Log vom 08-17 kommt `out:bridge` nicht mehr vor).** Wert dieses Eintrags = die Diagnosemethode, nicht der Stand. Der Log-Lauf hatte gezeigt, dass **76 % allen Inter-VLAN-Verkehrs** aus VLAN20 nach VLAN1 (`out:bridge`) ging, obwohl dort **nichts mehr antwortete** (alle Ziele im ARP `failed`/`incomplete`). Verursacher waren Konfigurationen, die nie nachgezogen wurden:
- **`prometheus` (`10.40.20.151`, NAS)** scrapet 10 tote Ziele: `10.40.41.109:3000` · `.126:9443` · `.127:80` · `.147:5678` · `.149:3000` · `.152:3001` · `.153:9093` · `.154:9100` · `.159:6333` · `.23:80`.
- **`10.40.20.105`** zieht auf `10.40.41.100:8006` = **pve unter seiner alten VLAN1-Adresse** (real `10.40.20.100`).
- **`10.40.20.107`** auf `10.40.41.10:8080` (NAS alt).
- 📌 **Umzugsregel: das letzte Oktett bleibt gleich, nur `41`→`20`** — belegt für grafana (`.109`), vaultwarden (`.127`), Portainer (`.126`). Für `.23/.147/.149/.152/.153/.154/.159` existiert **kein** laufendes VLAN20-Gegenstück (anderswo oder tot; `alertmanager` ist seit TP14.1 als kaputt notiert) → diese Ziele gehören geprüft und dann aus Prometheus entfernt, nicht umgeschrieben.
- ⚠️ **Kein Firewall-/Portproblem** (2026-08-14 geprüft, nachdem beim Umstellen ein Timeout auftrat): `pveproxy` lauscht auf `*:8006`, `pve-firewall` ist *disabled*, keine iptables-Regel, kein `/etc/default/pveproxy`. `https://10.40.20.100:8006` antwortet aus VLAN10 **und** VLAN20 mit HTTP 200 in ~0,06 s. Wer hier einen Timeout sieht, sucht an der falschen Stelle — **und `timeout` gibt es auf macOS nicht**, ein `timeout bash -c '… /dev/tcp/…'`-Test meldet deshalb falsch-negativ.
- ⚠️ **Eskaliert 2026-07-15 (historisch — durch den WLAN-Rebuild 2026-08-08 gegenstandslos):** der unter TP3.1 dokumentierte VLAN1-Leak trat damals auch auf ap1/`dirtyvader-main` auf — die ap-seitige Interim-Härtung (TP3.1) milderte das Symptom, **die endgültige Lösung ist hier, TP19**: VLAN1 physisch weg = kein Leck mehr möglich.
- 🟢 **cudy-Stand (User 2026-07-16): cudy1 (`.41.246`) auf `10.40.10.246` umgehängt** — bewusst **kein Abbau-Ziel mehr**, dient als Standalone-WLAN-Sender während der Umstellungsphase (nicht Teil des Teardowns, extern vom User selbst umkonfiguriert). **cudy2 (`.41.247`) wird nicht mehr reaktiviert** — faktisch tot, ARP-Sweep bestätigt keine Präsenz mehr auf VLAN1. B2.2 („cudy1/cudy2 stromlos/Reset/ausbauen") ist damit **erledigt/gegenstandslos** (cudy2 aus, cudy1 schon migriert statt abgebaut).
- 🟢 **Live-Sweep 2026-07-16 (ap1, vor geplantem Teardown):** VLAN1 ist bereits viel weiter zurückgebaut als dokumentiert:
- `defconf`/pool-v1-DHCP-Server ist bereits **`disabled=yes`** (Export bestätigt) — B2.4 („defconf zurückziehen") ist **de facto schon erledigt**, Ursprung/Zeitpunkt dieser Änderung nicht aus dieser Session bekannt (nicht in dieser Doku protokolliert).
- ARP auf `bridge` (VLAN1): nur noch **2 „reachable"-Hosts** — `sw1` (`.41.11`, by design, bleibt bis MGMT-VLAN99) und **`pve`-Interim-Fallback (`.41.100`)** (B1.5-Aufräumziel, noch offen — Host-seitige Änderung auf pve, nicht Router). Alle anderen ~20 ARP-Einträge sind **stale/„failed"** (Cache-Reste längst migrierter Hosts wie macmini/.20.167, NAS/.20.10, grafana/.20.109 etc.), keine aktive Kommunikation mehr.
- **B1.1 MacBook (Claude-Sandbox-Client) bereits sicher migriert:** aktive DHCP-Leases nur noch auf `dhcp-v20` (`10.40.20.101`) und `dhcp-v10` (`10.40.10.187`), keine `defconf`-Lease mehr. ⚠️ Dual-Homed über 2 VLANs gleichzeitig (`project_dual_homing_slowness_incident`-Muster) — funktional ok, aber im Auge behalten.
- **B1.2 unidentifizierte Geräte `.41.133`/`.41.198`:** kein ARP-Eintrag mehr vorhanden — entweder migriert oder abgeklungen, gelten für den Teardown als **nicht mehr blockierend**.
- ⚠️ **B1.3 (Media→VLAN40) und B1.4 (saisonale Shellys) nicht in diesem Sweep verifiziert** — weiterhin offen, kurz vor dem finalen Cutover gegenchecken.
- **Backup-Stand vor Cutover (2026-07-16):** nur **ap1 gesichert** (Binary `bf-ap1-pre-vlan1-teardown-20260716.backup` auf dem Router + lokaler Export `routeros/state-backups/ap1-export-20260716-pre-vlan1-teardown.rsc`). **ap2 nicht gesichert, aber unkritisch:** bewusst ausgesteckt (User-bestätigt 2026-07-16, kein Ausfall) — kein Backup nötig, solange es aus bleibt. **sw1 nicht per Telnet gesichert** (`claudecode`-Login 2× deterministisch abgelehnt), aber **kein Blocker**: User macht sw1-Änderungen ohnehin direkt im UI/CLI selbst (effizienter als über den Claude-Runner, gemeinsame Entscheidung) — Telnet-Automation für sw1 ist hier kein Muss.
- **Ziel (Kapstein der Segmentierung, s. Zeile 27):** altes **cudy-WLAN weg** + **VLAN1 als Client-Netz stilllegen** (`defconf`/pool-v1-Client-DHCP zurückziehen, ✅ bereits disabled). **Nicht** VLAN1 komplett: Switch-/AP-Mgmt (sw1 `.11`/sw2 `.12`/ap2 `.2`) bleiben **by design** in VLAN1 (statisch, kein DHCP nötig) bis zum separaten **MGMT-VLAN99-Cutover** (eigener TP). ~~cudy1/cudy2 = `.41.246`/`.247` (Teardown-Ziele)~~ — cudy1 migriert auf `.10.246` (Reuse statt Abbau), cudy2 tot/nicht reaktiviert (s.o.).
- 🎯 **User-Gate (Update 2026-07-14): Shelly ist wieder da (kein Blocker mehr) — SwitchBot ist KEIN Prereq mehr, funktioniert laut User erst NACH VLAN1-Retirement (Reihenfolge ggü. 2026-07-13 umgedreht) → wandert in die Teardown-Nachlese.** TP19 damit direkt startbereit. Twinkly nebenher (saisonal).
- **Verlorene Shelly — IDENTIFIZIERT via HA 2026-07-13** (User-Hinweis „du siehst ihn im HA"): HA-Device `shellyplugsg3-e4b063e41ab8`, **Modell = Shelly Plug S Gen3**, **MAC `e4:b0:63:e4:1a:b8`**, HA-device_id `7a21e8f3349e6660f8b35974fc989a7b`. **NICHT im 26er-Inventar** (Spare/Küchen-Steckdose, die Christoph beim Einrichten verbockt hat; er nannte sie „küche s20", aber s20=`98:a3:16:7c:5f:ec` läuft sauber auf `.30.206` — Verwechslung). **KEIN AP-Mode, nicht tot:** assoziiert am **Altnetz `elektrosmog.c`** (cudy → untagged VLAN1-L2), aber mit **bogus-IP `10.95.10.206`** (Fremd-Subnetz, User-Fehler) → unrouting-bar, daher weder in VLAN1/30-Sweep noch normal per IP erreichbar, aber **per MAC in HA/WiFi-Table sichtbar**. Der `.219/s12`-Verdacht war Sackgasse (s12 nur saisonal aus).
- **Update 2026-07-14: Shelly wieder da** (User-Meldung) — Live-Status (noch bogus-IP `10.95.10.206` oder inzwischen normal erreichbar?) **zu Sessionbeginn per HA/ARP verifizieren**; Recovery-Plan unten ggf. obsolet, falls sich das Gerät selbst erholt hat.
- **Recovery-Plan (falls beim Verify noch nötig):** ap1 bekommt **temporäre Zweit-IP `10.95.10.1/24` auf der Bridge** → `10.95.10.206` per L2 erreichbar → **ein** `WiFi.SetConfig {sta: ssid=dirtyvader-iot, pass=IOT-PSK, static, ip=10.40.30.}` + `Shelly.Reboot` → Gerät kommt auf VLAN30 hoch → temp-IP entfernen. Danach `.30.x`-Reservierung + Inventar (MAC `e4:b0:63:e4:1a:b8`) + eco-Policy. Umgeht die „blutige Nase" (IP am Gerät ändern während man drauf hängt = Verbindungsverlust) — vom Router aus in einem Rutsch. Christophs manueller Direkt-Versuch (AP-IP auf 10.40.30 „verdrehen") war genau deshalb gescheitert.
- ✅ **SwitchBot floor-lamp** `.41.184` (`b8:f8:62:09:f2:04`) → **erfolgreich auf VLAN30 übersiedelt (2026-07-16, Christoph via App/BLE)**, Res. `.30.227`.
- **Prereqs-Status (live nachgezogen 2026-07-16, s. Live-Sweep oben):**
1. ✅ **Christophs Endgeräte → VLAN10/20 erledigt:** MacBook-Air (`c0:c7:db:de:71:be`, Claude-Sandbox-Client) hat aktive Leases nur noch auf `dhcp-v10`/`dhcp-v20`, keine `defconf`-Lease mehr. iPhone `.41.186` nicht im aktuellen Sweep erneut geprüft (zuletzt off/asleep) — kurz vor Cutover gegenchecken.
2. ✅ **2 unidentifizierte VLAN1-Geräte (`.41.133`/`.41.198`) aus dem ARP verschwunden** — kein aktueller Eintrag mehr, gelten als nicht mehr blockierend (entweder migriert oder abgeklungen).
3. ⏳ **Media → VLAN40 weiterhin offen:** Apple TV 4K + Philips TV nicht in diesem Sweep verifiziert.
4. ⏳ **Saisonale Shellys s1/s2/s3/s12 weiterhin offen** — nicht blockierend, kann übersprungen werden (Re-Onboarding-Aufwand akzeptiert).
5. ⏳ **pve-Interim-IP `.41.100` weiterhin aktiv/reachable** — Host-seitiger Cleanup auf pve selbst noch offen (nicht Router-Config).
- **Teardown-Sequenz (angepasst an den Live-Stand):** VLAN1-Client-Sweep ist **schon fast leer** (nur sw1-Mgmt + pve-Interim reachable) → `defconf`/pool-v1 ist **bereits disabled** → cudy-Abbau **entfällt** (cudy1 umgenutzt statt abgebaut, cudy2 tot) → ✅ **SwitchBot + Twinkly erledigt (2026-07-16)** → **verbleibt:** Media-Check (B1.3), pve-`.41.100` abräumen (B1.5), Interim-Krücken bewerten (ap2 `frame-types=admit-only-vlan-tagged`, `st-main rrm/wnm`), Inventar/`ip-plan`/DNS `.41→Ziel` final.
- Abh.: **TP14 ✅** (Hosts raus aus VLAN1), **TP17 ✅** (Shellys raus). Verwandt: `project_vlan1_dhcp_incident` (cudy-Dual-DHCP-Leak), TP5 (Deny-by-default danach sauberer). SSoT hier + `docs/tp19-vlan1-cudy-teardown-runbook.md`. Folge: **TP19.1**.
#### TP19.1 — VLAN99-MGMT-Cutover (sw1/sw2/ap2-Mgmt VLAN1 → VLAN99) ▸ ✅ ERLEDIGT (2026-08-14 nachgezogen) — alle lebenden Geräte auf VLAN99, die beiden Rest-Punkte gegenstandslos
- ✅ **Abschluss-Nachtrag 2026-08-14 (Status-Korrektur, kein neuer Change):** der Header stand noch auf „ap2 vertagt, sw1 offen" (Stand 2026-07-16) — beides ist durch spätere TPs eingelöst bzw. gegenstandslos geworden, ohne dass es hier nachgetragen wurde. Ist-Stand: **core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`** — jedes lebende Netzgerät hat sein Mgmt auf VLAN99. **sw1/sw2 existieren nicht mehr** (physisch ausgebaut, in Nautobot per TP22 Schritt 5 auf *Decommissioned*). Damit ist TP19.1 vollständig; offen bleibt nur der VLAN1-**Client**-Teil, und der gehört zu TP19 (Hauptteil), nicht hierher.
- **Scope:** die einzigen Dinge, die TP19s Hauptteil bewusst in VLAN1 belässt — sw1 (`.41.11`), sw2, ap2-Mgmt (`.41.2`) — auf VLAN 99 (MGMT, `10.40.99.0/24`) umziehen. Erst danach ist VLAN1 wirklich **komplett** weg, nicht nur als Client-Netz.
- **User-Entscheid (2026-07-16):** kein eigenständiges TP-Nummer — bewusst als **Unterpunkt von TP19** geführt („VLAN1 killen"), nicht als TP22 o.ä.
- ✅ **ap1-Teil erledigt (2026-07-16):** ap1 brauchte keinen funktionalen Umzug (vlan99-Interface/GW `.99.1` läuft schon seit TP4) — nur der kosmetische DNS-Static-Nachtrag `ap1.bf.lan` `10.40.41.1`→`10.40.99.1` war offen, per MikroMCP erledigt (dry-run→apply, `manage_dns_entry`).
- ✅ **sw2-Teil erledigt (2026-07-16, Christoph selbst über die Klickanleitung):** sw2 jetzt auf **`10.40.99.12`** (statisch, planmäßiger Zielwert aus `ip-plan.md`) — per Ping verifiziert. Zwischenzeitlich war das Gerät kurz auf einer *dynamischen* `10.40.99.200`-Lease aufgetaucht (unklare Ursache, s. Memory `project_tp20_tp21_fasttrack_20260716`), jetzt aber sauber auf der geplanten statischen Adresse. `CLAUDE.md`/`ip-plan.md` nachgezogen.
- ✅ **ap2-Teil erledigt (2026-08-08, im TP3-Rebuild) — die Vertagung wurde wie geplant eingelöst.** ap2-Mgmt liegt auf **`10.40.99.12`** (VLAN99), gesetzt bei der Standalone-Neuprovisionierung, nicht isoliert von Hand; `primary_ip4` in Nautobot per TP22 Schritt 5 nachgezogen. Der `ap2.bf.lan`-DNS-Static wurde im TP22-Pilot-Deploy (2026-08-13, GC-ConfigPlan) von `10.40.41.2` auf `.99.12` gezogen. Ursprüngliche Vertagungs-Begründung zur Historie:
- 🔜 **~~ap2-Teil bewusst vertagt~~ (User-Entscheid 2026-07-16): erst NACH Nautobot (TP21).** Nautobot übernimmt dann in einem Rutsch ap1 + ap2 + die neuen CRS326-Switches (TP15) als config-as-code-Fleet — der ap2-Mgmt-VLAN-Umzug wird also nicht mehr isoliert von Hand gemacht, sondern Teil der Nautobot-Erstadoption. `ap2.bf.lan`-DNS-Static bleibt bewusst auf `10.40.41.2` bis dahin.
- 🔒 **TP19.1 war die letzte Ausnahme vom ap1/ap2-Config-Freeze — der jetzt (2026-07-16) zur generellen Regel ausgeweitet wurde** (s. Governance-Entscheid oben): **jede** Gerätekonfiguration künftig ausschließlich über Nautobot, keine direkten Änderungen mehr an einzelnen im Netz verstreuten Geräten. Der ap1-DNS-Fix oben war die letzte direkte MikroMCP-Write-Aktion unter der alten (engeren) Regel.
- ⚪ **sw1-Teil GEGENSTANDSLOS (2026-08-14 festgestellt):** Christoph hat sich für „auf CRS326 warten" entschieden — sw1 wurde am **2026-08-08 durch `crs326-core` ersetzt und physisch ausgebaut** (Nautobot: *Decommissioned*, TP22 Schritt 5). Ein Mgmt-VLAN-Umzug für ein nicht mehr existierendes Gerät entfällt ersatzlos. **`switch/ui-guides/02-vlan99-mgmt-cutover.md` ist damit historisch** (nie ausgeführt, nicht mehr ausführbar — der Telnet-Runner `switch/` insgesamt nur noch Archiv). Ursprünglicher Stand zur Historie: Klickanleitung fertig, Ausführung on-demand (User-Entscheid 2026-07-16), nicht blockierend.
- **Bereits vorbereitet:** `docs/ip-plan.md` Phase 2 hat schon einen Entwurf (ap2→`10.40.99.2`, sw1→`10.40.99.11`, „Lockout-Risiko, sorgsam", sw1 zuletzt).
- **Abh.:** TP19-Hauptteil (VLAN1-Client-Retirement) — logisch danach, da VLAN99 als eigenes Mgmt-Netz erst dann wirklich nötig wird, wenn VLAN1 sonst leer ist. sw1-Teil braucht sw1-Telnet-Zugriff (User macht sw1-Changes direkt, s. Feedback-Memory) — Lockout-Risiko am größten bei sw1 (einziger Netzpfad zu sw2 hängt am Uplink).
### TP20 — Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) ▸ ✅ **Plattform live (2026-07-16)** — Nautobot-DB angelegt, vw-Migration (TP20.1) offen — Memory `project_mikrotik_commit_nautobot_gc`
- ✅ **Platform-Postgres live (2026-07-16):** LXC 105 auf pve, `platform-pg.bf.lan` / `10.40.20.20`, Ubuntu 24.04 (native PG16-Paket, kein PGDG-Repo nötig — bewusste Abweichung von der sonstigen Debian/community-scripts-Konvention der übrigen pve-LXCs, aus PG16-Verfügbarkeitsgründen). Cluster mit `--locale en_US.UTF-8 -e UTF8` neu initialisiert (Erstinstallation lief in `C`/`SQL_ASCII`, für Nautobot ungeeignet, korrigiert). `wal_level=replica` gesetzt; **`archive_mode` bewusst noch NICHT an** — ohne pgBackRest/Garage-S3 (TP18) zum Prunen würde archivierte WAL unbegrenzt wachsen und die kleine 8GB-Disk füllen; wird nachgezogen sobald TP18 steht. Rolle+DB `nautobot` angelegt, `pg_hba.conf` erlaubt `scram-sha-256` aus `10.40.20.0/24`. End-to-End-Verbindungstest (Netzwerk, nicht nur localhost) erfolgreich. Creds in `postgres/.secrets` (gitignored, `chmod 600`).
- **Auslöser:** Mehrere Dienste brauchen zentrale DB (Nautobot-SoT [TP21], Vaultwarden, HA-Recorder). Grundsatzentscheid statt Nautobot-Insellösung — „wir skalieren schneller als wir schauen können" (User).
- ⚠️ **Abh.-Korrektur (User 2026-07-16): TP18 (Garage-S3) ist KEINE Vorbedingung mehr — „hat mit dem Netz nichts zu tun", transitiv aus der TP21-Kette entfernt.** pgBackRest→Garage-S3 (s.u.) wird **nachgezogen sobald Garage existiert**, blockiert aber nicht mehr den Start. `wal_level=replica`/`archive_mode` können mit einem Platzhalter/lokalen Archive-Ziel hochgezogen werden, produktives Off-Site-PITR kommt später.
- ⚠️ **Komodo-Abh. (s. TP2/Memory `project_komodo_tp_pending`) vorerst zurückgestellt:** Komodo selbst existiert noch nicht als laufendes System — kann also nicht „darüber" deployt werden. **Entscheidung (Claude, 2026-07-16, transparent statt stillschweigend): Postgres-LXC + Nautobot-Stack werden jetzt direkt gebaut (LXC/Docker-Compose), Komodo adoptiert sie später** — gleiches Muster wie bei den CRS326 (Hardware/Stack zuerst lauffähig, Management-Layer zieht nach). Sag Bescheid, falls das nicht gewünscht ist.
- 🔍 **Ressourcen-Check 2026-07-16 (live geprüft, SSH):**
- **pve** (10.40.20.100): 4 vCPU, **15 GB RAM total, ~12 GB belegt, nur ~2-3 GB frei, 3 GB bereits im Swap** — eng! Größter Verbraucher: HAOS-VM (`haos`, vmid 100) mit ~8,2 GB aktiv von 10 GB Limit. Disk unkritisch (94 GB Root, 85 GB frei).
- **Mac mini** (10.40.20.167): 10 CPU, 24 GB RAM, **nur ~1,9 GB "wired" (nicht reklamierbar)** — deutlich mehr echter Headroom als pve trotz "23G used"-Anzeige (macOS-Cache-Artefakt). Docker läuft bereits dort (n8n, n8n-redis, cloudflared×2, gitea-mcp, portainer_agent).
- NAS nicht geprüft (kein funktionierender SSH-Zugriff von hier aus mit den vorhandenen Keys/User) — für diese beiden Stacks laut TP20/TP21-Plan ohnehin nicht Zielhost.
- **✅ Entschieden (2026-07-16): Postgres-LXC auf pve, Nautobot-App-Stack auf NAS** (nicht Mac mini) — NAS hat mehr Headroom (~12 GB verfügbar) und hostet bereits **Gitea** (Nautobots Git-Backend für Golden-Config-Templates), kürzere Wege als Mac mini. Deploy-Weg: **Portainer-Stack auf ep14**, analog dem bestehenden Muster (`docs/nas-vlan20/stacks/*.yml`), Basis = offizielles `nautobot-docker-compose` (github.com/nautobot/nautobot-docker-compose), nicht von Null gebaut. Wird als **separate Session** angestoßen (parallel zum Postgres-LXC-Aufbau auf pve).
- **Nachtrag (2026-07-16, nach Aufräumen): pve hat sich entspannt** — nach Abschalten mehrerer mittelfristig eh stillzulegender Services (User) jetzt **6,7 GB frei, 0 Swap** (vorher 2-3 GB frei, 3 GB Swap). Postgres-LXC passt entspannt.
- **RAM-Upgrade-Idee (32GB-Stick) zurückgestellt (User 2026-07-16):** Intel N100 (pve-CPU) ist offiziell auf max. 16 GB spezifiziert + Single-Channel; ein dokumentierter Fall mit demselben Corsair-Modul auf vergleichbarer N100-Hardware endete in Memtest86-Fehlern, stabil erst nach Rückbau auf 8GB. Empfehlung: 16GB als Decke für diese Kiste behandeln statt auf einen Settings-Fix zu hoffen. **User rechnet ohnehin mittelfristig mit einem HW-Tausch für pve** — gehört ggf. in TP15s HW-Refresh-Scope, noch nicht formal aufgenommen.
- **✅ 2-Tier (User 2026-07-14):**
- **Tier-1 „Platform-Postgres" (precious):** EIN Postgres-Cluster, getrennte DBs für **Nautobot** (hier provisioniert) **+ Vaultwarden** (Onboarding = **TP20.1**). **PG 16** (Nautobot-Matrix-safe; **Nautobot unterstützt kein MariaDB**, nur PG/MySQL-8 → Postgres ist gesetzt, nicht optional).
- **Tier-2 (Bestand):** **HA-Recorder bleibt MariaDB** (Write-Churn, andere Zuverlässigkeitsklasse, single-site). *(Vaultwarden liegt heute ebenfalls auf MariaDB → wandert per TP20.1 nach Tier-1.)* Engine-Split fällt sauber entlang der Tier-Grenze.
- **✅ Single-Site-first (User 2026-07-14):** jetzt nur BF-Primary („gehostet wird vorerst auf der bf-site", 2026-07-16 reconfirmed). Nimmt das **2-Site-Quorum/Split-Brain**-Problem (bräuchte 3. Witness) vom Tisch. **ABER replication-ready ab Tag 1:** `wal_level=replica`, `archive_mode=on` (Ziel vorerst lokal/Platzhalter, s.o.) — späterer Standby = `pg_basebackup`, **kein** Primary-Umbau/Restart. pgBackRest→Garage-S3 folgt nach TP18, nicht mehr Blocker.
- **Substrat:** Postgres in **LXC auf pve** (DB-Klasse, nicht Docker).
- **Scope TP20 = Plattform:** Cluster hoch, Tiering, Backup/PITR, **Nautobot-DB** anlegen. **vw-Migration + Cross-Site-HA = TP20.1** (ausgelagert).
- **Offene Sub-Steps:** PG-Version final (16 vs 17); Rollen/DB-Anlage Nautobot; pgBackRest-Bucket in Garage folgt **nach** TP18 (nicht mehr Blocker, s.o.).
- ⚠️ **Komodo-Abh. (User 2026-07-16) vorerst zurückgestellt, s.o.:** ursprünglich als Blocker gedacht (TP20 + neue Softwarepakete über Komodo statt direkt/Portainer), aber Komodo existiert noch nicht lauffähig und hat keine eigene TP-Nummer (nur unter TP2/TP11 erwähnt) → **entkoppelt, um die Montag-Deadline zu halten.** Komodo-TP-Nummer weiterhin zu klären, aber nicht mehr auf dem kritischen Pfad für TP20-Start.
- Abh.: **keine harten Blocker mehr** (TP18 entfernt, Komodo entkoppelt). **Blockt weiterhin TP21 + TP20.1.**
#### TP20.1 — Vaultwarden → Platform-Postgres + Cross-Site-HA ▸ 🔜 KONZEPT (2026-07-14, ausgelagert aus TP20)
- **Ziel:** `vw` (`.20.127`, heute MariaDB) auf den Precious-Tier holen **und** das eigentliche User-Ziel erfüllen: **DB-Replikation über 2 Sites** (Standort-Verlust überlebbar).
- **Phase 1 — Migration (single-site):** vw **MariaDB→Postgres** (Export → Import in Tier-1-DB `vaultwarden` → `DATABASE_URL` umstellen → verifizieren). Danach ist vw precious + im pgBackRest/PITR-Schirm von TP20. *(Alternative verworfen: vw auf MariaDB lassen + MariaDB-Repl — zugunsten EINER Replikations-/Backup-Mechanik im Precious-Tier.)*
- **Phase 2 — Cross-Site-Standby:** `db-platform-standby`-LXC am 2. Standort → `pg_basebackup`/Streaming-Replication über **WG (TP10)** → **manuelles Promote-Runbook**. Repliziert den ganzen Tier-1-Cluster (vw = HA-Ziel, Nautobot bekommt Gratis-DR-Kopie). **Patroni + 3. Witness** nur, falls später Auto-Failover statt manuellem Promote gewünscht.
- **Verwandt/Abgrenzung:** vw ist auch in **TP16** offen (CF-Access: `bypass`→WARP-geschützt) — **DB-HA ≠ Access**, getrennt behandeln. Reziprokes 2-Site-Muster analog **TP16**/**TP18**.
- Abh.: **TP20** (Tier-1 Postgres muss stehen), **TP10** (WG-Transport für Phase-2), **TP18** (Backup-Schirm). Kein Blocker für TP21.
### TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ 🟢 **Plattform + Datenmodell + Templates live**
- 🔴 **BETRIEBSBEFUND 2026-08-18: der Stack hat sich zweimal in einer Nacht selbst umgebracht — Ursache
war der Image-Healthcheck, nicht Celery.** `nautobot-worker` und `-scheduler` erben aus dem Image
einen `HEALTHCHECK`, der alle **10 s** `nautobot-server health_check` startet — einen kompletten
Django-Bootstrap (~250 MB). Unter Last dauert ein Lauf **>2 min**, die Aufrufe stapeln sich also
unbegrenzt (**neun gleichzeitig gemessen**, aeltester 2:16 alt). Ergebnis: Speicher alle →
`proc_fork.c:189 Failed to fork(). errno=[12/Cannot allocate memory]` im NAS-Syslog um 23:48:47 →
eine Minute spaeter raeumt DSM den Stack ab (sauberes `docker stop`, daher ExitCode 0 bzw. 137
nach Grace — **kein** Kernel-OOM, `OOMKilled=false`). Dasselbe Muster schon um 19:35.
- **Behoben im Stack** (`docs/nas-vlan20/stacks/210-nautobot.yml`, Portainer-Stack 203):
`healthcheck: disable: true` auf worker+scheduler, `--concurrency=2 --max-tasks-per-child=100`,
`mem_limit` je Container, Web-Healthcheck-Timeout 5s → 15s. **Load fiel von 73,5 auf 5,8.**
- ⚠️ **`cpus:` geht auf Synology NICHT** — der Kernel hat keine CFS-Quota, der Stack-Deploy bricht
mit `NanoCPUs can not be set` ab. Nur `mem_limit` verwenden.
- 📌 **Nichts haengt an dieser Health:** `restart=unless-stopped` startet bei *Exit* neu, nicht bei
*unhealthy*, und kein `depends_on` wartet auf den Worker. Der Web-Container behaelt seinen
billigen curl-Check — das ist das Signal, das `service_healthy` nutzt.
- 🎯 **Strukturell bleibt es eng:** Nautobot teilt sich die DS923+ (4 Kerne) mit einer VMM-VM,
Active Backup und ~14 weiteren Containern. Der Stack bleibt vorerst dort (User-Entscheid
2026-08-18); **ein groesserer Server ist gedanklich bereits vorgemerkt**.
- 🔴 **Die Kehrseite des Re-Baselines, am eigenen Leib gelernt (2026-08-18):** `ingest-core-firewall.py`
erklärt den **Ist-Stand zum Soll** — es prüft nicht, ob der Ist-Stand sinnvoll ist. Beim Lauf nach
TP5 E3/E4 ist damit die **kaputte Fasttrack-Reihenfolge** des Core als Absicht in den Config-Context
gewandert (s. TP6). Der Warnhinweis im Skriptkopf („nur nach einer bewusst durchgeführten,
verifizierten Änderung laufen lassen") ist also **notwendig, aber nicht hinreichend**: die Änderung
war bewusst, die *Umgebung*, die mitgeschrieben wurde, hat aber niemand angesehen.
**Merkregel: nach dem ersten Re-Baseline einer Sektion deren Diff einmal ganz lesen**, nicht nur die
Zeilen, die man selbst angefasst hat.
- ✅ **Nachtrag 2026-08-19: der konkrete Fall ist aufgelöst** — Christoph hat entschieden, die
Fasttrack-Reihenfolge **so zu lassen** (Begründung in TP6). Der Eintrag in der SoT ist damit
nachträglich korrekt. **Die Merkregel bleibt trotzdem gültig:** dass ein versehentlich
mitgeschriebener Zustand sich hinterher als brauchbar herausstellt, ist Glück, kein Verfahren.
- 🔧 **Zwei echte Template-Fehler, beim Re-Baseline 2026-08-18 gefunden** (beide gepusht):
- `/ip service set … **address=**` → RouterOS 7.23/7.24 exportiert **`available-from=`**. Das
erzeugte vier Dauer-Drifts (ap1/ap2/branch/core), bei denen das **Geraet** recht hatte und das
Soll falsch war — und ein Push haette auf 7.24 auflaufen koennen.
- `hw-offload` gehoert in die SKIP-Liste von `ingest-core-firewall.py`: die REST-API meldet es auf
der defconf-fasttrack-Regel, `/export` schreibt es nicht → ewige Falsch-Drift auf genau einer Regel.
- Ergebnis: **71/76 compliant**; die verbleibenden 5 sind der TP28-Rest (`wifi-*` auf dem Core). (App 2026-07-22, Basisdaten + Git-Backend + L3-Core-Template 2026-08-05, ap1/ap2 As-built 2026-08-08); **Device-Adoption/Compliance-Push ausgelagert → TP22** — Memory `project_mikrotik_commit_nautobot_gc`
- **Ziel:** modellierter Control-Plane über den MikroTik-**RouterOS**-Kern: GUI-SoT + **Config-Compliance/Drift** + intended-config + Push. **Ergänzt** MikroMCP (interaktiv/Debug), **ersetzt es nicht** (deklarativ/DR/Compliance). Aus User-Recherche 2026-07-14: MikroTik in Golden Config **erstklassig** (`mikrotik_routeros` + `_api`, Nornir/NAPALM, backup+deploy).
- **Scope-Grenze (Fassung 2026-08-16, nachgezogen):** RouterOS in-model = **`crs326-core` (L3-Router) + `crs326-branch` (L2) + ap1/ap2 (reine APs)** — alle 4 Geräte sind seit TP22 adoptiert, Golden Config deckt damit den **ganzen Fabric inkl. WLAN**. Es bleibt **nichts** außerhalb: der frühere Vorbehalt „außerhalb bleibt Omada-WLAN" ist gegenstandslos, seit der Omada-Stack gelöscht (2026-07-16) und CAPsMAN abgeschafft (2026-08-08) ist.
> *Vorherige Fassung (überholt): „RouterOS in-model = Router (ap1/ap2, dauerhaft — kein RB5009) + beide CRS326-Switches (RouterOS-Boot) → Golden Config deckt den ganzen Kabel-Fabric. Außerhalb bleibt nur Omada-WLAN (eigener Controller, spielt aber laut TP15-Korrektur ohnehin keine Rolle mehr)."*
- 🔒 **Wird zum alleinigen Config-Weg für Netzgeräte (Governance-Entscheid 2026-07-16, s. oben):** ab hier keine ad-hoc MikroMCP-Writes/Switch-UI-Klicks mehr auf Dauer — Nautobot managed den Fleet.
- ✅ **Nautobot-Stack live (2026-07-16), auf NAS statt Mac mini** (Ressourcen-Check hatte Mac mini vorgeschlagen, dann auf NAS umentschieden — mehr Headroom + Gitea-Co-Location, s. TP20-Log): Portainer-Stack `nautobot` (Id 203, ep14), 4 Container — `nautobot` (App/API, `10.40.20.30:8080`), `nautobot-worker` (Celery), `nautobot-scheduler` (Celery Beat), `nautobot-redis`. Bild `ghcr.io/nautobot/nautobot:3.1.7` (offizielles Image direkt, **nicht** das `nautobot-docker-compose`-Repo — das ist ein `invoke`/Poetry-getriebenes Dev-Tooling-Repo ohne eigenständige `docker-compose.yml`, für einen Portainer-String-Stack ungeeignet; bewusste Abweichung, dokumentiert statt stillschweigend). DB = Platform-Postgres (`10.40.20.20`, TP20). Compose-Datei `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored, wie alle NAS-Stack-Files).
- 🐛 **Gotcha beim ersten Deploy:** alle 3 Nautobot-Container liefen initial gleichzeitig `migrate` (Standard-Entrypoint-Verhalten) → Race-Condition, `IntegrityError` auf `pg_type`. Fix: `nautobot`-Service kriegt einen `healthcheck` (`curl .../health/`), `nautobot-worker`/`nautobot-scheduler` starten erst bei `depends_on: nautobot: condition: service_healthy` — serialisiert die Migration sauber auf einen Container. DB dafür einmal `DROP`+`CREATE` (sauberer Neustart, kein Datenverlust — war eh leer).
- ✅ **End-to-End verifiziert:** `/api/status/` mit Token 200, Nautobot-Version 3.1.7, alle 4 Container laufen. Superuser `admin` angelegt, Creds im Stack-File (gitignored) + `postgres/.secrets` (DB-Seite).
- ⚠️ **Bekannt offen, nicht blockierend:** kein HTTPS/Reverse-Proxy vor Nautobot (Django-Security-Warnings beim Start, erwartet für internen HTTP-only-Betrieb auf VLAN20 — analog anderen internen Diensten); Celery-Worker-Registrierung beim allerersten Start nicht sofort sichtbar (`celery-workers-running:0` initial), unkritisch, braucht kurz zum Einpendeln.
- **PoC-Pfad (angepasst an die Montag-Deadline):** ~~Nautobot-Stack hoch~~ ✅ → **ap1 + ap2 in einem Rutsch adoptieren** (ap2 bekommt dabei gleich seinen VLAN99-Mgmt-Umzug aus TP19.1, s.o. — nicht isoliert vorher) → sobald CRS326 verkabelt sind, **direkt als Nautobot-verwaltete Geräte adoptieren statt erst manuell konfigurieren** → Compliance/Drift + Templates/intended für VLAN-/Firewall-Standards danach.
- ✅ **Basisdaten-Ingest durchgeführt (2026-07-22)** — Ist-Zustand, nicht Zielzustand (User-Entscheid: „wir gehen vom status quo aus"). Quelle = ap1 live via MikroMCP. Skript-Muster idempotent (get-or-create + Delta-PATCH).
- **Locations:** neuer LocationType `Bereich` unter `Site`; `BF → Technik UG` (crs326-core, crs326-branch, ap2) und `BF → Wohnzimmer Sideboard` (ap1).
- **Devices:** ap1 = Rolle *Router* (Ist **am 2026-07-22** — ⚠️ seit der Adoption 2026-08-13 ist ap1 in Nautobot *Wireless AP* und `crs326-core` der *Router*, s. TP22), ap2 = neue Rolle *Wireless AP* + Status **Offline**, beide CRS326 **Platform RouterOS nachgetragen** (fehlte → hätte jeden GC-Job auf ihnen verhindert) + Werks-MAC/software-id in `comments`. Neu als Übergangs-Devices **ohne Platform** (nie GC-fähig, bewusst): `sw1` (ALLNET), `sw2` (ES208G), `cudy1`.
- **IPAM:** VLAN-Group `BF` + **9 VLANs** (1 *Deprecated*, 10/20/30/40/50/90/99/100 aktiv) + **8 Prefixes**, jeweils an ihr VLAN gebunden. VLAN100/WAN bewusst ohne Prefix (öffentliches Liwest-/24, gehört uns nicht).
- **ap1-Interfaces (26):** ether1-5, bridge, lo, wg-hub, vlan10-100, wifi1/wifi2, 7 VAPs — inkl. MACs, `ether2` als Trunk (untagged VLAN1 + tagged 10-100), VAPs als Access-Ports auf ihrem VLAN. 9 Gateway-IPs an ihren Interfaces. Platzhalter-Interface `mgmt` auf ap1 gelöscht (existiert real nicht).
- **WLAN:** logischer Controller `bf-wlan` (**kein** CAPsMAN — `controller_device` bewusst leer, die Control-Plane *ist* Nautobot GC) + Gruppe `BF-WLAN-APs` (ap1+ap2) + **6 WirelessNetworks mit VLAN-Bindung** + 2 RadioProfiles (2,4 GHz ch1 / 5 GHz ch36, Domain AT).
- 🐞 **API-Gotcha:** `tagged_vlans` wird vom REST-Serializer in Detail-/List-Views als `null` zurückgegeben, **obwohl korrekt gespeichert** — Verifikation nur über **GraphQL** verlässlich. Kostet sonst eine Stunde Fehlersuche an einem Nicht-Problem. Ebenso: Relationen kommen per Default ohne `display` (`?depth=1` nötig), und `VLANGroup`/`Prefix`/`VLAN` brauchen passende `content_types` am LocationType, sonst 400.
- ✅ **CRS326-Core L3-Device-Modell + Ports angelegt (2026-08-05):** `crs326-core` jetzt Rolle *Router*, alle VLAN-SVIs (`vlan10/20/30/40/50/90/99/100`) + Gateway-IPs `.1` (außer vlan100) in Nautobot; Portbelegung via `nautobot/ingest-crs326-ports.py` aktualisiert. `crs326-branch` bleibt *Switch* mit Mgmt-IP `.99.13` (Zielrenumber `.12` im Cutover-Fenster).
- ⚠️ **Offen/nachzutragen:** exaktes Modell von sw1 + cudy1 (aktuell `… (Modell TBD)`); Standort von sw1/sw2/cudy1 (hängen an `BF` statt an einem Bereich); ~~ap2s `primary_ip4` zeigt noch auf das tote `10.40.41.2`~~ → ✅ **erledigt (TP22 Schritt 5): `10.40.99.12`.**
- ✅ **Git-Backend an Gitea angebunden (2026-08-05):** Repository `bf-golden-config` (`jinja_templates/mikrotik_routeros.j2`) sync-t in Nautobot; Generate Intended Config für `crs326-core` erfolgreich, intended config in GC-Tabelle vorhanden.
- ✅ **Lokale Cutover-Config materialisiert:** `routeros/tp15-core-cutover-intended.rsc` enthält die aus Nautobot gerenderte Config plus die 54 ap1-Leases und den WG-Hub-Privkey (lokales Fenster-Artefakt, **nicht** im Golden-Config-Repo).
- **↪️ Adoption + Compliance-Push → ausgelagert nach TP22** (eigener TP, User-Entscheid 2026-08-08): ap1/ap2/crs326-Adoption, RouterOS-Nornir-Dispatcher, `enable_deploy`, WG-Privkey/PSK-Secret-Handling. TP21 selbst = Plattform + Datenmodell + Templates (steht). Datenmodell-Nachträge (branch/core-Ports final, sw1 außer Betrieb, cudy1) laufen in TP22 mit.
- Abh.: **TP20** (Postgres muss stehen), Gitea ✅, RouterOS-Fleet (core/branch/ap1/ap2).
### TP21.1 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) ▸ ✅ NETZ WIEDER LIVE (2026-08-08); Aufräum-Todos offen
> **Kern-Erkenntnis (wichtig gegen Doku-Verwirrung):** **`crs326-core` IST jetzt der produktive L3-Router.** Der Core-Cutover (TP15) wurde faktisch vollzogen — ap1 wurde plattgemacht/getrennt, core hat Routing/DHCP/DNS/NAT/WG-Hub übernommen. core-Mgmt = **`10.40.99.1`** (vlan99-SVI) bzw. same-subnet-Fallback **`10.40.10.1`** (RouterOS-Version 7.19.5 identifiziert das Gerät zweifelsfrei als CRS326, nicht als hAP/ap1). Die CLAUDE.md-Zeile „core grundkonfiguriert, NICHT produktiv" ist damit **überholt**.
- **Verlauf:** Ausgangssymptom war ein L3-Ausfall-Eindruck (kein DHCP, „kein Routing nach .20.x"). Ursache = **unvollständiger Cutover**: core hatte L3-SVIs + Firewall, aber es fehlten DHCP-Server, WAN-Egress, WG-Hub und der DNS-Resolver-Upstream. ap1 (bisher alleiniges L3-Gerät) war bereits Werksreset + getrennt.
- ✅ **DHCP wiederhergestellt** aus letztem ap1-Backup (`routeros/ap1-pre-core-cutover-20260806-073616.rsc`): 7 Server (`dhcp-v10…v99`) + 8 Networks + **54 statische Reservierungen** + Option `dns 1.1.1.1`. Reproduzierbar in `routeros/tp21-core-dhcp-from-ap1.rsc`. Legacy VLAN1 (pool-v1/defconf) + tote omada-inform-Option bewusst weggelassen.
- ✅ **WAN/Internet:** dhcp-client auf `vlan100` → Liwest-Public-IP `90.146.33.177` gebunden (Arris hängt physisch an core.vlan100), NAT-Masquerade `out-interface-list=WAN`, Default-Route korrekt; die self-pointing Blackhole-Route `0.0.0.0/0 via 10.40.99.1` entfernt. **disabled-Falle beachtet:** die ap1-Masquerade-Regeln `out-interface=bridge`/`=ether1` sind `disabled=yes` und wurden NICHT übernommen.
- ✅ **WG-Hub (TP10)** neu aufgebaut: `wg-hub` (listen 51821, Privkey aus `routeros/ap1-wg-hub.privkey`, Hub-Pubkey unverändert) + Peers `fm`/`dw` + Routen. **Beide Tunnel handshaken** (dw + fm live). Reproduzierbar (Privkey redacted) in `routeros/tp21-core-wan-wg-restore.rsc`.
- ✅ **DNS gefixt:** core hatte `allow-remote-requests=no` + leeren Upstream → Client-DNS tot UND Hub konnte myfritz (FM-Endpoint) nicht auflösen. Fix = `allow-remote-requests=yes` + `servers=1.1.1.1` (**plain**, weil core `certs=0` → `verify-doh-cert` unmöglich). Das entsperrte auch FM.
- ✅ **Identity** auf `crs326-core` gesetzt (war fälschlich „ap1" — genau diese Altlast stiftete Verwirrung). `mcp/mikromcp/config/routers.yaml` auf Zielschema: core `.99.1`, branch `.99.2`, ap1 `.99.11`, ap2 `.99.12` (wirkt nach MCP-Neustart; danach core via routerId `crs326-core`).
- ✅ **disabled/invalid-Falle bereinigt (User-Hinweis „keine disabled-Falle?"):** (a) die 2 TP10-WG-Forward-Regeln (`allow from/to WG spokes`) waren `I`=invalid, weil aus dem ap1-Backup mit der **stale Interface-ID `*1D`** übernommen → auf `in/out-interface=wg-hub` (per Name) korrigiert; (b) 2 **inaktive WG-Route-Leichen** (`gateway=""`, aus einem früheren Cutover-Versuch vor Existenz von wg-hub) entfernt, nur die aktiven `gateway=wg-hub` bleiben. **Finaler Sweep sauber:** filter-invalid=0, nat-invalid=0, routes-inactive=0, if-disabled=2 (nur ungenutzte `sfp-sfpplus1/2`, gewollt).
- ✅ **Backups:** `core-pre-dhcp-restore-20260808.backup` (vor Eingriff) + `core-post-tp21-recovery-20260808.backup` (nach allen Fixes neu gezogen) auf dem Gerät.
- ℹ️ **branch** ist gesund (L2, forwardet), Mgmt `10.40.99.2` (~~+ temp `10.40.10.2`~~ — die Temp-IP ist am 2026-08-16 entfernt worden) — via core-MNDP bestätigt. Das in Winbox-MAC gesehene „0.0.0.0" ist der bekannte Discovery-Artefakt (Gerät hat auf dem Discovery-VLAN keine IP), kein echtes Problem.
- 🔜 **TODO (Aufräum-Liste, spätere Session — danach WLAN-Neuprovisionierung):**
1. **DoH-Cloudflare-Gateway auf core wiederherstellen** (aktuell nur plain 1.1.1.1, keine Filterung/Account-Bindung): CA-Bundle importieren → `use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes` (Referenz-Config liegt auf **ap1**).
2. **Intended config nach Nautobot zurückschreiben** (die Direkt-Writes liefen unter der CRS326-Cutover-Governance-Ausnahme).
3. **branch `192.168.88.1`-Rest verifizieren** (Bootstrap Rev 4 entfernt es in Zeile 70; via MCP noch nicht gegengeprüft, weil Registry-Umstellung MCP-Neustart braucht).
4. ✅ **core `ether4` GEKLÄRT (2026-08-13) — es ist das Wohnzimmer-Sideboard (Smart-Managed-Switch, nur VLAN40-Multimedia), und der Port ist der falsche.** Symptom war „TV meldet, LAN funktioniert nicht".
- **Ursache = VLAN-Mismatch, kein Kabeldefekt:** `ether4` hat `pvid=40` (untagged **rein** → VLAN40 ✓), ist in VLAN40 aber **tagged**-Member (untagged sind `ether2`/`ether3`/`ether5`) → alles **raus** ist 802.1Q-getaggt und der untagged Switch/TV verwirft es. Einseitige Kommunikation.
- **Belege:** TX 3,4 GB / 16 M Pakete gegen RX 452 KB / 1.338 Pakete, 0 stabil gelernte MACs; TV-Lease `10.40.40.11` (`70:AF:24:B8:C9:86`) **bound, last-seen 1h54m**, gleichzeitig ARP-Status **`failed`** — ein Gerät, das nachweislich sendet, aber nicht antwortet. Der bound-Lease ist kein Gegenbeweis: beim Einschalten macht der TV einen INIT-REBOOT-`REQUEST` auf seine alte IP, RouterOS quittiert mit ACK und markiert bound — **das ACK muss den Client nie erreichen**.
- **Herkunft:** `ether4` war als **ap1-Uplink-Trunk vorbereitet** („ap1 wandert #15→#4"); ap1 blieb auf `ether15`, in den übrigen Trunk-Port wanderte das Sideboard-Kabel.
- ✅ **BEHOBEN 2026-08-13 — am Port, nicht am Kabel (User-Vorgabe): „Patchfeld 1–10 ist durchnummeriert und komplett gepatcht, die Nummerierung wird nicht wegen einer Config geändert."** Richtig so — die Portnummer ist physische Infrastruktur. Also `ether4` sauber auf **untagged VLAN40** gezogen statt das Kabel zu verschieben.
- **Ausgeführt via Nautobot-GC-ConfigPlan** (`plan_type=manual`, 6 Zeilen, change_control_id `TP21.1-4 …`), nicht per Direkt-Write — Governance gewahrt. Vorher `/system backup` = `core-pre-ether4-vlan40-20260813.backup` (Regel 3). `ether4` aus den Tagged-Listen von VLAN 10/20/30/40/50/90 entfernt + als untagged-Member zu VLAN40 ergänzt. **`pvid=40` stand bereits** — das war nie das Fehlende. **Verifiziert:** `vlan40 untagged=ether2,ether3,ether4,ether5`, `tagged=ether1,ether8,ether15`; vlan99 (Mgmt) und vlan100 (WAN) unangetastet. Damit ist auch der Flood-Leak aus IoT/INFRA/MAIN zum Wohnzimmer-Switch weg.
- ✅ **End-to-End-Beweis ERBRACHT (2026-08-14, live via MikroMCP auf `crs326-core`)** — der TV war am 08-13 tatsächlich nur im Standby. Alle vier Kriterien grün: `ping 10.40.40.11` = **5/5, 0 % Loss** (avg 563 µs); ARP `complete=true`/`status=delay` auf `vlan40` (vorher `failed`); `/interface/bridge/host` lernt `70:AF:24:B8:C9:86` auf **`ether4`, VID 40**; `ether4` `rx-unicast=6.909` / 3,0 MB RX (am 08-13 noch 1.338 Pakete / 452 KB → der Downstream lebt jetzt). DHCP-Lease `tv` weiterhin bound auf `dhcp-v40`. **Damit ist TP21.1 Punkt 4 vollständig abgeschlossen** und die Diagnose „tagged Egress auf einem untagged Port" ist end-to-end bestätigt (SSoT-Merkregel: `ref_tagged_egress_oneway_port`).
- 🔴 **INCIDENT + Lehre (2026-08-13): Der erste Deploy lief gegen das FALSCHE GERÄT (ap1 statt core).** Ich hatte 4 Device-UUIDs mit einer Sammelabfrage geholt und die **Reihenfolge angenommen**; die Nautobot-REST-API sortiert aber **alphabetisch** → `ap1, ap2, crs326-branch, crs326-core`. Der ConfigPlan trug damit ap1.
- **Schaden: keiner.** RouterOS wies jedes `set` ab (`invalid value for argument interface`), weil `ether8/13/15` auf einem 5-Port-hAP nicht existieren; die Ablehnung ist **pro Kommando atomar** → kein Teilzustand. ap1 nachgeprüft: VLAN-Tabelle vollständig, 25 WLAN-Clients, Mgmt `.99.11`. **Das war Glück, kein Können** — bei einem auf beiden Geräten *gültigen* Kommando (z.B. `tagged=ether1`) hätte es ap1s VLAN-Tabelle zerlegt.
- **Regel daraus: Device-UUIDs IMMER einzeln per Name auflösen, nie aus einer Sammelabfrage positional zuordnen** — und vor dem Deploy `plan.device.display` gegen den Zielnamen assert'en (im Skript jetzt so gemacht).
- **Bestätigt zum zweiten Mal:** der Deploy-**Job** meldete `SUCCESS` und der **Plan** `Completed`, obwohl auf dem Gerät nichts ankam. **Weder Job- noch Plan-Status sind ein Wirksamkeitsnachweis — nur die Gegenprüfung am Gerät zählt.** Der Fehl-Plan wurde gelöscht (irreführender Datensatz).
- ✅ **Lücke geschlossen (2026-08-13, User: „compliance rules! die Änderung machen wir"):** `/interface bridge port` + `/interface bridge vlan` sind jetzt für **alle drei Geräteklassen** im Template und als ComplianceFeatures scharf — s. TP22.
5. **`ap1`/`ap2` Neuprovisionierung** (WLAN-Rebuild, eigene Session) — ap1 ist Werksreset; danach greifen die `.99.11`/`.99.12`-Registry-Einträge.
### TP22 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) ▸ ✅ **DURCH (2026-08-13)**, Compliance-Backlog **abgearbeitet 2026-08-16 → 68/68, 0 Drifts** — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08
- **Ziel:** die 4 Live-Geräte (crs326-core, crs326-branch, ap1, ap2) real unter **Nautobot Golden Config** nehmen — Backup → Compliance/Drift → intended → **Push (`enable_deploy`)**. Damit greift der Governance-Entscheid „Config exklusiv über Nautobot" operativ. Macht die aktuellen `routeros/.secrets/*.rsc`-Handoffs mittelfristig überflüssig.
- **Stand aus TP21:** Plattform + Datenmodell + Git-Template + intended-config für core stehen; ap1/ap2 als reine APs zurückgeschrieben (`nautobot/ingest-ap-rebuild.py`, 2026-08-08). `enable_plan`/`enable_deploy` in `nautobot_config.py` noch **bewusst aus** (pull/read-only).
- ✅ **Schritt 0 DURCH (2026-08-13) — Backup-Scrubbing + Repo-Neuanlage.** Vorgezogen aus Schritt 4, weil ohne Scrubbing jedes weitere Backup Secrets geleakt hätte.
- 🔴 **Root-Cause-Fund:** Es gab **keine** ConfigReplace/ConfigRemove-Regeln → der Backup-Job vom 2026-07-22 hatte den ap1-`/export` ungefiltert nach Gitea geschrieben. `backups/ap1.cfg` enthielt im Klartext den **damals wie heute live genutzten WG-Hub-private-key** (bei der Core-Recovery unverändert auf `crs326-core` übernommen, damit FM/DW-Spokes matchen) **und 6 WLAN-PSKs**.
- ✅ **5 ConfigReplace-Regeln** (Platform RouterOS) via `nautobot/ingest-gc-scrubbing.py` (Dry-Run-Default, Regex-Selbsttest bei jedem Lauf): `10-wifi-passphrase`, `20-wireguard-private-key`, `30-preshared-key`, `40-password`, `50-secret` → Maske **`"<>"`**. Gegen den echten Export kalibriert: RouterOS quotet nur bei Bedarf (4 von 6 PSKs standen **unquotiert**), deshalb Alternation `("(?:[^"\\]|\\.)*"|\S+)` mit der **quotierten Variante zuerst** (`\S+` ist greedy und würde bei einem PSK mit Leerzeichen den Rest exponieren). Replace normalisiert immer auf die quotierte Form → Maske ist formunabhängig byte-identisch (Voraussetzung für das Template in Schritt 1). Negativtest: `public-key=` wird **nicht** maskiert (sonst bräche der WG-Peer-Block).
- 🐞 **Blocker nebenbei gefunden + gefixt: Nautobot hatte das ALTE Router-Passwort.** Der erste Backup-Lauf meldete Job-`SUCCESS`, schrieb aber nichts — `backup_last_attempt_date` sprang, `backup_last_success_date` nicht (**GC meldet Job-Erfolg auch bei Geräte-Fehlern; nur das Success-Datum je Gerät zählt**). Ursache: `NAUTOBOT_ROUTEROS_PASSWORD` im Stack war 12 Zeichen aus der Zeit vor der PW-Vereinheitlichung (2026-08-08, 32 Zeichen, identisch auf allen 4 MikroTik). Fix = Stack-Env nachgezogen + Stack 203 redeployed; Datei zusätzlich auf `chmod 600` (Backup `.bak-20260813`).
- ✅ **Gitea-Repo `admin/bf-golden-config` gelöscht + neu angelegt** (User-Entscheid: Repo neu, **nicht** rotieren — History-Rewrite verworfen, weil Gitea unerreichbare Blobs bis zum server-seitigen `gc` behält). Struktur + Template + README wiederhergestellt. **Vollscan aller Blobs der neuen History: 0 unmaskierte Secrets** (die 6 Regex-Treffer sind 5× README-Doku der Feldnamen + 1× der Template-Platzhalter). Nautobot-GitRepository re-synct, End-to-End-Backup ins frische Repo verifiziert (112 Z., 6/6 PSKs maskiert).
- 📌 **Risiko-Akzeptanz (User 2026-08-13): keine Rotation.** Faktenlage: Repo `private=true`, anonymer Zugriff 404. Aber Gitea hängt auf CF-`bypass` am Internet und `ha-xstate` ist ein **Service-Account mit Gitea-Admin** (kann jedes private Repo lesen) → Lesbarkeit für 2 Accounts, nicht fürs Internet. **Trigger für nachträgliche Rotation:** ändert sich die Gitea-Exposition oder der `ha-xstate`-Account → WG-Key rotieren (FM/DW-Klickanleitung `routeros/fritzbox-wg-lanlan-klickanleitung.md`).
- ⚠️ **DR-Konsequenz:** ein maskiertes Backup ist **kein Wiederherstellungs-Artefakt**. Restore hängt weiter an `~/.secrets/`, `routeros/.secrets/*` und `routeros/ap1-wg-hub.privkey` (alle gitignored).
- ✅ **Schritt 1 DURCH (2026-08-13) — AP-Zweig im Golden-Config-Template.**
- **Datenmodell = ConfigContext** (`nautobot/ingest-ap-configcontext.py`). Verworfen: Wireless-`RadioProfile` (haengt über `ControllerManagedDeviceGroup` an einem *Controller* — den gibt es seit der CAPsMAN-Abschaffung bewusst nicht mehr; ein Fake-Controller wäre Modellschuld), CustomFields (skalieren nicht), Ableitung aus VAP-Descriptions (SSID nur als Freitext → stille Brüche). Angelegt: ConfigContext **`ap-common`** (Scope Role *Wireless AP* + Platform RouterOS, weight 1000) mit `zones`/`radios`/Site-Konstanten + **`local_config_context_data`** je AP mit nur den 2 Kanalnummern. **Frequenz wird im Template gerechnet** (`5000+5*ch` / `2407+5*ch`) statt gespeichert.
- **Cross-Check im Skript:** die `zones`-Tabelle wird gegen die 11 real modellierten VAP-Interfaces (Name-Präfix + `untagged_vlan`) validiert, Divergenz = Abbruch → ConfigContext und DCIM können nicht auseinanderlaufen.
- **Template:** neuer Zweig `{% elif obj.role.name == "Wireless AP" %}` (rollen-, nicht namensbasiert). Scope bewusst **nur die compliance-verwalteten Sektionen** (channel/datapath/security/configuration/bridge-vlan/vlan99/ip-address/route) — **nicht** die ganze Config: defconf-Boilerplate (ipv4/ipv6-Firewall, `/disk`, `/tool mac-server`) und die VAP-Zeilen bleiben draussen, weil deren MACs gerätevergeben und in Nautobot nicht modelliert sind. Die früheren „Ausnahmen" fallen aus den Daten heraus: IOT hat `bands:["2g"]` → landet automatisch nur auf `wifi2`; VLAN1/VLAN99 stehen als literale Zeilen ausserhalb der Schleife (**VLAN99 auf `bridge`, NICHT auf die Radios** — sonst Mgmt over-the-air).
- 🔑 **Formatregel gelernt: gegen den echten `/export` schreiben, nicht gegen die `.rsc`.** RouterOS sortiert Attribute **alphabetisch** und schreibt `ft-over-ds` nur bei `ft=yes`; die handgesetzte `.rsc` hat abweichendes Padding/Reihenfolge. Referenz-Export: `routeros/state-backups/ap1-gc-backup-scrubbed-20260813.cfg`. **RouterOS exportiert CRLF**, Jinja rendert LF — GC normalisiert das korrekt (die schon vorher grüne `system-identity`-Compliance war der Beleg).
- ✅ **Abnahme:** ap1 **Intended == Geräte-Ist, 0 Drift** (33 Zeilen, zeilenweise identisch); ap2 unterscheidet sich in **exakt den 4 erwarteten Werten** (identity, 5180↔5745, 2412↔2462, `.99.11`↔`.99.12`).
- ✅ **Mitgenommen:** WG-Privkey-Platzhalter im core-Zweig auf dieselbe Maske `"<>"` vereinheitlicht (sonst Dauer-Diff, sobald eine Feature auf `/interface wireguard` liegt); **DNS-Statics korrigiert** (s. Nummernplan unten). `routeros/push-gc-template.py` gehärtet: **Pre-Push-Secret-Guard** (verweigert den PUT bei unmaskierten Werten), `--dry-run` mit Diff, `-m` statt hartkodierter Commit-Message, `sha` optional (sonst 404 nach Repo-Neuanlage).
- ✅ **Schritt 2 DURCH (2026-08-13) — Adoption + Backup/Compliance grün, 20/20.**
- SecretsGroup **„RouterOS Nornir"** an `crs326-core` + `crs326-branch` gehängt (ap1/ap2 hatten sie schon) — einheitliches Admin-PW seit 2026-08-08 macht eine Gruppe für alle 4 ausreichend.
- **Datenmodell-Korrekturen vorgezogen (technisch Voraussetzung):** `crs326-core` Status *Planned* → **Active** (ist der produktive L3-Router), `crs326-branch` `primary_ip4` `.99.13` → **`.99.2`** (live verifiziert).
- ✅ **Backup über alle 4 erfolgreich**, Sichtprüfung: **0 unmaskierte Secrets** (ap1/ap2 je 6 PSKs maskiert, core WG-Privkey maskiert, branch secret-frei).
- ✅ **4 neue ComplianceFeatures** (`wifi-channel`, `wifi-datapath`, `wifi-security`, `wifi-configuration`, alle `config_ordered=false`) + die bestehende `system-identity` → **20/20 compliant** über alle 4 Geräte.
- ⚠️ **Bewusst noch KEINE Features für `bridge-vlan`/`ip-address`/`ip-route`:** ComplianceRules gelten pro *Platform*, nicht pro Rolle — und der **`crs326-core`-Zweig ist in Slash-Form** geschrieben (`/ip/address add …`), was den Export (Space-Form, `/ip address add …`) **nicht** matcht → das gäbe permanenten Falsch-Drift auf dem Core. **Follow-up: core-Zweig auf Export-Form umstellen**, dann diese Features nachziehen. Die wifi-Features sind unkritisch, weil core/branch keine WLAN-Hardware haben (beide Seiten leer = compliant).
- ✅ **Follow-up DURCH (2026-08-13) — `crs326-core`-Zweig auf `/export`-Form.** Der Zweig war als *staged Cutover-Skript* geschrieben (Slash-Form `/ip/address`, plus `disabled=yes`-Flags aus der Vorbereitungszeit) und matchte den Export nicht → er hätte platform-weite ComplianceRules dauerhaft rot gefärbt. Jetzt Space-Form + alphabetische Attribute, `disabled=yes` entfernt (core ist seit 2026-08-08 produktiv), VLAN-Tabelle als Jinja-Konstante, GW-IPs weiter datengetrieben. **Ergebnis: 68 von 69 Zeilen byte-identisch zum Gerät.** Zusätzlich **Switch-Zweig** für `crs326-branch` (Mgmt-Layer) ergänzt — vorher fiel branch auf „nur `/system identity`" durch und meldete legitime Config als Drift. **9 weitere ComplianceFeatures** (interface-list/-vlan/-wireguard, ip-address/-pool/-dhcp-server/-dns/-firewall-nat/-route); bei `ip-dhcp-server` bewusst **drei spezifische Präfixe** statt `/ip dhcp-server`, sonst fingen die 54 dynamischen `lease`-Zeilen mit.
- ✅ **Schritt 3 DURCH (2026-08-13) — Deploy-Pipeline scharf, Pilot erfolgreich.**
- Image **`3.1.7-gc9`** auf der NAS gebaut (`/volume1/docker/nautobot-image`), Stack 203 retaggt. ⚠️ **Falle:** ein bereits vorhandenes `gc7` trug dieselben Flags; ein Build ohne geänderte Quelldatei zieht den **Docker-Cache** und liefert eine identische Image-ID — immer gegen die Flags im *fertigen* Image verifizieren. `scp` auf die NAS schlug fehl („Connection closed"), Übertragung per `ssh 'cat > datei'`.
- **Jobs sind in Nautobot zusätzlich einzeln freizuschalten** (`enabled=true`): die Plugin-Flags machen sie nur verfügbar.
- 🔴 **Kernbefund: `napalm-ros` ist ein READ-ONLY-Treiber.** Der Default-Dispatcher wählt NAPALM (weil `Platform.network_driver_mappings` `napalm: ros` enthält) → Backup (`get_config`) geht, **`merge_config` scheitert grundsätzlich**, mit leerer Fehlermeldung (`E1015 … ``). **Fix:** in den Golden-Config-Settings (nicht in den Nornir-Settings!) `custom_dispatcher: {"mikrotik_routeros": "nornir_nautobot.plugins.tasks.dispatcher.mikrotik_routeros.NetmikoMikrotikRouteros"}` — dieser Dispatcher pusht via `netmiko_send_config`. Danach lief der Deploy sofort.
- ⚠️ **Gescheiterter Deploy = sauberer Fehlschlag:** das Gerät blieb beim NAPALM-Versuch vollständig unverändert (kein Teilzustand). Zusätzlich meldet der Deploy-**Job** `SUCCESS`, obwohl der **Plan** auf `Failed` steht — wie beim Backup gilt: **nur der Objekt-Status zählt, nicht der Job-Status.**
- ✅ **Pilot-Deploy: die DNS-Statics auf dem Core.** Vorher `/system backup` (`core-pre-tp22-first-deploy-20260813.backup`). ⚠️ **`config_set` eines generierten Plans ist schreibgeschützt**, und `plan_type=missing` erzeugt **nur `add`** — das hätte Duplikate ergeben (`ap1.bf.lan` gleichzeitig auf `.99.3` und `.99.11`). Deshalb **manueller Plan mit Purge-vor-Add**. Ergebnis verifiziert: `ap1→.99.11`, `ap2→.99.12`, `branch→.99.2`, weiterhin 11 Einträge, keine Duplikate. **Compliance 51/56.**
- ✅ **Schritt 5 DURCH (2026-08-13) — Datenmodell-Nachträge, `nautobot/ingest-tp22-datamodell.py`.** Quelle war durchgehend **das Gerät**, nicht die Doku: `bridge/port` (pvid), `bridge/vlan` (tagged/untagged), `bridge/host` (gelernte MACs je Port+VID) plus ARP/DHCP-Leases zur Identifikation der Gegenstellen.
- **`crs326-core`: von 5 auf 26 Ports.** Vorher waren 3 davon als „GEPLANT (tentativ)" modelliert und `ether1` zeigte auf das tote `sw1 #12`. Belegt (MAC-hart): `ether8`→crs326-branch, `ether13`→pve `.20.100`, `ether14`→Sonoff `.30.250`, `ether15`→ap1, `ether16`→Arris/WAN, `ether5`/`ether6`→Sonos Schlafzimmer `.195`/`.193`. Die Trunk-Sets sind **exakt wie am Gerät** übernommen, inkl. Schiefstände (z.B. trägt `ether1` kein VLAN20/99) — bewusst nicht geradegezogen.
- **`crs326-branch`:** `ether1`-Ziel `sw1 #8` → `crs326-core ether8`; `ether5`/`ether6` = Sonos Arbeitszimmer (aktuell kein Link, die Schlafzimmer-Paare hängen am Core); `ether9–23`+sfp waren als „disabled" modelliert, sind live aber **enabled** (Bootstrap Rev4) → korrigiert.
- **Standort-Fehler gefunden:** `crs326-branch` **und** `ap2` standen in Nautobot auf *Technik UG*, real *Technik OG* (die Doku war richtig, das Modell falsch) → korrigiert.
- **Status-Endzustände:** sw1/sw2/cudy2 → **Decommissioned** (physisch ausgebaut; „Decommissioning" heißt *Prozess läuft*). Nautobot liefert diesen Status mit, aber nur für `circuits.circuit` → `content_types` um `dcim.device` erweitert statt einen zweiten gleichnamigen anzulegen. `cudy1` → **Offline** (core `ether11` hat kein Link; der endgültige Teardown bleibt TP19). sw1s 4 Duplikat-Ports (`gi8/gi11/gi15/gi16` neben `GE*`) entfernt.
- ✅ **Compliance-neutral verifiziert:** Generate-Intended + Compliance über alle 4 Geräte neu gelaufen → weiterhin **51/56** mit exakt denselben 5 bekannten Drifts. Grund: der core-Zweig des Templates iteriert nur Interfaces mit Präfix `vlan` und hält die Bridge-VLAN-Tabelle als Jinja-Konstante. **Umkehrschluss als Dauerregel: die `vlanXX`-Descriptions auf dem Core sind NICHT kosmetisch** — sie landen als `comment=` in der intended config.
- 🔍 **Nebenbefund, halbkonfigurierte Ports (NICHT angefasst, gehört zu TP19/TP5):** `core ether11` (cudy1) hat `pvid=10` ohne VLAN10-Membership → funktionslos; `core ether3` hat `pvid=1` bei untagged-VLAN40-Membership; `core ether2` ist gleichzeitig statisches untagged-Member von VLAN1 **und** VLAN40; `branch ether9–23` tragen `pvid=20` ohne jede Membership. Alles Bring-up-Reste, betriebsneutral (kein Link), aber sie gehören in die VLAN1-Aufräumrunde.
- ✅ **Nachtrag 2026-08-13 — Compliance-Abdeckung erweitert auf 68 Checks (63/68 grün; seit 2026-08-16 **68/68**).** Drei neue ComplianceFeatures, alle auf Anhieb grün:
- **`wifi-steering`** (`/interface wifi steering`) — sichert die TP3.3-Reparatur ab.
- **`bridge-port`** + **`bridge-vlan`** (`/interface bridge port` / `/interface bridge vlan`) — der **L2-Layer war bis dahin komplett unüberwacht**: der core-Zweig emittierte gar keine Bridge-Sektion, der AP-Zweig nur die VLAN-Tabelle ohne Ports. Jetzt für alle drei Geräteklassen im Template.
- 🔑 **Bewusst als Literal-Block, nicht aus DCIM abgeleitet.** Der As-built trägt akzeptierte Schiefstände (`core ether3` ist untagged-Member von VLAN40, hat aber `pvid=1`; `core ether2` hängt zusätzlich in VLAN1; `branch ether9–23` haben `pvid=20` ohne Membership). Würde man `pvid` aus `untagged_vlan` generieren, meldete die Compliance genau diese bekannten Altlasten als Dauerdrift. Der Literal-Block liefert das, was hier gebraucht wird: **Drift-Erkennung gegen den Ist-Zustand**.
- ⚠️ **`/interface bridge` selbst bleibt außen vor** — trägt die gerätespezifische `admin-mac`, wäre pro Gerät verschieden und ist kein Fleet-Standard.
- 🐞 **Falle dabei: Compliance vergleicht gegen das letzte BACKUP, nicht gegen das Live-Gerät.** Nach dem Steering-Deploy meldete sie erst Drift auf `wifi-configuration`/`wifi-steering`, weil das Backup vom Nachmittag stammte. **Reihenfolge nach jeder Geräteänderung: Backup → Intended → Compliance.**
- ✅ **Compliance-Backlog ABGEARBEITET (2026-08-16, User-Auftrag „beseitige autonom alle Compliance-Drifts") — Ergebnis: 68/68, 0 Drifts.**
- **Was entfernt wurde (alle vom Typ `extra`, also am Gerät vorhanden und in der intended config bewusst nicht vorgesehen):** ap1+ap2 `/ip dns static 192.168.88.1 router.lan` (Werks-`defconf`, zeigte auf eine IP, die es im Netz nicht gibt — der Core hat sein eigenes, korrektes `router.lan → 10.40.99.1`); `crs326-branch` `vlan10 "TEMP Mgmt VLAN10"` + `10.40.10.2` (Bring-up-Interim). Der fünfte Drift, `crs326-core` `10.40.41.211`, war schon vorher über den ConfigPlan `TP19-vlan1-core-address` gefallen.
- **Richtung der Beseitigung bewusst „am Gerät entfernen", nicht „ins Template aufnehmen"** — das entspricht der Absicht, die im Switch-Zweig von `mikrotik_routeros.j2` seit 2026-08-13 schriftlich steht („Bewusst NICHT emittiert … gehoeren entfernt"). Die frühere Zurückhaltung („potenzielle Rettungspfade") ist mit dem User-Entscheid vom 2026-08-16 aufgehoben; der Mgmt-Fallback aus VLAN10 entfällt damit, Rückweg im Notfall = MAC-Winbox am selben Draht.
- **Weg: 3 manuelle GC-ConfigPlans** (`TP22-drift-aps-defconf-dns`, `TP22-drift-branch-temp-vlan10`), Governance gewahrt — kein Direkt-Write. Vorher `/system backup` je Gerät (`{ap1,ap2,branch}-pre-drift-cleanup-20260816.backup`). Deploy **gestaffelt**: erst die beiden APs, am Gerät gegengeprüft, dann branch.
- 🔑 **Neue Falle gelernt: `POST /plugins/golden-config/config-plan/` ist `405 Method Not Allowed`** — das ViewSet ist read-only. ConfigPlans entstehen **ausschließlich** über den Job **`Generate Config Plans`** (bei `plan_type=manual` landet der `commands`-TextVar als `config_set` auf jedem ausgewählten Gerät). Das erklärt, warum es bis dahin kein Skript gab und alle 17 Vorgänger-Pläne per Hand entstanden sind.
- ✅ **Verifikation am Gerät, nicht am Job:** ap1/ap2 `list_dns_entries` → `total: 0` bei unveränderten 28 bzw. 3 WLAN-Clients; branch `/interface vlan` → nur `vlan99`, `/ip address` → nur `10.40.99.2/24`, `ping` vom Core 4/4 mit 0 % Loss. **`/interface bridge vlan/export` byte-identisch** zu vorher — die Gegenprobe darauf, dass kein neuer Drift entsteht (`bridge` steht dort statisch als tagged-Member von VLAN10, unabhängig vom SVI). Danach `Backup → Intended → Compliance`, alle 4 Geräte mit frischem `*_last_success_date`.
- 🛠️ **Tooling neu: `nautobot/gc-configplan.py`** — legt manuelle Pläne über den Job an, löst Device-UUIDs **einzeln per Name** auf und assertet `plan.device.display` **und** `config_set` gegen das Ziel (die alphabetische Sortier-Falle aus TP21.1 kann damit nicht wiederkommen), verweigert jedes `config_set` mit `<>` (Regel 1) und jede `print`-Zeile, kann gestaffelt deployen (`--deploy-ids`) und kapselt die Reihenfolge `Backup → Intended → Compliance` als `--refresh-compliance`. Dry-Run ist Default.
- 🔒 **GOVERNANCE — verbindliche Deploy-Regeln (festgeschrieben 2026-08-13, VOR dem Scharfschalten von `enable_deploy`):**
1. **Config Plans werden ausschliesslich feature-/manual-scoped erzeugt, NIEMALS als „full intended".** Ein Voll-Push schriebe wörtlich `passphrase="<>"` auf die APs (= alle WLAN-Clients raus, inkl. ~26 Shellys) bzw. `private-key="<>"` auf den Core (= WG-Hub tot, FM/DW-Spokes weg). **Jede Sektion, die `<>` enthält (`/interface wifi security`, `/interface wireguard`), ist vom Push generell ausgeschlossen.**
2. **Das AP-Template wird NIE zeilenweise auf ein laufendes Gerät gepusht.** Die Provisionierung setzt zwischenzeitlich `vlan-filtering=no` → das reisst den vlan99-Mgmt-Pfad ab, die restlichen Zeilen kämen über dieselbe SSH-Session nie an (halb konfigurierter AP). Voll-Reprovisionierung ausschliesslich per Datei-Upload + `/import` (`routeros/.secrets/ap{1,2}-prov-base.rsc`). Verschärfend: `device-mode=home` blockt auf den hAPs `scheduler`/`fetch` → die übliche Auto-Revert-Absicherung steht dort **nicht** zur Verfügung; der VLAN1-untagged-Rettungspfad darf nie wegoptimiert werden.
3. **Vor jedem Push ein `/system backup` auf dem Zielgerät.** Das GC-Backup ist wegen des Scrubbings **kein** vollwertiges Restore-Artefakt.
- ✅ **Schritt 4 ENTSCHIEDEN (User 2026-08-13): Variante (a) — keine Secret-Injection, bleibt wie es ist.** Ursprünglich geplant war „WLAN-PSKs + WG-Privkey via Nornir-Secrets injizieren"; die **Governance-Regel 1** (unten) hat das überholt: Sektionen mit `<>` sind vom Push generell ausgeschlossen, die Secrets werden also nie gebraucht. **Konsequenz, bewusst akzeptiert:** Secrets bleiben dauerhaft außerhalb von Modell und Push, DR läuft weiter über `~/.secrets/` + `routeros/.secrets/*` + `routeros/ap1-wg-hub.privkey`; „Gerät vollständig aus dem Modell wiederherstellbar" gilt nur *ohne* Secrets. Die verworfene Alternative (b) hätte Regel 1 aufweichen müssen — ein Render-Fehler hätte dann WLAN bzw. WG-Hub abgeschossen. **Damit ist TP22 inhaltlich abgeschlossen.**
- **Schritte:** (1) **AP-GC-Template**: `mikrotik_routeros.j2` um `role == "Wireless AP"`-Zweig erweitern (Bridge/vlan-filtering/ether1-Trunk/vlan99-Mgmt/6 SSIDs **ohne** PSK/L3) → APs komplett aus GC generierbar; (2) **Nornir/NAPALM-Adoption** aller 4 (SecretsGroup „RouterOS Nornir"; **einheitliches Admin-PW seit 2026-08-08 vereinfacht das**), Backup/Compliance-Job grün; (3) **`enable_plan`+`enable_deploy`** scharf — erst Dry-Run/Plan, dann kontrollierter Push; (4) **Secret-Handling** WG-Privkey (core) + WLAN-PSKs (bleiben aus dem Git-Template, via Nornir-Secrets injizieren); (5) Datenmodell-Nachträge: branch/core-Ports final, sw1 (außer Betrieb) + cudy1-Status.
- **Nutzen:** Drift-Erkennung + reproduzierbarer Rebuild jedes Geräts aus dem Modell.
- **Abh.:** TP21 ✅ (Plattform/Modell/Templates), TP20 ✅, einheitliches Admin-PW ✅. **Danach ist der kritische Pfad frei für TP5 (Deny-by-default) → TP6 (CAKE).**
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 1 DURCH, STUFE 2 VORBEREITET (2026-08-18)**
- ✅ **Stufe 1 — DoH auf dem Core scharf** (`routeros/tp23-stufe1-doh-core.rsc`), schliesst zugleich
**TP21.1 TODO 1**. `use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query`,
**`verify-doh-cert=yes`**. `servers=1.1.1.1` bleibt als Bootstrap fuer den DoH-Hostnamen stehen.
- 🔑 **Die alte Begruendung „core hat kein CA-Bundle, verify also unmoeglich" war zu pessimistisch.**
Es braucht kein Bundle, sondern **genau einen Root**: die Kette ist
`Leaf → GTS WE1 → GTS Root R4`, importiert wurde nur **R4** (~1 KB, gueltig bis 2036) — bei
**2,8 MB freiem Flash** ist das der Unterschied zwischen geht und geht nicht.
Fingerprint lokal und am Geraet gegengeprueft (`349dfa40…3c7d`).
- 📌 Der Zertifikatsimport steht bewusst **nicht** im GC-Template: er braucht die Datei am Geraet
und ist einmaliger Bootstrap, kein Sollzustand. Im Template steht nur die `/ip dns set`-Zeile.
- ⚠️ **Der Deploy meldete FAILURE, obwohl alles sass.** `/certificate import` gibt `failed: 0` aus,
und der Job scannt den Geraete-Output stumpf nach dem Wort. Wieder ein Beleg fuer die
TP22-Regel: **Job-Status ist kein Wirksamkeitsnachweis**, nur die Gegenpruefung am Geraet zaehlt.
- ⏳ **Stufe 2 — AdGuard eingerichtet, aber bewusst NICHT scharf geschaltet** (User-Vorgabe 2026-08-18).
`adguard_bf1` (`10.40.20.16`) macht bereits DoH zum selben CF-Gateway. Ergaenzt wurden:
conditional upstreams **`[/bf.lan/]10.40.20.1`** und **`[/40.10.in-addr.arpa/]10.40.20.1`**
(ohne die zweite Zeile wuerden interne Reverse-Lookups zu Cloudflare abfliessen), sechs
**CIDR-Client-Gruppen** (ein VLAN = eine Gruppe) und die permanente Ausnahmegruppe
**`dns-bypass-exempt`** (aktuell `10.40.10.183`). Abnahme per `dig`: `core.bf.lan` → `10.40.99.1`,
`nas.bf.lan` → `10.40.20.10`, extern ueber DoH. Backup `AdGuardHome.yaml.bak-tp23-20260817`.
- **Kein DHCP-Scope wurde angefasst** — alle sieben liefern weiter `10.40.X.1` aus. Das
Scharfschalten ist damit spaeter ein reiner DHCP-Change; die passende Firewall-Zeile
(`TP5-E2: LAN->AGH dns (Vorrat)`) steht bereits, damit dabei die scharfe Firewall nicht
angefasst werden muss.
- ⚠️ **AGH schreibt die YAML beim Start neu** (eigene Schluesselreihenfolge, Kommentare weg,
`time_zone: Local` → `UTC`). Wer die Datei patcht, muss danach am **laufenden** Stand pruefen,
nicht am eigenen Entwurf.
- 🔜 **Offen bleiben Stufe 3–6** (zweite AGH-Instanz, per-Lease Option 6, HA-Buttons, und zuletzt die
Bypass-Sperre). **Ausdruecklich keine HA/zweite Instanz** (User 2026-08-18).
- **Auslöser:** sobald gefiltertes DNS scharf ist, gehen einzelne Seiten/Dienste kaputt. Es braucht **Ausnahmen ohne Admin-Eingriff** — per User/Client/Gruppe/global, temporär oder permanent, mit Oberfläche. **Bedarf ist belegt, nicht vermutet:** der bisherige Workaround war „Client von Hand auf `1.1.1.1` stellen", regelmäßig (User 2026-08-14).
- 🔴 **Zentrale Reihenfolge-Regel: genau dieser Workaround IST der DNS-Bypass, den TP5 schließen will → erst der Ersatz, dann die Sperre.** Ohne funktionierende Ausnahme-Oberfläche verliert u.a. der Entwicklungsrechner samt Agenten-Toolchain seinen Ausweg — und DNS-Blocks äußern sich dort nicht als klare Meldung, sondern als diffus kaputte Build-Umgebung.
- **Zwei Befunde, die die Architektur bestimmen:**
1. **DoH ist nicht das Problem.** Clients sprechen plain mit dem lokalen Resolver, der macht DoH nach oben — die empfohlene Auflösung des DoH/Split-Horizon-Konflikts, `bf.lan` bleibt heil. Was Seiten blockiert, ist die **Policy**. ➡️ **Nie einen „DoH aus"-Schalter bauen**, sondern Policy-Ausnahmen.
2. **Identität existiert nur dort, wo die Client-IP sichtbar ist.** Router-DoH aggregiert alle Clients hinter *einer* Quelle — CF sieht nur den Core. Per-User-Identität *bei CF* verlangt einen **Agent** (WARP; bei NextDNS/ControlD deren CLI) → auf Shelly/Sonos/TV unmöglich. **RouterOS kann keine per-Client-Ausnahme** (keine DNS-Views; `/ip dns static` wirkt domain-weit — verifiziert). ➡️ Ein lokaler Resolver mit Client-Policy ist unvermeidbar.
- ✅ **Architektur-Entscheid: Filter zentral bei CF, Identität + Ausnahmen lokal.** Die **Filterliste bleibt vollständig bei Cloudflare** (Core via DoH, unterwegs via WARP); lokal wird **nie eine Filterliste vorgehalten, nur die Diffs**. Vorteile: kein RAM-Thema auf dem Core (eine Adlist kostet ~1,9 KB/Domain, 50k ≈ 95 MB — Konkurrenz mit Conntrack/DHCP/WG), **eine** Policy-Quelle statt zweier, gleiche Regelbasis zuhause wie unterwegs.
- ✅ **Resolver-Wahl: AdGuard Home** (läuft bereits, `10.40.20.16`). Vergleich gegen die realen Anforderungen:
- **Pi-hole disqualifiziert:** **spricht kein DoH** → bräuchte cloudflared/unbound als Sidecar (vierte QUIC-Komponente neben den drei TP16-Connectoren). Dazu belegtes Bruchrisiko **v5→v6** (FTL/API/Web neu, Docker-Env-Variablen tot).
- **AGHs „keine Gruppen"-Schwäche verschwindet hier**, weil Clients als **CIDR** eintragbar sind → `10.40.30.0/24` = die IoT-Gruppe. **Die VLAN-Segmentierung ist bereits die Policy-Grenze.** Dazu natives DoH, per-Device-ID auch bei wechselnder IP, „Unblock for this client", API + HA-Integration.
- **Technitium** = ernsthafte Alternative (autoritativer DNS + DHCP + Apps, `networkGroupMap`); seine IP/Subnet-Bindung trifft uns kaum, weil die IPs per Reservierung faktisch statisch sind. **Neubewertung legitim** (die TP4-Ablehnung galt einer anderen Frage) — sinnvoller Anlass wäre, DNS **und** DHCP zusammenzuführen. Eigener TP, nicht dieser.
- **Verworfen:** eigene Self-Service-UI bauen · per-Device-DoH via NextDNS/ControlD (Agent-Zwang, bricht `bf.lan`) · Umbrella ($30–45/User/Jahr) · Cloudflare verlassen (wirft TP16/TP16.1 weg) · Adlist auf dem Core.
- **Zuweisung ist nativ möglich:** `/ip/dhcp-server/lease` hat die Property **`dhcp-option`** (verifiziert) → **Option 6 pro Lease**, bei 54 Reservierungen ein Konfigurationsschritt. *(Nebenfund: Leases haben `address-lists` → automatische Aufnahme in eine Firewall-Liste, direkt nützlich für die TP5-JIT-Gruppe.)*
- ✅ **WARP im LAN abschalten — Mechanismus: Cloudflare „Managed Networks".** Der Client sucht bei jedem Netzwechsel einen **selbst gehosteten TLS-Endpunkt**, prüft dessen SHA-256-Fingerprint und zieht ein Device-Profil, das WARP **deaktiviert**. Beacon auf **NAS/pve**, nicht auf den Core (`certs=0`). Beseitigt die ICMP-Falle vom 2026-08-13 (`ref_warp_local_icmp_trap`) an der Wurzel.
- ✅ **Kollision aufgelöst (2026-08-14):** die frühere Sorge war, dass TP16-Schritt 5 `vw` auf WARP-geschützt hebt und damit „WARP zuhause aus" bricht. **Der User-Constraint aus TP16.2 streicht genau das** — `vw` bleibt bypass und verlangt nie Device-Identity. **Damit steht der Managed-Networks-Weg frei.** Rest-Risiko nur für Dienste, die künftig doch Device-Identity verlangen sollten → dann Access-Policy zusätzlich auf die Heim-IP (`bf4-home-ip` existiert bereits).
- ✅ **Kein Verlust beim Fremdnetz-Zugriff:** FM/DW laufen über den WG-Hub (TP10), nicht über WARP. Managed Networks schaltet nur *im* Heimnetz ab.
- ⚠️ Free-Plan-Verfügbarkeit des Features **nicht belegt** → im Dashboard gegenprüfen. Holzhammer-Alternative (WARP-Endpunkte per Firewall sperren) **nicht empfohlen**: bricht still, Client probiert Transport-Fallbacks, trifft auch Gäste.
- **Umsetzung in Stufen — Sperre zuletzt:**
1. **DoH auf dem Core scharf** → schließt TP21.1-TODO 1 (heute plain `1.1.1.1`); CA-Bundle nötig.
2. **WARP-Managed-Network** (Beacon auf NAS/pve) → WARP zuhause aus.
3. AGH in den Pfad, **zweite Instanz** + Config-Sync, beide per DHCP als DNS 1 + 2.
4. **Client-Gruppen per CIDR** (ein VLAN = eine Gruppe); per-Lease Option 6 nur für Abweichler.
5. HA-Buttons für temporäre Freigaben + JIT (AGH-API, RouterOS-REST, CF-API), RouterOS-`scheduler` als Backstop, damit Ablauf auch ohne HA greift.
6. 🔴 **Erst jetzt (in TP5): DNS-Bypass sperren** — Vorbedingung: Freigabe über HA funktioniert **nachweislich**, und die permanente Ausnahmegruppe **`dns-bypass-exempt`** (Entwicklungsrechner + Build-/Agent-Tooling) steht. Dann: nur Resolver dürfen 53/853 nach außen (schließt Geräte, die eigene Resolver hardcoden — Smart-TV mit `8.8.8.8` ist der Klassiker).
- **Grenzen, offen benannt:** **ein Resolver im Datenpfad** ist unvermeidbar (zwei Instanzen federn ab, ⚠️ **AGH hat kein natives HA → Config-Sync ist Betriebsaufwand**, sonst gilt nach Failover eine andere Policy) · **keine Blockseite bei HTTPS** (gilt für jede DNS-Filterlösung, auch Umbrella) · **CF-Log-Retention 24 h** im Free-Plan — laut User ausreichend, weil Ausnahmen JIT entstehen und ältere Fälle reproduziert werden.
- ⚠️ **802.1X löst die IoT-Identität NICHT:** Consumer-IoT (Shelly, Sonos) hat keinen 802.1X-Supplicant; der Standardweg ist **MAB** = wieder MAC-basiert → **kein Identitätsgewinn gegenüber der DHCP-Reservierung**. **TP8 bleibt richtig, aber als Sicherheitsthema** (dynamische VLAN-Zuweisung, kein MAC-Spoofing bei Managed-Geräten), nicht als DNS-Identitätsquelle.
- **Abh.:** **TP21.1 TODO 1** (DoH auf core), **TP5** (Firewall/Bypass-Sperre — Reihenfolge s.o.), **TP16 Schritt 5** (Access-Kollision), **TP16.1** (falls WARP zuhause bleiben soll), **TP8** (nur Sicherheit). **Kein Blocker für TP5 → TP6**, aber TP5 darf die Bypass-Sperre nicht vorziehen.
### TP24 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) ▸ 🔜 KONZEPT — **aus TP15 herausgelöst (User-Entscheid 2026-08-16)** — `docs/hw-refresh-requirements.md §9`
- **Warum ein eigener TP:** Die USV hing bisher als Unterpunkt in TP15 (HW-Refresh Switching/Router) mit. TP15s Kernauftrag — CRS326-Beschaffung, Bring-up, Core-Tausch — ist seit 2026-08-08 **durch**; die Stromversorgung ist als einziger Rest übrig geblieben und hat mit Switching sachlich nichts zu tun. Sie ist ein **eigenständiges Infrastruktur-Thema mit eigener Einkaufs-, Verkabelungs- und Entscheidungslogik** (Elektrik, Batterie, Standort) und blockiert nichts auf dem kritischen Pfad. Darum eigener TP.
- **Was es ist (Begriffsklärung):** eine **Pufferbatterie für die Netz-/Infrastruktur-Hardware** — **keine** klassische AC-USV mit 230-V-Steckdosen. Ein 48-V-Gleichspannungsbus versorgt die Geräte **direkt an ihrem DC-Eingang**; die Batterie hängt am selben Bus und übernimmt bei Netzausfall ohne Umschaltlücke. Der Umweg AC→DC→AC→DC (und damit dessen Dauerverlust) entfällt.
- **Zielbild (Aufbau, unverändert gültig aus der TP15-Recherche):**
| Teil | Typ | Preis | Wirkungsgrad |
|---|---|---|---|
| Netzteil | MeanWell **LRS-150-48** (150 W, 48 V) | ~€30 | ~88 % |
| DC-USV-Modul | MeanWell **DDR-120-48** (120 W) | ~€80 | ~95 % (pass-through) |
| Batterie | **LiFePO4 12,8 V / 20 Ah** (256 Wh) | ~€50 | — |
| DC-DC 12 V | MeanWell SD-100A-12 (100 W, für NAS/pve) | ~€30 | ~85 % |
| **Summe** | | **~€190** | |
`230 V AC → LRS-150-48 → 48-V-Bus →` Switches direkt · `→ DC-DC 12 V →` DS923/pve · `→ DDR-120-48 →` LiFePO4.
- **Warum 48 V:** PoE-Technik arbeitet nativ mit 44–57 V (kein Wandler nötig), höhere Spannung = kleinere Ströme = weniger Leitungsverlust, und 48 V ist der Telecom-/USV-Standard.
- **Laufzeit (256 Wh):** ~2,5 h bei 100 W reiner DC-Last · **~5,5 h wenn nur Switch + PoE (45 W)** · ~2 h wenn ein Inverter für den Mac mini mithängt.
- **Effizienz-Argument (der eigentliche Grund für DC statt AC):** eine AC-USV verheizt bei 100 W Dauerlast ~25–40 W (≈ €66–105/Jahr), die DC-Variante ~5–12 W (≈ €13–32/Jahr) — bei praktisch gleicher Anschaffung (~€190 vs. €150–250). Der Mehrpreis amortisiert sich über den Dauerbetrieb.
- ✅ **DC-Eingang vorhanden (geprüft):** `crs326-core` + `crs326-branch` (12–57 V, DC-Jack) · ap1/ap2 hAP ax² (12–57 V, alternativ PoE-in). ⚠️ **Adapter/Sonderfall:** DS923+ (externes 12 V/7 A, 4-Pin Mini-Fit JR) · pve/N100 (12 V Hohlstecker) · **Mac mini hat gar keinen DC-Eingang** (eingebautes Netzteil → nur über Inverter).
- **Offene Entscheidungen (aus TP15 mitgenommen, unverändert offen):**
1. **Mac mini auf USV?** Braucht einen Inverter (~15–20 % Verlust + Komplexität). Vorfrage: läuft er überhaupt 24/7? Wenn nein, reicht ein Graceful-Shutdown-Script auf USV-Warnung.
2. **Standort/Verkabelung:** Der Bus soll laut Skizze im **Arbeitszimmer/Technik OG** sitzen — der L3-Core steht aber in **Technik UG**. Entweder zweiter Zweig nach unten oder Bus dorthin, wo der Core steht. **Diese Frage ist durch den Core-Umzug neu zu stellen** (die Skizze stammt aus der Zeit, als oben der Verteiler geplant war).
3. **Arris-Kabelmodem:** vorentschieden auf **Grid** (muss ohnehin am Netz bleiben, DC-Zweig nach unten wäre Aufwand für ~1–2 W).
4. **Batteriegröße:** 20 Ah als Startwert; 50 Ah wenn die gepufferte Last über Switch+AP hinaus wächst.
5. **Monitoring/Shutdown:** wie meldet die USV einen Netzausfall an NAS/pve (Signalkontakt vs. nichts)? Ohne das gibt es kein sauberes Herunterfahren, nur Durchhalten bis die Batterie leer ist.
- **Nutzen konkret:** bei Stromausfall bleiben Core-Switch, Branch-Switch und beide APs online → **Netz, WLAN und der WG-Hub überleben** (der WAN-Uplink nur, solange auch der Arris Strom hat, s. Entscheidung 3). Für NAS/pve zählt vor allem das **kontrollierte Herunterfahren** statt des harten Abschaltens.
- **Abh.:** keine harten. **TP15 ✅** (die zu versorgende Hardware steht und hat DC-Eingänge). Verwandt: **TP18** (Garage-Nodes brauchen je Site eine Stromversorgung), **TP11** (USV-Zustand ins Monitoring). **Kein Blocker für TP5/TP6.**
- **Historie:** war bis 2026-08-16 Unterpunkt von TP15 („🔋 DC-USV: MeanWell LRS-150-48 + DDR-120-48 + LiFePO4 20Ah (~€190), 48V-Bus; CRS326 DC-input passt"). Vollständige Recherche inkl. Schaltbild, Effizienzvergleich und Gerätetabelle: `docs/hw-refresh-requirements.md §9` (+ §10 D/E/F für die offenen Fragen).
### TP25 — WLAN-Stabilisierung ▸ 🔴 **JETZT KRITISCHER PFAD — Roaming gemessen (08-20), Übergabekosten gemessen (08-21): Dead Spot statt Roaming-Problem** — Sammel-TP für Christophs Beobachtungen 1–6
> 🆕 **Messung 2026-08-20/21: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md)**
> (Nebenprodukt der TP6-Messreihen, Core-Sampler im 2-s-Takt über 2,5 h, teils mit laufendem Videocall).
> Drei Befunde:
> **(A)** Der Bandabstand 2,4 ↔ 5 GHz beträgt **8–10 dB** — physikalisch normal (6,7 dB allein
> Freiraumdämpfung), **keine** Konfigurationsursache. Das Problem ist der Absolutwert: 5 GHz liegt an
> den Telefonier-Orten bei **−75 bis −80 dBm**.
> **(B)** **Sticky Client:** ein Weg Küche → Kinderzimmer → Schlafzimmer → Terrasse, dreimal direkt an
> ap1 vorbei (−43 dBm), **eine einzige Übergabe** — und die erst oben bei −73 dBm.
> **(C)** **Band-Ping-Pong: 12 Wechsel in 9 Minuten** am selben Ort, immer ap2 5↔2,4 GHz.
> **(D) 2026-08-21 nachgemessen** (Gehweg, 5 Pings/s auf das Telefon, `docs/tp25-walk-messung.sh`):
> die „~4 s je Wechsel" waren **Zählerartefakt** — bei **−48…−55 dBm** kostet eine Übergabe
> **0 bis 2,6 s**, meist unter einer halben Sekunde, **Fast Transition funktioniert also**. Der wahre
> Befund ist schlimmer und liegt woanders: bei **−69…−77 dBm** ist das Telefon **6 bis 45 s** nicht
> erreichbar, zweimal über 20 s am Stück. **Nicht die Übergabe ist teuer, sondern die schwache Zone.**
> **(E) 2026-08-21, Raumkarte — der Dead Spot hat einen Namen: die TERRASSE.** Ping vom Core, Konsole
> auf dem iPhone, Raumwechsel per Klick markiert. **Im ganzen Haus null Verlust** (Technik UG/OG, Diele,
> Kinder-, Schlaf-, Arbeitszimmer, Treppe — alle 0 %, Übergaben ap1↔ap2 kosten 0,2 s). **Auf der
> Terrasse: 43 % Verlust, längste Lücke 20,4 s bei −72 dBm.** Erklärung ist die Topologie: ap2 sitzt in
> **Technik OG** — das ist aber **kein eigener Raum, sondern ein virtueller Bereich im Arbeitszimmer**
> (User-Korrektur 2026-08-21, die frühere „drei Wände"-Erklärung war falsch). Zwischen ap2 und Terrasse
> liegen **zwei Mauern der Diele OG plus teilweise die PV-Anlage an der Mauer** — und die ist als
> leitende Fläche ein **Schirm**, keine dämpfende Wand. Deshalb wird mehr Leistung oder ein schmalerer
> Kanal dort wenig ausrichten; gegen einen Schirm hilft nur, ihn zu umgehen. Details: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md) §6.
> ⚠️ **Konsequenz für Fund E: ein `signal-range`-Boden würde die Terrasse verschlechtern**, weil es dort
> keinen besseren AP gibt, auf den gesteuert werden könnte. Stattdessen: **(1)** 5 GHz auf 20 MHz
> (+3 dB, Ein-Wort-Change im Template), **(2)** **ap2 von Technik OG in die Diele OG umhängen** — die
> grenzt an Terrasse, Arbeitszimmer und Treppe, kostet **keinen neuen AP** und erfüllt damit die
> User-Randbedingung; zu klären ist die Verkabelung (ap2 hängt an `crs326-branch ether24`).
> **(3)** dritter AP erst, wenn beides nicht reicht.
> **(F) 2026-08-21 abends, Raumkarte 2 — ap1 versetzt ans Sideboard (`core ether3`).** Das OG gewinnt
> (Diele OG −50 ⌀ über 5,5 min mit 0 % Verlust, Technik OG −44), die **Schlafräume verlieren**:
> Schlafzimmer von −61 (ap1/5 GHz) auf **−83 (ap2/2,4 GHz)**, Kinderzimmer von −53 auf −75, Bad −83 mit
> 31 % Verlust. 🔴 **Und genau das ist vermutlich KEIN Abdeckungsproblem:** laut User liegen Schlaf- und
> Kinderzimmer **2 bzw. 4 m Luftlinie von ap1**, während ap2 4–6 m entfernt ist und zwei zusätzliche
> Mauern dazwischen hat. Ein Client, der so am ferneren AP klebt, ist ein **Steering**-Fall — und damit
> die erste Stelle im Haus, an der **Fund E (`signal-range`-Boden) wirklich hilft**, weil es hier eine
> bessere Alternative gibt. **Test steht aus** (Handover §5). Korrektur zur Terrasse: der Platz, an dem
> das Telefon normalerweise steht, ist mit **−46…−66 dBm gut**; schlecht ist der Teil dahinter.
> **(G) 2026-08-22 — Sideboard-Test BEENDET, ap1 geht zurück auf `core ether15`.** Ergebnis nach einem
> Tag, User-Befund: **ap1 am Sideboard richtet das Wohnzimmer nicht** — womit die RF-Vorhersage aus der
> Idee von 2026-08-13 (geschlossenes Media-Möbel, Metallgehäuse, niedrig) bestätigt ist — **und der
> Preis ist ein dunkles Schlafzimmer** (Fund F: −61 → −83 dBm). Damit ist die Idee **„ap1 ersetzt den
> Sideboard-Switch" entschieden: nein.** Die User-Vorgabe „Platzierung hat Vorrang vor dem eingesparten
> Gerät" greift — und die bessere Platzierung ist die alte. Transaktion
> `TEST-ether3-trunk-ap1-sideboard` wird zurückgerollt (`ether3` wieder **untagged VLAN40** =
> Sideboard-Access-Port); Compliance geht damit auf 92/92 zurück.
> 🔴 **Reihenfolge beim Rückbau:** erst ap1 physisch auf `ether15` umstecken, **dann** die Config
> zurückrollen. Umgekehrt hängt ap1 im Moment des Deploys ungetaggt in MEDIA und verliert vlan99/Manager.
> `ether15` ist durchgehend in allen VLANs getaggt geblieben, das Umstecken allein genügt also.
> ↗️ **Stattdessen als Nächstes:** ap2 wurde auf **2,5 m Höhe** gehievt (User 2026-08-22) — der billigste
> verbliebene Hebel unter der Randbedingung „kein neuer AP, solange nicht alles aus den vorhandenen
> rausgeholt ist". Wirkung **noch ungemessen**: Raumkarte mit [`tp25-walk-messung.sh`](tp25-walk-messung.sh)
> neu laufen, sonst steht Höhengewinn gegen Fund F ohne Zahlen im Raum. **Erster Eindruck des Users
> (2026-08-22): die Terrasse ist damit besser ausgeleuchtet** — subjektiv, Nachmessung steht aus und
> ist genau der Punkt, an dem der Dead Spot aus (E)/(F) zu prüfen wäre.
> ⚠️ **Korrektur 2026-08-22 (User):** die Formulierung „der Steering-Fall entfällt mit dem Rollback" war
> **falsch**. Was entfällt, ist nur die *konkrete Ausprägung* aus (F) — Schlaf-/Kinderzimmer bei 2 m zu
> ap1 an ap2 klebend. **Der Defekt selbst bleibt**, er verlagert sich nur: Übergabe bzw. Abriss passieren
> dann an anderen Stellen im Haus. Das ist auch belegt: **Fund B** (Sticky Client, dreimal an ap1 bei
> −43 dBm vorbei, eine einzige Übergabe) und **Fund C** (Band-Ping-Pong, 12 Wechsel in 9 min) wurden am
> **2026-08-20 gemessen — mit ap1 auf `ether15`**, also in genau der Stellung, in die wir jetzt
> zurückgebaut haben. Der Sticky-Client-Defekt ist damit **platzierungsunabhängig**.
> ⇒ **Fund E** (`signal-range`-Boden am Manager) bleibt unverändert die richtige Maßnahme und ist nach
> dem Rückbau der **oberste offene Punkt** in TP25 — nicht erledigt, nicht gegenstandslos.
> 📋 **Übergabe: [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md).**
> ⚠️ **Auf `crs326-core` ist eine Config-Transaktion OFFEN** (`TEST-ether3-trunk-ap1-sideboard`, Scope
> `bridge-vlan`, Frist 2026-09-04) — die Compliance ist deshalb **absichtlich** nicht 92/92.
> **Und das passiert TROTZ aktivem 802.11k/v und FT** — live geprüft: `st-main` mit `rrm=yes`/`wnm=yes`,
> `ft-over-ds=yes`. Die Merkregel `ref_ft_is_not_roaming` („11k/v fehlen") ist damit **historisch**.
> **Reihenfolge der Maßnahmen** (Doku §6, respektiert die User-Randbedingung „kein neuer AP, solange
> nicht alles aus den vorhandenen rausgeholt ist"): erst die Lücke messen → 5 GHz auf **20 MHz** statt
> 20/40 (+3 dB Link-Budget, für 1,2 Mbit Call irrelevant an Durchsatz) → **Fund E** (`signal-range`-Boden
> am Manager) → **2,4 GHz NICHT vorschnell drosseln** (dort hängen ~20 IoT-Geräte) → dritter AP zuletzt.
> **Restliste (Stand 2026-08-18):** Fund A–D ✅ · Drift-Luecke ✅ (mit TP28, 92/92) ·
> **offen: Fund E** (`access-list` mit `signal-range`-Boden — ⚠️ gehoert jetzt auf den **Manager**,
> nicht mehr auf die APs) · **Fund F** (5:05-Takt bei IoT-Trennungen, am Manager nachmessen —
> die AP-Tabellen sind unter CAPsMAN leer) · **Re-Walk + Dead-Spot-Kartierung** (hands-on).
> Alles drei ist Messarbeit gegen die korrigierte Basis, keine Konfigurationsarbeit mehr.
> **Warum eigener TP:** Christoph hat am 2026-08-16/17 sechs konkrete Beobachtungen gemeldet und ausdrücklich
> **einen** TP dafür verlangt. Sie hängen alle an derselben Ursache-Klasse (Funkparameter, die nicht nach Manual
> gesetzt sind), gehören also zusammen. **TP25 übernimmt den offenen Rest von TP3.3** (Re-Walk, Dead-Spot-Kartierung)
> — dieselbe physische Arbeit, doppelt geführt wäre Unsinn. TP3.2/TP3.3 bleiben als Historie stehen.
> **User-Randbedingung (2026-08-17, wörtlich): „ich weigere mich einen anderen access point zu kaufen solange ich
> nicht das gefühl habe, wir haben alles aus dem ap rausgeholt."** Das ist die Abnahmebedingung dieses TPs —
> keine Hardware-Empfehlung, bevor die Parameter stimmen.
- ⚠️ **Ausgangslage: Drift ist bestätigt, aber NICHT die Ursache.** Christoph hat am 08-16/17 selbst an den APs
gespielt („drift ist garantiert. es hat vorher aber auch nicht funktioniert"). Der Audit unten ist gegen den
**Ist-Stand** gefahren (`/interface/wifi/export` + `/interface/wifi/monitor` + Log, beide APs, 2026-08-17 14:01).
Zwei der Funde sind älter als die Experimente und stammen aus der TP3-Neuprovisionierung.
#### 🔴 Fund A — 2,4 GHz läuft auf 40 MHz, obwohl das Profil 20 MHz sagt (Beobachtung 4)
- **Christophs Frage war berechtigt, aber der Adressat ist ein Override, keine Entscheidung.** Das Channel-Profil
ist korrekt: `add band=2ghz-ax frequency=2412 name=ch-2g width=20mhz`. **Überschrieben wird es inline am Radio:**
`set [find default-name=wifi2] channel.band=2ghz-ax .skip-dfs-channels=10min-cac .width=20/40mhz`
→ **das Inline-Override gewinnt gegen das verlinkte Profil.** `monitor` beweist es: ap1 `2412/ax/Ce`,
ap2 `2462/ax/eC` — `Ce`/`eC` = 40 MHz. **Niemand hat 20/40 „gewählt"**; es ist genau die Override-Klasse, die
CLAUDE.md für den TP3-Bring-up schon dokumentiert („Werks-Inline-Overrides auf wifi1/wifi2 überschreiben das
verlinkte Profil") — damals nur für `configuration.ssid`/`security.*` bereinigt, für `channel.*` **nie**.
- **Warum 40 MHz auf 2,4 GHz falsch ist (Manual-Ebene, nicht Geschmack):** es gibt nur 3 überlappungsfreie
20-MHz-Kanäle. ap1 belegt mit `2412/Ce` ≈ 2402–2442, ap2 mit `2462/eC` ≈ 2442–2482 → **die beiden APs decken
zusammen praktisch das ganze Band ab.** Jeder Nachbar-AP überlappt zwangsläufig einen von beiden. Dazu halbiert
40 MHz die spektrale Leistungsdichte → **~3 dB weniger Reichweite bei gleicher Sendeleistung.** Und der Nutzen
ist null: IoT-Clients sind fast alle 20-MHz-only. **Das ist die „miserable Reichweite" aus Beobachtung 4.**
- **Fix:** die Inline-`channel.*`-Overrides auf `wifi1`/`wifi2` **leeren** (`!channel`), damit die Profile greifen.
Nebenbefund: `skip-dfs-channels=10min-cac` auf `wifi2` ist bedeutungslos — im 2,4-GHz-Band gibt es keine
DFS-Kanäle. Copy-Paste-Spur, harmlos, gehört trotzdem raus.
#### 🔴 Fund B — ap2 sendet auf 5 GHz mit 9 dBm statt 18 dBm (Beobachtungen 1+2)
- **Gemessen:** ap1 `wifi1` `tx-power: 18`, ap2 `wifi1` **`tx-power: 9`** — bei identischer Hardware und
identischem Profil. **9 dB Unterschied = Faktor ~8 in der Leistung**, ap2s 5-GHz-Zelle hat grob ein Drittel
des Radius von ap1.
- ✅ **BEHOBEN 2026-08-17: ap2s 5-GHz-Radio läuft jetzt mit 18 dBm** (`5220/ax/Ce/I`). Messreihe am Gerät:
| Zustand | Kanal | tx-power |
|---|---|---|
| ap2 vorher | `5745/ax/Ceee` (80 MHz, ohne `/I`) | **9** |
| ap2 nach Frequenzwechsel | `5220/ax/eeCe/I` (80 MHz, mit `/I`) | **9** ⬅️ unverändert! |
| ap2 nach Breitenkorrektur | `5220/ax/Ce/I` (40 MHz) | **18** ✅ |
| ap1 durchgehend | `5180/ax/Ceee/I` → `5180/ax/Ce/I` | 18 → 18 |
- 🔴 **Ehrliche Korrektur: meine erste Begründung war falsch, und die zweite auch nicht vollständig.**
Ich hatte die 9 dBm auf **ETSI-Band C** (ch149 ohne `/I`-Flag) zurückgeführt. **Widerlegt:** der Wechsel ins
Indoor-Band bei gleicher Breite ließ die Leistung auf 9 dBm. „80 MHz kostet generell Leistung" trägt auch
nicht — **ap1 fuhr 80 MHz im selben Band mit 18 dBm.** Der Fix ist gemessen, der Mechanismus ist es nicht.
- 🔬 **Kohärente Resthypothese (nicht bewiesen, aber testbar):** es sind **zwei** Effekte, je einer pro Kanal —
(a) `5745/Ceee` = ETSI-Band-C-Limit (die erste Theorie gilt für *diesen* Kanal weiter), und
(b) `5220/eeCe` = das Kontrollkanal liegt **nicht** am Blockanfang (`eeCe` statt `Ceee`), was das Radio
derated. Das würde ap1s `5180/Ceee`=18 und ap2s `5220/eeCe`=9 gleichzeitig erklären.
**Diskriminierender Test:** ap2 testweise auf `5180/Ceee` — 18 dBm ⇒ Hypothese (b) trägt; 9 dBm ⇒ es ist
gerätespezifisch (dann ist ap2s Radio der Verdächtige, relevant für die Hardware-Frage!). **Nicht nötig für
den Betrieb, aber vor einem Hardwarekauf wissenswert.**
- ⚠️ **Die TP3-Notiz „Kanäle non-DFS ap1 ch36/ch1, ap2 ch149/ch11" hatte trotzdem keinen tragenden Grund** —
s. eigener Absatz „Herkunft der ch149-Notiz" unten.
- 🎯 **Der Fix löst Fund B und C in einem Zug — und er ist nicht offensichtlich:** unter ETSI Austria existiert
im Indoor-Band (5180–5240) **nur ein einziger 80-MHz-Block** (`5180/Ceee` = ch36–48). Zwei APs können dort
also nicht beide 80 MHz fahren, ohne sich zu überlappen — **genau deshalb ist ap2 überhaupt auf 5745 gelandet.**
Mit **40 MHz** gehen beide sauber ins Indoor-Band:
| AP | neu | Kanäle | Leistung | Belegung |
|---|---|---|---|---|
| ap1 | `5180/Ce` | 36+40 | **18 dBm** | 5170–5210 |
| ap2 | `5220/Ce` | 44+48 | **18 dBm** | 5210–5250 |
Beide `/I`-freigegeben (aus der Radio-Tabelle bestätigt), überlappungsfrei, **+9 dB für ap2**.
#### 🔴 Fund C — 80 MHz auf 5 GHz kostet Reichweite, die hier fehlt (Beobachtung 1)
- `ch-5g width=20/40/80mhz`, laufend `5180/ax/Ceee` bzw. `5745/ax/Ceee` = **80 MHz**. Dieselbe Sendeleistung auf
die vierfache Bandbreite verteilt kostet **~6 dB** effektiven SNR am Zellenrand gegenüber 20 MHz, ~3 dB
gegenüber 40 MHz. In einer 2-AP-Wohnung mit Terrasse ist Spitzendurchsatz das falsche Optimierungsziel —
**Randabdeckung und Stabilität sind das richtige.**
- **Beleg, dass genau das das Terrassen-Symptom ist:** ap1-Log, Telefon steht still im Halter:
`-81` → roamt auf 2,4 GHz `-82` → zurück `-83` → **`disconnected, connection lost, signal strength -88`**.
**−88 dBm ist unter der Nutzbarkeitsschwelle.** Das ist **kein** Roaming-Problem (Christoph sagt selbst
„ich red da nicht mal von roaming"), sondern **Link-Budget** — und dagegen hilft Breite runter, nicht drehen.
#### 🔴 Fund D — `w2-iot` trägt einen unzulässigen Channel-Override auf einem Slave-VAP (Beobachtung 3)
- **ap1:** `add channel.band=2ghz-n .width=20mhz configuration=cfg-iot … master-interface=wifi2 name=w2-iot`
- **ap2:** `add channel.width=20mhz configuration=cfg-iot … master-interface=wifi2 name=w2-iot`
- **Ein Slave-VAP teilt das Radio des Masters und kann keinen eigenen Kanal haben.** Auf ap1 erzwingt das
Override zusätzlich `2ghz-n`, **während der Master `wifi2` auf `2ghz-ax` läuft** — ein Band-Mismatch zwischen
Master und Slave ist keine unterstützte Konfiguration. **Das ist der Mechanismus hinter „alle SSIDs sind da,
`-iot` beharrlich nicht, und plötzlich ist es wieder da":** der VAP fällt aus der Beacon-Rotation und kommt
bei einem Re-Apply zurück.
- 📌 **Und es ist der Beweis für Beobachtung 6:** die beiden APs sind hier **nicht identisch** (ap1 mit Band+Width,
ap2 nur Width). Eine generierte Config kann das nicht produzieren — das ist Handarbeit, und zwar auf genau dem
Objekt, das keine Handarbeit verträgt.
#### 🟠 Fund E — keine `access-list`, also kein Signal-Boden gegen klebende Clients (Beobachtung 2)
- **`/interface/wifi/access-list` ist auf beiden APs leer.** Damit gibt es **keinen `signal-range`-Boden** — ein
Client darf hängen bleiben, bis die Verbindung zusammenbricht, statt frühzeitig abgestoßen zu werden. Das ist
der Standard-RouterOS-Griff gegen genau dieses Symptom.
- **Beleg für das Kleben:** ap2-Log, Client `56:71:6C:22:E6:A9` flappt in 22 Minuten **sechsmal** zwischen
`wifi1`↔`wifi2` bei −62…−83 dBm. Und: `F2:62:4C:E1:AF:09@wifi2 disconnected, not responding, signal strength -47`
— **Abbruch bei ausgezeichneten −47 dBm.** Beides zusammen = Band-Oszillation ohne Steering-Politik, nicht
schlechte Abdeckung.
- ⚠️ **Nebenbefund:** die TP3.2-Access-List, die den P750W an ap1 pinnte („überlebt ap2-Re-Provision"), ist
**beim Rebuild 2026-08-08 verlorengegangen** — im Export beider APs steht keine einzige Access-List-Zeile.
Laut CLAUDE.md ist der Pin nicht mehr nötig (L3-GW liegt auf dem Core), der **Verlust war aber unbemerkt.**
- `ft=yes ft-over-ds=no` war **korrekt für den damaligen Standalone-Betrieb** (over-DS braucht ein
Inter-AP-Protokoll) — kein Fund, nur zur Abgrenzung festgehalten.
📌 **Seit TP28 gilt das nicht mehr:** unter CAPsMAN ist **`ft-over-ds=yes`** aktiv, weil der Manager
genau dieses Inter-AP-Protokoll mitbringt.
#### 🟡 Fund F — IoT-Clients trennen exakt alle ~5:05 bei gutem Signal (offen, präzise vermessen)
```
12:55:29 connected -59 → 13:00:36 disconnected -59 (5:07)
13:17:27 connected -63 → 13:22:32 disconnected -63 (5:05)
13:35:32 connected -60 → 13:40:37 disconnected -59 (5:05)
13:57:31 connected -59 → 14:02:35 disconnected -58 (5:04)
```
- **−59 dBm ist ein gutes Signal. Eine auf 3 Sekunden reproduzierbare 5-Minuten-Periode ist ein Timer, kein Funk.**
- 🚫 **Ausdrücklich NICHT die alte Power-Save-Merkregel wiederbeleben** — die ist am 2026-08-13 als Fehldiagnose
zurückgezogen worden (`ref_wifi_powersave_multicast`, Drucker war schlicht aus). Dieser Befund ist ein *anderer*:
vier saubere Messpaare mit konstanter Periode.
- **Kandidaten + der Test, der sie trennt:** (a) periodischer VAP-Re-Apply durch Fund D → prüfen, ob ap2
(ohne Band-Override) dieselbe Periode zeigt; (b) DHCP-Lease/Renew-Interaktion → `lease-time` der beiden MACs
(`38:A5:C9:C0:73:1E`, `38:A5:C9:BC:33:31`) gegen die Periode legen; (c) das Gerät selbst mit 5-min-Duty-Cycle
→ dann bleibt es nach Fund-D-Fix unverändert. **Erst nach dem Fund-D-Fix erneut messen** — sonst misst man zwei
Effekte übereinander.
#### 🧭 Beobachtung 5 — „alle Welt sagt CAPsMAN, nur wir sagen Nautobot GC. Hilfe oder Last?"
- **Klare Antwort: es sind keine Alternativen, sie lösen verschiedene Probleme.** CAPsMAN = *Runtime*-Control-Plane
fürs WLAN (ein Ort für Radio-Settings, sofortiger Push, dynamische VAPs). Nautobot GC = *Config-as-Code*-SoT
+ Drift-Erkennung über die **ganze** Flotte (Core, Branch, beide APs). Man kann beides haben; das Reibungspunkt
ist nur, dass CAPsMANs dynamische Objekte für GC unsichtbar sind (der Grund von 2026-07-22, und der gilt).
- 🔎 **Aber eine Begründung von damals hält der Prüfung nicht stand.** Der Entscheid nannte als ersten Grund
„WLAN dauerhaft instabil, besonders wenn beide APs laufen". **Das WLAN ist nach dem Standalone-Rebuild
weiterhin instabil** — die These „CAPsMAN war die Ursache" ist damit empirisch geschwächt. Die Funde A–E sind
Funkparameter; CAPsMAN hätte sie weder verursacht noch geheilt. **Der Rebuild hat ein Symptom umgezogen,
keine Ursache beseitigt.**
- **Wo GC hier tatsächlich Last war:** es hat diesen Drift **nicht verhindert.** Die Compliance deckt
`wifi-steering` ab (seit TP3.3), aber offensichtlich **nicht die Channel-/Inline-Override-Fläche** — sonst wären
Fund A und D als Drift aufgeschlagen. Das ist eine **Lücke im Feature-Set, kein Argument gegen GC.**
- **Ehrliches Urteil (meine Empfehlung):** GC als SoT **behalten** — es ist das Einzige, was Hand-Tuning davon
abhält, sich still zu akkumulieren, und genau das ist hier passiert. Aber: **GC ist kein WLAN-Tuning-Werkzeug.**
Der Ausweg ist nicht CAPsMAN, sondern die Parameter **einmal nach Manual richtig** zu setzen und dann mit einer
Compliance-Feature auf `/interface/wifi` **festzunageln**. Bei genau 2 APs ist der Kanal-/Leistungsplan eine
Einmalentscheidung, kein Laufzeitproblem — der Hauptvorteil von CAPsMAN greift hier also kaum.
*(Wenn ein dritter AP dazukäme, wäre die Abwägung neu zu führen.)*
#### 🔎 Herkunft der ch149-Notiz (Christophs Frage 2026-08-17: „wie kam es zu dieser Notiz?")
- **Nachverfolgt bis Commit `03f328f`, 2026-06-18, CAPsMAN-Ära.** Wortlaut der Commit-Message:
*„Kanal-Plan non-DFS/überlappungsfrei: ap1 ch36+ch1, ap2 ch149+ch11 (Auto-Channel hatte beide APs auf
5500-DFS/2437 gelegt = Co-Channel)"*.
- 🎯 **Das gelöste Problem war Co-Channel, nicht DFS.** Auto-Channel hatte beide APs auf **denselben** Kanal
gelegt; der Fix war, überhaupt zu pinnen. Dass die Wahl auf 36/149 fiel, ist das **FCC-Standardpaar** aus
jedem Tutorial — „non-DFS" wurde als Begründung *nachträglich* mitgeschrieben, war aber nie eine geprüfte
Anforderung. **Christophs Einschätzung ist damit belegt: es gibt keinen Grund, Frequenzbänder auszunehmen.**
- 📌 **Und ein Detail, das die Sache entschärft: `skip-dfs-channels=10min-cac` schließt DFS gar nicht aus.**
Es überspringt nur Kanäle, die die **10-Minuten**-CAC brauchen (Wetterradar-Band 5600–5650). Der Rest des
DFS-Bands bliebe nutzbar. Ausgeschlossen wurde DFS in Wahrheit durch das **harte `frequency=`-Pinning** —
wo nichts ausgewählt wird, wirkt kein Skip. (Die Overrides sind mit Schritt 2 ohnehin weg.)
- **Konsequenz für den Kanalplan:** das DFS-Band **5500–5700** ist eine echte Option und liegt bisher brach —
**11 zusätzliche Kanäle**, in ETSI ohne `/I`-Beschränkung. Preis: CAC-Wartezeit beim Start und im
Radar-Fall ein erzwungener Kanalwechsel (30 min Sperre). Bei Christophs aktuellem Symptombild
(Abbrüche) wäre ein radar-getriggerter Wechsel unangenehm — **deshalb jetzt nicht**, aber es ist der
Reservekanalraum, falls ein dritter AP dazukommt und 40 MHz nicht mehr reicht.
#### 📡 Beobachtung 4b — „könnte `2ghz-g` nicht besser zu IoT passen?"
- **Verfügbar auf diesem Radio** (`/interface/wifi/radio`): `2ghz-g:20mhz` · `2ghz-n:20mhz,20/40mhz` ·
`2ghz-ax:20mhz,20/40mhz`.
- ❌ **Für Reichweite bringt es nichts.** `band=` setzt die **höchste** angebotene Generation, nicht die
einzige — ein `2ghz-ax`-AP ist rückwärtskompatibel, g/n-Clients assoziieren normal. Und ein schwacher
Client fällt über die Ratenadaption von selbst auf die robusten niedrigen Raten. `band` ändert weder
Sendeleistung noch Empfängerempfindlichkeit.
- ✅ **Für Interoperabilität ist der Gedanke aber richtig.** Billige ESP-/Realtek-IoT-Stacks haben
dokumentierte Probleme mit 802.11ax-Beacons (HE-Capabilities, PMF). `2ghz-n` nimmt die HE-Elemente aus
dem Beacon und kann Assoziationswackler beheben — ein **Kompatibilitäts-**, kein Abdeckungsargument.
- 🎯 **Pointe: genau dieser Instinkt steckte schon im Fehler.** `channel.band=2ghz-n` auf `w2-iot` (Fund D)
war exakt der Versuch, IoT ein legacy-freundlicheres Band zu geben. **Die Absicht war richtig, der
Mechanismus falsch** — `band` ist eine Eigenschaft des **Radios**, nicht eines Slave-VAPs. Alle
2,4-GHz-SSIDs teilen `wifi2`.
- **Empfehlung, falls Fund F (5-Minuten-Takt) nach dem Override-Fix bleibt:** `wifi2` global auf **`2ghz-n`**
— die saubere Version dessen, was der Override wollte. Kostet real nichts (kein 2,4-GHz-Client hier braucht
ax-Raten, `dirtyvader-main` auf 2,4 GHz ist ohnehin Fallback). **`2ghz-g` NICHT als ersten Schritt:** die
20-MHz-Bindung ist richtig, aber 54 Mbit/s-Deckel und kein HT für *alle* 2,4-GHz-Clients ist ein echter
Verlust ohne Zusatznutzen gegenüber `n`. `g` ist die Eskalationsstufe, nicht der Einstieg.
#### 🏠 Deckenmontage-APs — bringen sie einen echten Vorteil? (Christophs Überlegung 2026-08-17)
- **Der Gewinn liegt in Platzierung und Antennendiagramm, nicht im Silizium.** Ein cAP ax ist dieselbe
Chip-Generation und ebenfalls 2×2 wie der hAP ax² — **mehr Rohleistung gibt es nicht.** Was er gibt:
ein nach unten optimiertes Diagramm, freie Sichtlinien von der Decke, PoE-Montage ohne Möbel davor.
- 🔴 **Aber die Entscheidung wäre heute blind gewesen.** Vor dieser Session lagen ~9 dB auf ap2 brach
(Fund B), 2,4 GHz verschenkte ~3 dB an 40 MHz (Fund A) und der 5-GHz-Zellenrand ~3 dB an 80 MHz (Fund C).
**Hardware zu kaufen, um Fehlkonfiguration zu kompensieren, ist der teuerste Weg.** Genau das deckt sich
mit Christophs eigener Randbedingung („nicht kaufen, bevor alles aus dem AP raus ist").
- ✅ **Jetzt ist der Boden gelegt** (beide APs 18 dBm, überlappungsfrei, 20 MHz auf 2,4). **Die Entscheidung
gehört an den Re-Walk (Schritt 5).** Bleiben danach Dead Spots — Terrasse, Kanten —, dann *ist* es ein
Platzierungsproblem, und Deckenmontage ist die richtige Antwort. Dann stellt sich aber zuerst die
billigere Frage: **reicht Umhängen der vorhandenen APs?** „Drehen hat nicht geholfen" passt zu einer
Abschattung, die Drehen nicht behebt — Versetzen schon.
- 📌 **Falls es auf drei APs hinausläuft, wird die CAPsMAN-Abwägung neu geführt** (s. Beobachtung 5): bei
3 APs ist koordinierte Kanal-/Leistungsverwaltung erstmals ein Laufzeit- und nicht mehr ein
Einmalproblem. Und der diskriminierende Test aus Fund B (ist ap2s Radio gerätespezifisch schwach?)
gehört **vor** einen Kauf.
#### 📋 Umsetzungsreihenfolge (alles via GC, gestaffelt, ap2 zuerst)
1. ✅ **Fund D DURCH (2026-08-17)** — `channel.*`-Overrides von `w2-iot` auf **beiden** APs entfernt.
`w2-iot` ist danach auf ap1 **und** ap2 `BR` (bound + running) → Beobachtung 3 strukturell erledigt.
Dateien: `routeros/tp25-schritt2-fix-overrides-ap2.rsc`, `routeros/tp25-schritt1-3-ap1.rsc`.
2. ✅ **Fund A DURCH (2026-08-17)** — Inline-`channel.*` auf `wifi1`/`wifi2` geleert. **2,4 GHz läuft jetzt
auf 20 MHz** (`2412/ax` bzw. `2462/ax`, kein `Ce`/`eC` mehr), tx-power 15 dBm. ap1 ch1 / ap2 ch11 =
überlappungsfrei mit Platz für ch6 in der Nachbarschaft.
3. ✅ **Fund B+C DURCH (2026-08-17)** — `ch-5g` auf `width=20/40mhz`; ap1 `5180/ax/Ce/I`, ap2 `5220/ax/Ce/I`,
**beide 18 dBm** (ap2 vorher 9). Zellen benachbart, überlappungsfrei (5170–5210 / 5210–5250).
- 🔴 **GOTCHA, der zwei Deploys gekostet hat: `/interface/wifi/set !channel` löscht die
Inline-Sub-Property-Overrides NICHT** — es wirft nicht, es tut nichts. Danach stand `channel.width`
unverändert da, obwohl Profil *und* `configuration` korrekt auflösten. **Richtig ist einzeln:
`!channel.width !channel.band !channel.skip-dfs-channels`.** Dasselbe Muster wie der bekannte
`!configuration.ssid !security.*`-Griff aus dem TP3-Bring-up — nur nie auf `channel.*` angewandt.
- ⚠️ **Zwischenzustand war schlechter als der Ausgangszustand:** weil die Breite noch auf 80 MHz stand,
rutschte ap2 mit der neuen Frequenz in **denselben** 80-MHz-Block wie ap1 (beide ch36–48) = genau die
Co-Channel-Lage, die 2026-06-18 wegkonfiguriert werden sollte. **Merke: bei Kanalarbeit Breite VOR
Frequenz ändern**, sonst erzeugt man auf dem Weg eine Kollision.
4. 🔜 **Fund E** — `access-list` mit `signal-range`-Boden je AP; Schwellen erst nach dem Re-Walk festlegen.
*(Hinweis aus der P750W-Historie: `/interface/wifi` hat **kein** `disconnect-timeout`/`keepalive` mehr —
das gab es nur im alten `wireless`-Paket, AX-Radios können nicht zurück. Der Signal-Boden ist damit der
einzige verbleibende Griff gegen klebende Clients.)*
5. **Re-Walk + Dead-Spot-Kartierung** (der übernommene TP3.3-Rest) — **erst jetzt**, gegen die korrigierte Basis.
Vorher gemessene Werte sind wertlos.
6. ⏳ **Drift-Lücke — Teil 1 GESCHLOSSEN, Teil 2 offen und jetzt exakt vermessen (2026-08-17).**
- ✅ **Der akute Teil ist zu:** die neuen Werte stehen in der SoT (`nautobot/ingest-ap-configcontext.py`:
`radios[5g].width` `20/40/80mhz`→`20/40mhz`, `DEVICE_CHANNELS[ap2][5g]` `149`→`44`), Config-Context
gepatcht, dann **Backup → Intended → Compliance** → **72/72, keine Drifts.** Damit kann ein künftiger
Deploy die Fixes **nicht** mehr zurückdrehen. *(Bemerkenswert: `width` für 2,4 GHz stand dort die ganze
Zeit korrekt auf `20mhz` — die SoT war richtig, nur das Gerät hat sie nicht befolgt. Der Beweis, dass
das Problem der Inline-Override war und nicht die Datenlage.)*
- ✅ **ERLEDIGT 2026-08-18 (mit dem TP28-GC-Umbau).** Die `wifi-*`-Features haengen jetzt am Core,
die APs sind auf die `cap`-Zeile eingedampft, und drei neue Rules (`wifi-capsman`,
`wifi-provisioning`, `wifi-cap`) decken den Rest ab. **92/92 compliant.** Die unten
beschriebene Luecke auf `/interface wifi` ist damit gegenstandslos: ein CAP hat keine
Inline-Overrides mehr. Der historische Befund bleibt als Begruendung stehen:
- 🔴 **Der Erkennungsteil bleibt offen. Die 18 Compliance-Rules abgefragt — es fehlen genau zwei Sektionen:**
| vorhanden | fehlt |
|---|---|
| `/interface wifi channel` · `… configuration` · `… datapath` · `… security` · `… steering` | **`/interface wifi`** (die Interface-Sektion) |
| `/ip firewall nat` | **`/ip firewall filter`** |
- **Folge 1 (WLAN):** die fünf `wifi-*`-Features decken nur die **Profil**objekte ab. Die Inline-Overrides
leben in `/interface wifi` — **deshalb sind Fund A und D seit dem 2026-08-08 unentdeckt geblieben, obwohl
die Flotte durchgehend „compliant" meldete.** Genau die Lücke, die ich unter Beobachtung 5 vermutet hatte;
sie ist jetzt belegt, nicht vermutet. Erschwerend: die VAP-MAC-Adressen sind gerätespezifisch (Template
Zeile ~220 nimmt sie bewusst raus), eine Rule auf diese Sektion braucht also erst
`local_config_context_data` je AP.
- 📋 **Vorarbeit `/ip firewall filter` (2026-08-17, Reconnaissance — vor dem Template-Bau nötig):**
`crs326-core` **25** Regeln (defconf + TP10 + 6 TP5-Holes + 11 TP5-E2 + Log-Regel) · `crs326-branch` **0** ·
`ap1`/`ap2` je **12 defconf-Regeln** (Werksreste: input-seal, fasttrack, ipsec-Zeilen, sogar
„accept to local loopback (for CAPsMAN)" — inert, weil die APs nicht routen, aber vorhanden).
➡️ **Zwei Entscheidungen, bevor die Rule scharf gemacht wird**, sonst meldet sie sofort flächendeckend Drift:
(a) **APs:** die 12 defconf-Regeln als Sollzustand ins Template schreiben **oder** sie abräumen
(sie tun nichts — ein reiner AP hat keine `forward`-Kette zu schützen). Empfehlung: **abräumen**, sonst
schreibt man Werksmüll als Absicht in die SoT — dasselbe Argument wie bei `telnet` in TP16.1.
(b) **branch:** 0 Regeln ist der korrekte Sollzustand und muss im Template auch als *leer* gerendert werden.
- ✅ **Folge 2 — GESCHLOSSEN (2026-08-17, TP26): `/ip firewall filter` hat jetzt eine Compliance-Rule.**
Vorher gab es nur `ip-firewall-nat`; „72/72" sagte über die Firewall gar nichts. Jetzt **76/76**
(eine neue Rule × 4 Geräte). `config_ordered=true` — bei Firewallregeln ist die Reihenfolge semantisch.
**Damit ist TP5 Etappe 3 entblockt.**
- ⚠️ **`steering=`/Channel-Änderungen re-applien alle VAPs und werfen jeden Client kurz raus** (TP3.3 am
2026-08-13 gemessen: ap1 25→2, ap2 7→14). **Nicht während eines Calls deployen.** `/system backup` je AP vorher.
- **Abh.:** TP22 ✅ (GC-Deploy-Pipeline). Übernimmt den offenen Rest von **TP3.3**, verwandt **TP3.2** (Coverage-Doku
`docs/wifi-p750w-coverage-and-map.md`). **Kein Blocker für TP5/TP6** — läuft parallel.
### TP16.3 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) ▸ 🔜 **AUSFÜHRBAR (Runbook 2026-08-19)** — `docs/tp16.3-nautobot-mesh-only-runbook.md`
> 📌 Der Schnitt (`nautobot` aus dem `bf-nas`-Ingress) ist ausformuliert inkl. Vorbedingungen, PUT-Ablauf
> und Rollback. Zwei Punkte stehen dort explizit, weil sie den TP sonst kippen: **Mesh-Hostname-Routes
> sind Egress und veröffentlichen nichts** (wer das verwechselt, baut den Public-Strang ab), und
> **Nautobot ist nicht nur Web-UI, sondern die Deploy-Pipeline** → zur Abnahme gehört ein Job-Lauf,
> nicht nur ein Browser-Aufruf.
> **Christophs Frage (2026-08-17):** „recherchiere, ob unser neuer wg-tunnel/cf mesh als datenpfad für die
> server/services genutzt werden kann. die containerpflicht als offizieller weg ist für mich unlogisch."
- ✅ **Die Container-„Pflicht" gibt es für den privaten Zugang tatsächlich nicht — und wir nutzen sie schon nicht mehr.**
CF-Docs (Mesh↔Tunnel-Vergleichstabelle) sind eindeutig: **Mesh** = bidirektional, L3/L4, **TCP+UDP+ICMP**, jeder
Teilnehmer bekommt eine Mesh-IP, Connector = `warp-cli` **oder ein nativer WireGuard-Peer** (unser Fall).
**Tunnel** = *inbound zum Origin*, Veröffentlichen von Hostnames, Connector = `cloudflared`, Protokolle
HTTP/S·TCP·SSH·RDP·SMB **über WebSocket geproxied**. Für „Server hinter dem Node erreichbar machen, die keinen
Client laufen lassen können" nennen die Docs ausdrücklich **CIDR-Routes auf dem Mesh-Node** als den Weg —
wörtlich für „servers, databases, printers, IoT devices".
- 🎯 **Das ist bei uns seit TP16.1 bereits scharf:** die vnet-Route `10.40.0.0/16` zeigt auf `bf-core-mesh`
(§4.4, Weg A). **Der gesamte private Datenpfad läuft schon über natives WireGuard auf dem Core, ohne jeden
Container.** Und er ist im TP5-Log-Lauf **belegt**, nicht vermutet: 137 Zeilen `in:cf-mesh`, Quelle
`100.96.0.1` → Nautobot `.20.30:8080` (62×), NAS `.20.10:6690` (46×), macmini `:22`, NAS `:5000/:5001/:443/:22`,
pve `.20.100:8006`, pve-mcp `.20.173:3000`, branch/ap1/ap2 `:22`. **Christophs Instinkt war richtig.**
- 🔴 **Wo die Container-Pflicht echt ist — und warum sie nicht willkürlich ist:** für einen **öffentlichen
Hostname**, den jemand **ohne** WARP im Browser aufruft, braucht es einen Reverse-Proxy an der Edge. Das ist
`cloudflared`. **Achtung Verwechslungsfalle:** Mesh kennt seit 2026-07-02 ebenfalls **Hostname-Routes**, und
die können auch *public* hostnames — aber das ist **Egress**: „route that hostname's traffic through the node
and egress via the node's public IP". Es **veröffentlicht nichts**. Wer das verwechselt, baut den Public-Strang
ab und ist überrascht.
- ✅ **Der reale Gewinn ist trotzdem groß, weil die meisten Dienste keinen öffentlichen Hostname brauchen.**
Kriterium: **hat der Dienst einen Client, der keinen interaktiven Access-Login kann?** (dieselbe Logik wie der
TP16.2-Constraint bei `vw`)
| Muss öffentlich bleiben (cloudflared) | Grund | Kann auf Mesh-only |
|---|---|---|
| `vw` | Bitwarden-Clients, harter TP16.2-Constraint | `pve`, `nautobot`, `portainer`, `grafana` |
| `hass` | Companion-App (400-Muster) | `pve-mcp`, `ha-mcp`, `gitea-mcp`, `ds-bf` |
| `n8n` | eingehende Webhooks von Dritten | `ccu`, `iobroker`, `z2m25` |
| `gitea` | Git-Clients / CI von außen — **zu prüfen** | |
→ Für die rechte Spalte ist der Mesh **funktional gleichwertig und operativ einfacher** (keine Ingress-Zeile,
kein Connector-Paar, keine HA-Frage). Sie erreicht man ohnehin schon per privater IP.
- 📌 **Zwei Nebeneffekte, die den Fall stärken:** (1) die **TP16-HA-Degradierung** (`bf-nas`/`bf-pve` je nur ein
Connector) verliert an Gewicht, sobald nur noch die vier Public-Dienste an cloudflared hängen — weniger Dienste
pro SPOF; (2) **ICMP + beliebiges UDP** gehen über den Mesh und über cloudflared grundsätzlich nicht (der
QUIC-Proxy war genau die Lücke aus TP16.1).
- ⚠️ **Grenzen, ehrlich benannt:** (a) der Mesh-Weg setzt **WARP auf dem Client** voraus — für Christophs eigene
Geräte gegeben, für Gäste/Fremdgeräte nicht; (b) **IPv6-CIDR-Routes brauchen MASQUE** und funktionieren mit
einem WireGuard-Device-Profil **nicht** — deckt sich mit dem v4-only-Entscheid aus TP16.1, wäre aber ein
Blocker, falls TP12 später v6 über den Mesh wollte; (c) es bleibt eine **Cloudflare-Abhängigkeit** — der Mesh
ersetzt cloudflared, nicht Cloudflare.
- **Vorgeschlagener Umsetzungsschnitt (klein anfangen, reversibel):** einen Dienst der rechten Spalte als Probe
aus dem CF-Ingress nehmen (Kandidat: `nautobot` — nur Christoph nutzt es, im Mesh-Log mit 62 Treffern der am
häufigsten per Mesh angefasste Dienst, und ein Fehlschlag kostet nichts Produktives), eine Woche fahren, dann
entscheiden. **Rollback = eine Ingress-Zeile zurück.**
- 📋 **Runbook: `docs/tp16.3-nautobot-mesh-only-runbook.md`** (2026-08-19) — der Schnitt oben, ausführbar
ausformuliert: Vorbedingungen (Mesh-Route, Zugriff über `10.40.20.30:8080` **mit WARP von außerhalb**),
Ingress-`PUT` über `CF_API_TOKEN` (**nicht** über den CF-MCP, der ist read-only), Rollback aus der
gesicherten Config.
- 🔴 **Der Abnahmepunkt, den man übersieht:** Nautobot ist nicht nur Web-UI, sondern die
**Deploy-Pipeline**. Nach dem Schnitt gehört ein Job-Lauf zur Abnahme, nicht nur ein Browser-Aufruf
— der Netmiko-Deploy kommt von `10.40.20.31` (`nautobot-worker`), nicht von `.20.30`.
- ⚠️ Beim Kürzen des `ingress`-Arrays muss die **Catch-all-Regel die letzte bleiben**, sonst greift
der Ingress für alle übrigen Hostnames nicht mehr.
- **Abh.:** TP16.1 ✅ (Mesh live), TP16 (Public-Strang + dessen HA-Degradierung), **TP16.2** (der `vw`-Constraint
liefert das Entscheidungskriterium). ⚠️ **Berührt TP5:** der Mesh-Verkehr braucht bei deny-by-default eine
explizite `forward`-Zeile — s. TP5 Etappe 2.
### TP26 — `/ip firewall filter` unter Drift-Kontrolle ▸ ✅ **DURCH (2026-08-17)**
> **Nachgetragen 2026-08-18.** TP26 existierte bis dahin nur als zwei Commits und eine `.rsc` — ohne
> Abschnitt in dieser Datei. Genau die Lücke, die eine frische Session nicht finden kann.
- **Auslöser:** vor TP26 gab es eine ComplianceRule auf `/ip firewall nat`, aber **keine** auf
`/ip firewall filter`. Die vielzitierte „72/72 grün" sagte über die Firewall damit **gar nichts** —
und das ausgerechnet, während TP5 den Deny-by-default vorbereitete. Der Drop wäre die einzige Regel
im Netz gewesen, die niemand überwacht.
- ✅ **Rule `ip-firewall-filter` angelegt**, `config_ordered=true` — bei Firewallregeln ist die
Reihenfolge semantisch, nicht kosmetisch. Ergebnis damals: **76/76**. Damit war TP5 Etappe 3 entblockt.
- ✅ **Zweiter Teil: die defconf-Firewall auf beiden APs abgeräumt** (`routeros/tp26-ap-firewall-cleanup.rsc`).
ap1/ap2 trugen je 12 Werksregeln — input-seal, fasttrack, ipsec-Zeilen, sogar „accept to local loopback
(for CAPsMAN)". Inert, weil ein reiner AP nichts routet. **Entscheid: abräumen statt ins Template
schreiben** — sonst erklärt man Werksmüll zur Absicht. Dasselbe Argument hat 2026-08-18 auch die alten
WLAN-Objekte auf den APs erledigt (TP28).
- 📌 **`--assert` hat dabei zwei Fehlschläge gefangen**, die der Job-Status als Erfolg gemeldet hätte.
Seitdem ist die Gegenprüfung am Gerät fester Bestandteil von `gc-manual-plan.py`.
- **Abh.:** TP22 ✅. Direkte Folge: TP5 E3/E4.
### TP27 — RouterBOOT-Firmware auf 7.24 nachziehen ▸ ✅ **DURCH (2026-08-17/18)**
> **Nachgetragen 2026-08-18.** Auch TP27 hatte keinen Abschnitt — die Arbeit steckt in Christophs Commit
> `b93514a` plus `routeros/tp27-routerboot-firmware-724.rsc` und
> `docs/routerboot-firmware-klickanleitung.md`.
- **Kontext:** Christoph hat alle vier MikroTik selbst auf **RouterOS 7.24 stable** gehoben
(core/branch 7.19.5 → 7.24, ap1/ap2 7.23.2 → 7.24). Offen blieb die zweite Hälfte jedes
MikroTik-Upgrades: die **RouterBOOT-Firmware** hängt danach zurück.
- 🔑 **Die Falle, wegen der es ein eigener TP ist:** `/system routerboard upgrade` **schreibt** die
Firmware nur — wirksam wird sie erst beim **nächsten Reboot**, und `current-firmware` zeigt bis dahin
weiter den alten Wert. Wer direkt nach dem Befehl prüft, hält den Schritt fälschlich für gescheitert.
Reihenfolge also: schreiben → gezielt rebooten → `current-firmware` gegenprüfen.
- ⚠️ **`/system routerboard upgrade*` steht auf der MikroMCP-Deny-Liste** (zu Recht — es schreibt den
Bootloader). Der Weg führt über Golden Config; der Reboot dann über `sshpass`/MikroMCP.
- ✅ **Abnahme 2026-08-18:** auf allen vier Geräten `current-firmware = upgrade-firmware = 7.24`.
- **Abh.:** keine. Berührt TP28 insofern, als CAPsMAN erst ab 7.2x das neue `/interface/wifi` mitbringt.
### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **KOMPLETT (2026-08-18)** — mit Nachtrag: **`slaves-datapath` fehlte, IOT+MEDIA lagen ~14 h**
- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
**der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
`-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
**statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
- **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
`/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
`/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
`vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
- 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
`torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
— der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
- ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
(30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
**frische DHCP-Lease pro SSID**, nicht die Registration-Table.
- **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
- ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
`last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
- ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@ "/system reboot"`.
- ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
`nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
→ Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
allein erreicht Nautobot nicht.
- ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
(`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
- 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
- ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
`configuration=`-Verweis, der beim Loeschen folgenlos blieb.
- 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
`/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
`/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
**nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
(der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
`discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
**Ergebnis: 92/92 compliant, 0 Drifts.**
- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<>`
im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
(`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
drei Bring-up-Dateien, die voneinander abgeschrieben sind.
- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
waren die Funkparameter aus TP25.
- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
`/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
(`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
`wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
`wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
*(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
„KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
die Punkte darunter mitziehen.**)*
- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
nur `admin logged in/out via api`).
- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
### TP-R — Router-Capabilities-Sweep ▸ FERTIG (`docs/research/routeros-capabilities.md`)
- **Leitsatz: nativ vor Container, NAS vor Router** (128MB-Flash-Limit).
- **Sofort nativ (~0 RAM):** RoMON · Netwatch · Scheduler + Auto-Backup/Mail · SNMP→externes Monitoring · **mDNS-Repeater** · spectral-scan · **802.11r/k/v (Fast-Roaming statt Mesh)** *(die Sweep-Fassung nannte hier CAPsMAN — das ist seit 2026-08-08 abgeschafft; 11r/k/v läuft standalone weiter, s. TP3/TP3.3)* · Back-to-Home/WireGuard.
- **Auf NAS:** FreeRADIUS(+LDAP) · NPM · icinga/Grafana · CF-Tunnel.
- **Weglassen:** LoRa (keine HW) · OpenFlow · CALEA · **ZeroTier (CPU-Fresser bis ~60 % → WireGuard)** · lokales `dude`.
- ✏️ **Korrektur Reverse-Proxy:** RouterOS **7.22+ HAT** einen nativen (`/ip/reverse-proxy`, ACME/SNI) — aber **ersetzt NPM NICHT** (kein WebSocket/gRPC/Sticky, nur IPv4-Backends, TLS frisst Router-CPU) → NPM bleibt auf NAS.
- Speist TP8/9/11/12.
---
## Reihenfolge / Abhängigkeiten
### 🎯 Kritischer Pfad (Stand 2026-08-19)
```
TP5 E1 ✅ ─► E2 ✅ ─► E2-Nachtrag ✅ ─► E3 Drop ✅ ─► E4 input+MGMT ✅ ─► E5 MAIN→IOT ✅ ─► TP6 (CAKE) ◄ Ziel
▲
ab hier ist alles frei
```
**TP5 ist am 2026-08-18 abgeschlossen.** Damit steht rechnerisch nur noch **TP6** auf dem kritischen Pfad —
🟠 **aber dessen Prämisse ist offen**: eine Messung von 2026-06-16 (`docs/call-quality-diagnosis.md`)
bescheinigt dem Netz bereits **A/A+** und hält CAKE für „marginal". Der nächste Schritt ist deshalb ein
**Bufferbloat-Test gegen den heutigen Edge**, nicht der Bau einer Queue. Fällt er gut aus, ist der
kritische Pfad **leer** und das Projekt in seinem Kern fertig — dann entscheidet Christoph, was das neue
Ziel ist. Alles andere läuft nebenher und blockiert nichts.
1. ✅ **TP5 Etappe 1–4 — komplett durch** (2026-08-16 bis 2026-08-18). Details im TP5-Abschnitt oben.
Die Wartezeit-Position, die hier bis 2026-08-17 als eigener Schritt stand, ist eingeloest: statt
eines mehrtaegigen Fensters wurde der Nachtrag aus einem **dreieinhalbstuendigen** Fenster
abgeleitet und der Drop mit Auto-Revert plus weiterlaufender Log-Regel abgesichert.
Die Vorbedingung D2 (iPhone `.30.201`) ist **gegenstandslos**: das Geraet hing dort bewusst zum
Anlernen eines IoT-Geraets (User 2026-08-17). Wer im IOT-SSID sitzt, ist im selben VLAN wie das
Geraet und beruehrt die `forward`-Kette gar nicht; fuer Ad-hoc-Zugriffe aus MAIN gibt es jetzt die
selbstverfallende Gruppe `jit-admin`.
2. 🟠 **TP6 — QoS/CAKE + Call-Quality-A/B-Test.** ~~Das eigentliche Projektziel; **keine offenen Blocker mehr**.~~
**Korrigiert 2026-08-19:** kein *Blocker*, aber eine offene **Prämisse** — der Satz oben und dieser
Punkt widersprachen sich seit dem 18. Richtig ist: der nächste Schritt ist eine **Messung**, kein Bau.
Runbook liegt bereit: `docs/tp6-praemisse-messrunbook.md`. Fällt sie A/A+ aus, wird TP6 **geschlossen
statt gebaut** — dann ist der kritische Pfad leer und das Projekt in seinem Kern fertig.
**Eine harte Reihenfolge-Regel dazwischen:** TP5 darf die **DNS-Bypass-Sperre erst schließen, wenn TP23s
Ausnahme-Oberfläche steht** — der bisherige Workaround „Client von Hand auf `1.1.1.1`" *ist* genau dieser
Bypass. Alles andere aus TP5 ist davon unabhängig und kann vorher laufen.
⚠️ **Und eine zweite, aus TP16.1/TP16.3:** der Drop braucht `cf-mesh` explizit — steht seit Etappe 2 als
`TP5-E2: mesh->LAN` / `LAN->mesh` drin. Ohne diese zwei Zeilen sperrt Etappe 3 den Fernzugang aus, also genau
den Weg, über den man den Fehler zurücknehmen wollte.
### Follow-ups nebenher (blockieren nichts)
- **TP25 — WLAN-Stabilisierung (NEU 2026-08-17):** Audit durch, **6 Funde** (2,4 GHz fährt 40 MHz statt der
profilierten 20; ap2 sendet auf 5 GHz mit **9 statt 18 dBm**, weil ch149 unter ETSI leistungsbegrenzt ist;
80 MHz kostet Randabdeckung; unzulässiger Channel-Override auf `w2-iot`; keine `access-list`; 5-Minuten-Takt
bei IoT-Trennungen offen). Umsetzung in 6 Schritten, alles via GC. **Übernimmt den offenen Rest von TP3.3.**
- **TP16.3 — cloudflared-Reduktion (NEU 2026-08-17):** Recherche durch. Der Mesh **ist** schon der private
Datenpfad (im TP5-Log mit 137 Zeilen belegt); Container braucht nur noch der **öffentliche** Ingress.
Vorschlag: `nautobot` als Probe aus dem CF-Ingress nehmen.
- ~~**DoH-Cloudflare auf `crs326-core`**~~ ✅ **erledigt 2026-08-18** (TP23 Stufe 1 = TP21.1 TODO 1).
- **TP24 — DC-USV** (seit 2026-08-16 eigener TP, aus TP15 herausgelöst).
- ~~**TP19 — VLAN1-Client-Retirement / cudy-Teardown**~~ ✅ **geschlossen 2026-08-18** (VLAN1-ARP = 0). Die zwei Reste sind dorthin verschoben, wo sie hingehören: tote CF-Tunnel → TP16, HA-Portainer `setup_retry` → TP11.
- ~~**TP3.3 — WLAN-Coverage-Kur**~~ → **in TP25 aufgegangen** (2026-08-17). 802.11k/v ist repariert (08-13);
Re-Walk + Dead-Spot-Kartierung laufen als Schritt 5 von TP25 — und zwar erst **nach** den Funkparameter-Fixes,
weil vorher gemessene Werte wertlos sind.
- **TP10 —** DDNS-Auto-Updater für `bf4.emsenhuber.net` (auf dem Core möglich, `device-mode` erlaubt scheduler).
- **TP14.4 —** CF-Access-Service-Token rotieren.
- **Doku-Aufräumen** (`docs/doku-cleanup-session.md`) — Punkte A–D am 2026-08-16 abgearbeitet, E teilweise.
📌 **Läuft in einer eigenen Session** — Übergabe: `docs/handoff-doku-session-20260818.md`.
⚠️ **Solange die läuft, `teilprojekte.md` nicht parallel bearbeiten** (in der Nacht auf den 18.
viermal committet, das kollidiert sonst).
### Nachgelagert / research-getrieben
**TP8** (Identity/802.1X) · **TP11** (Monitoring) · **TP12** (IPv6) · **TP13** (Botschafts-WLANs) ·
**TP16.2** (Härtung bypass-Dienste) · **TP18** (Garage-Objektspeicher) · **TP20.1** (Vaultwarden → Platform-PG) ·
**TP23** (DNS-Policy, s. Regel oben).
*(**TP16.1** stand hier bis 2026-08-17 fälschlich als offen — es ist seit demselben Tag ✅ abgeschlossen und
steht unten unter „Durch". Nachfolger ist **TP16.3**, das oben unter Follow-ups läuft.)*
### ✅ Durch (Fundament — nicht mehr planen, nur nachschlagen)
TP1 · TP2 · TP2.1 · TP3 (Rebuild 2026-08-08) · TP3.1 · TP4 (Rolle seit 08-08 auf dem Core) · TP7 · TP7.2 ·
TP10 (Kern) · TP14 (+14.1/14.2/14.2a/14.3/14.5) · TP15 · TP16 (Kern) · TP17 · TP19.1 · TP20 · TP21 · TP21.1 ·
TP22 · TP-R · **TP3.2** (08-17) · **TP5** (08-18) · **TP7.1** (08-18) · **TP19** (08-18) · **TP23 Stufe 1** (08-18) · **TP26** (08-17) · **TP27** (08-17/18) · **TP28** (08-17, GC-Modell 08-18). **Verworfen:** TP9 (Gateway-HA, User-Entscheid 2026-08-08). **Obsolet:** Omada-PVID-Fix
(User-Entscheid 2026-07-22 — „nie wieder aufgreifen").
📜 Historische Fassung dieses Abschnitts (bis 2026-08-14 fortgeschrieben, hier bewusst erhalten)
> 🎯 **AKTUELLER KRITISCHER PFAD (Stand 2026-07-16, überschreibt die Reihenfolge unten): TP20 (Platform-Postgres) → TP21 (Nautobot) — Ziel: fertig vor CRS326-Produktivsetzung, spätestens Montag 2026-07-20.** TP18 (Garage-S3) und die Komodo-Abhängigkeit wurden am selben Tag als Blocker entfernt/entkoppelt (s. TP20), um diese Deadline zu halten — beide werden später nachgezogen, nicht mehr vorher abgewartet. **➡️ Stand-Update 2026-08-08:** TP20/TP21-Plattform steht, Core-Cutover (TP21.1) + WLAN-Rebuild (TP3) + VLAN20-Migration (TP14) sind durch. Neuer Rest-Pfad: **TP22 (Device-Adoption + Compliance-Push) → TP5 (Deny-by-default) → TP6 (CAKE, das Ziel).** Kleine Follow-ups davor: DoH-Cloudflare auf core, USV (**TP24**). **➡️ Stand-Update 2026-08-13 (Abschluss): TP22 ist DURCH** (alle 5 Schritte; Fleet adoptiert, Deploy-Pipeline scharf, Datenmodell as-built, Secret-Handling entschieden) → **kritischer Pfad jetzt: TP5 (Deny-by-default) → TP6 (CAKE)**. Kleine Follow-ups davor: DoH-Cloudflare auf core, DC-USV (**TP24**), **Doku-Aufräumen (`docs/doku-cleanup-session.md`, vorbereitet 2026-08-14 — dieses Dokument beschreibt an mehreren Stellen noch die Topologie vor dem Core-Cutover)**. ➡️ **Neu 2026-08-14: TP23 (DNS-Policy & Self-Service) hängt an TP5** — und zwar in *beide* Richtungen: TP23 braucht TP5s Firewall, **TP5 darf aber die DNS-Bypass-Sperre erst schließen, wenn TP23s Ausnahme-Oberfläche steht** (der bisherige Workaround „Client auf 1.1.1.1" *ist* dieser Bypass). Ebenfalls in TP5 gezogen: **TP7.1** (SSDP-Reflector für die macOS-Sonos-App). ~~Kabel `core ether4`→`ether2` (Sideboard/TV)~~ **entfällt (2026-08-14):** gelöst wurde am **Port**, nicht am Kabel (User-Vorgabe „Patchfeld-Nummerierung wird nicht wegen einer Config geändert") — `ether4` steht seit 08-13 auf untagged VLAN40, End-to-End-Beweis am 08-14 erbracht, s. **TP21.1 Punkt 4**. Historisch: **TP22 war das nächste Arbeitspaket** — TP17 (Shelly-Migration) ist parallel dazu vollständig abgeschlossen (alle Shellys auf VLAN30, User-bestätigt). Grund für den Nautobot-First-Ansatz: Governance-Entscheid „Config exklusiv über Nautobot" (s. oben) macht Nautobot zur Voraussetzung für praktisch jede weitere Router-/Switch-Änderung. Die Punkte 3-4 unten (Omada-PVID-Fix etc.) sind **obsolet** (User-Entscheid 2026-07-22 — Omada spielt seit TP15-Korrektur keine Rolle mehr, nie wieder aufgreifen) — ganze Liste braucht mal eine Aufräum-Session, hier nur der aktuelle Fokus vorangestellt.
> ✅ **TP16 (CF-Tunnel Round-Robin/macvlan-Host-Isolation → reziprokes 3-Tunnel-HA)** KERN UMGESETZT 2026-07-10 (#1033 strukturell weg). Offen nur Access-Strang (WARP/`vw`) = user-gated. As-Built `docs/cf-tunnel-mapping.md §10`.
1. ~~**TP1 (GE8) → TP2 (Omada Controller).**~~ ✅ erledigt 2026-06-15.
2. ~~**TP4 (Router-Cutover L3-Infra + WAN-Edge).**~~ ✅ erledigt 2026-06-15 (L3) + 2026-07-01 (Edge-Cutover, FB physisch weg).
3. **Jetzt: Omada-PVID-Fix** — Omada Access-Profile korrigieren (PVID 10/20, untagNetworkIds bereinigen, sicher P3/P5/P6 zuerst), dann IP-Migration NAS/macmini/pve auf VLAN-Subnetze + DNS-Einträge updaten. **IP-Migration NAS/pve = TP14** (NAS ✅ 2026-07-10, pve 🔜 Session B).
4. **Dann:** TP5 (Deny-by-default fertigziehen — Sonos-/HA-/mDNS-Holes + WAN-Input-Seal stehen bereits; **TP5.1 = DNS-Redirect + Live-Torch für Low-Trust-Geräte/Airfryer hängt dran**) → TP6 (QoS/CAKE).
~~TP7 (mDNS)~~ ✅ 2026-07-02 (inkl. HA/vlan20), ~~TP3 (WLAN/CAPsMAN)~~ ✅ 2026-06-18, ~~TP3.1 (ap2-VLAN-Trunk)~~ ✅ 2026-07-01,
~~TP3.2 (P750W/Discovery)~~ ✅ 2026-07-02, ~~Sonos-Migration~~ ✅ 2026-07-02 (alle 10 auf VLAN 40).
⏳ **TP3.3 (WLAN-Coverage-Kur) offen** — Fortsetzung neue Session (`docs/wifi-p750w-coverage-and-map.md`).
5. ~~**TP10 (VPN)**~~ ✅ Kern durch (2026-06-27) + Edge-Cutover (2026-07-01) → DURCH. Offen nur: DDNS-Auto-Updater.
6. **Nachgelagert/Research-getrieben:** TP8 (Access), TP9 (HA), TP11 (Monitoring + CF-Tunnel-Track-B/C), TP12 (IPv6), TP13 (Spaß).
7. **Foundation (2026-07-14):** **TP15** (MikroTik-Switching / Omada-WLAN, 2× CRS326 bestellt) ∥ **TP20 (Platform-Postgres, single-site replication-ready)** → **TP21 (Nautobot Golden Config)** ∥ **TP20.1 (Vaultwarden→PG + 2-Site-HA)**. TP20 hängt an **TP18 ①** (Garage-Bucket für pgBackRest).
---
### TP29 — Anwesenheitserkennung fürs Energiesparen ▸ 💡 **IDEE, noch nicht auf dem kritischen Pfad** (angelegt 2026-08-21)
> **User-Formulierung (2026-08-21):** „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So
> kommen wir dem Ziel der Energieersparnis ohne Komfortverlust groß näher."
- **Ziel:** raumgenaue Anwesenheit als Eingangssignal für Heizung und Licht — sparen, **ohne** den
typischen Komfortverlust („erst kalt, dann warm, wenn jemand da ist").
- 🔎 **Der Entwurfsgedanke sind die komplementären Schwächen der drei Quellen** — keine trägt allein:
| Quelle | erkennt | erkennt **nicht** |
|---|---|---|
| **BLE über die Shellys** | grobe Zonen, billig, überall vorhanden | Menschen ohne getragenes Gerät; Raumgrenzen unscharf |
| **WLAN** (Registration-Table am Manager: Gerät, AP, Signal) | wer mit Telefon da ist, inkl. AP-Zuordnung | Menschen ohne Telefon; Raum nur so genau wie die AP-Abdeckung |
| **mmWave-Radar** | auch die **stillsitzende Person ohne Gerät** | braucht einen Sensor **je Raum** |
- ✅ **Was schon steht:** Shellys vollständig auf VLAN30 (TP17 ✅), HA produktiv — und mit
[`docs/tp6-monitor-core.sh`](tp6-monitor-core.sh) existiert seit TP25 bereits ein Sammler, der die
WLAN-Präsenzdaten am Manager mitschreibt (Gerät → AP → Signal, im Sekundentakt).
- ❓ **Offene Fragen für die erste Session:** Datenpfad ins HA (MQTT oder REST?) · Firewall-Regeln für
neue Sensorik (**TP5 ist deny-by-default**, jeder neue Dienst über eine VLAN-Grenze braucht eine
Regel) · Aufbewahrung und Privatsphäre der Ortsdaten · ob Radar-Sensoren auf `dirtyvader-iot` laufen
oder eigene Segmentierung brauchen.
- **Abgrenzung:** TP25 **misst** den Funk, TP29 **nutzt** ihn als Sensor — dieselbe Datenquelle,
anderes Ziel. Die Raumkarten aus TP25 sind für TP29 direkt verwertbar: sie sagen, wie genau die
WLAN-Quelle einen Raum überhaupt auflösen kann.
- **Abh.:** keine harten. Profitiert von TP25 (Abdeckung/AP-Zuordnung) und TP11 (Monitoring), wenn es
dahin kommt. **Kein Blocker für irgendetwas.**