# BF-Netzwerk — Teilprojekte-Archiv (abgeschlossene TPs) > **Was hier steht:** jedes Teilprojekt, an dem **keine offene Arbeit** mehr hängt — im **Volltext > und unverändert**, so wie es in `teilprojekte.md` stand. Nichts ist gekürzt oder umgeschrieben > worden; der Split am **2026-08-23** hat Text nur verschoben. > > **Was hier NICHT steht:** der aktuelle Stand. Der lebt in **[`teilprojekte.md`](teilprojekte.md)** — > Ist-Stand, kritischer Pfad und alle **offenen** TPs. Eine frische Session braucht **nur** jene > Datei; diese hier liest man, wenn man wissen will, **warum** etwas so entschieden wurde. > > ⚠️ **Nicht als Ist-Stand lesen.** Jeder Abschnitt beschreibt den Stand zum Zeitpunkt seines > Abschlusses. Wo ein Gerät seither ausgebaut oder eine Rolle umgezogen ist, steht das als datierter > Nachtrag drin (Projektkonvention: **fortschreiben, nicht überschreiben**) — aber die Wahrheit über > das heutige Netz ist das Gerät, dann `CLAUDE.md`, dann `teilprojekte.md`. **Warum überhaupt getrennt (User-Anstoß 2026-08-23: „die teilprojekte.md beginnt zu explodieren"):** die Datei war auf **2217 Zeilen** gewachsen, **49 % davon abgeschlossene TPs**. Das Wachstum kam nicht aus der *Zahl* der TPs, sondern aus der **Historie erledigter** — und die wird gelesen, wenn jemand *gezielt* danach sucht, nicht beim Einstieg. Bewusst **nicht** getrennt wurde nach TP (das wären ~35 Dateien): die Querbezüge, die das Dokument wertvoll machen — Reihenfolge, Abhängigkeiten, „TP23 hängt an TP5 in *beide* Richtungen" — leben von der Nähe im selben Text. Ein Schnitt pro TP hätte die teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. Begründung im Detail: [`doku-cleanup-session.md`](doku-cleanup-session.md) Punkt F. --- ## Inhalt | TP | Thema | Status bei Abschluss | |---|---|---| | [TP1](#tp1) | ALLNET Core-Switch finalisieren | ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt) | | [TP2](#tp2) | Omada Access-Switch + Controller | ✅ FERTIG (2026-06-15) | | [TP2.1](#tp2-1) | Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) | ✅ FERTIG (2026-07-16, User solo) | | [TP3](#tp3) | WLAN | 📜 VOLLSTÄNDIG HISTORISCH | | [TP3.1](#tp3-1) | ap2 VLAN-Trunk-Fix | ✅ Config live (2026-07-01) | | [TP3.2](#tp3-2) | WLAN Coverage / P750W-Discovery | ✅ ABGESCHLOSSEN (2026-08-17, User-Test erfolgreich) | | [TP3.3](#tp3-3) | WLAN Coverage-Kur | 📜 IN TP25 AUFGEGANGEN (2026-08-17) | | [TP4](#tp4) | Router-Cutover (hAP#1 = Router + WAN-Edge) | ✅ FERTIG (2026-07-01) | | [TP5](#tp5) | Firewall & Inter-VLAN-Policy | ✅ DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf · + E5 (2026-08-19) | | [TP6](#tp6) | QoS/CAKE + Call-Quality-A/B-Test | ✅ GESCHLOSSEN 2026-08-23 — ⚠️ Begründung darin von TP6.1 überholt | | [TP6.1](#tp6-1) | Saettigung vermeiden statt Bufferbloat bekaempfen | ✅ LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last | | [TP7](#tp7) | Media-Discovery (mDNS) für VLAN 40 | ✅ FERTIG (2026-07-02) | | [TP7.1](#tp7-1) | SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) | ✅ LÄUFT (2026-08-18) | | [TP7.2](#tp7-2) | HA↔Sonos static-host binding + VLAN1/GUEST-Discovery | ✅ FERTIG (2026-07-03) | | [TP9](#tp9) | Gateway-/Router-HA | ❌ OBSOLET (User-Entscheid 2026-08-08) | | [TP10](#tp10) | Multi-Site VPN | ✅ KERN DURCH (2026-06-27) | | [TP14](#tp14) | Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) | ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) | | [TP14.1](#tp14-1) | Session A: NAS fertigstellen | ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT | | [TP14.2](#tp14-2) | Session B: pve-Docker-LXC (.126) → VLAN20 | ✅ DURCH (2026-07-10) (ipvlan) | | [TP14.2a](#tp14-2a) | pve-mcp Single-Session-Fix + VLAN20-Host-DNS | ✅ (2026-07-10) | | [TP14.3](#tp14-3) | Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep | ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt | | [TP14.5](#tp14-5) | Session E: Freigabe-Gate → TP5-Firewall | ✅ ERFÜLLT (2026-08-08) | | [TP15](#tp15) | HW-Refresh: Switching + Router | ✅ ABGESCHLOSSEN (2026-08-08) | | [TP16.1](#tp16-1) | Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` | ✅ ABGESCHLOSSEN (2026-08-17) — privater Zugang via nativem WireGuard `cf-mesh` | | [TP17](#tp17) | Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` | ✅ KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30 | | [TP19](#tp19) | cudy-Teardown + VLAN1-Client-Retirement | ✅ GESCHLOSSEN (2026-08-18) | | [TP20](#tp20) | Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) | ✅ Plattform live (2026-07-16) | | [TP21](#tp21) | Nautobot Golden Config (config-as-code Control-Plane) | 🟢 Plattform + Datenmodell + Templates live | | [TP21.1](#tp21-1) | Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) | ✅ NETZ WIEDER LIVE (2026-08-08) — Aufräum-Todos **alle erledigt** (Stand 2026-08-23) | | [TP22](#tp22) | Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) | ✅ DURCH (2026-08-13) — Compliance heute **104/104, 0 Drifts** (live 2026-08-23) | | [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) | | [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) | | [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) | | [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) | Dazu am Ende: **[TP25 (Langfassung)](#tp25-lang)** — die Messchronik und die Funde A–E im Volltext. TP25 selbst ist **nicht** abgeschlossen und steht weiter in `teilprojekte.md`; nur seine 394 Zeilen Herleitung liegen hier. --- ### 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). ### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ ✅ **GESCHLOSSEN 2026-08-23 — CAKE gebaut, gemessen, wieder ausgebaut.** ⚠️ **Die Erklaerung darin ist von TP6.1 ueberholt: nicht DOCSIS, sondern fehlende Klassentrennung.** > 🔴 **ZUERST LESEN — diese Analyse ist teilweise widerlegt (2026-08-23, durch TP6.1).** > Unten steht, die ~30 ms entstuenden im DOCSIS-Uplink und seien unabaenderlich. **Sie sind es > nicht.** TP6.1 holt dieselbe Strecke unter derselben Saettigung auf **11,0 ms** — allein durch > Klassentrennung mit Prioritaet. Der Fehler lag in der **Methode** hier: Bulk und Messverkehr > kamen vom **selben Host**, damit gab es fuer CAKEs `triple-isolate` nichts zu trennen, und > blosses Shapen aendert keine Reihenfolge. **Weiter gueltig bleibt:** dass CAKE als *eine* > Queue ueber allem nichts brachte (dreimal gemessen), und der Befund zur Queue-Platzierung > (Interface-HTB tot, Global-HTB wirkt). Der Rest dieses Abschnitts ist als Messprotokoll zu > lesen, nicht als Ursachenanalyse. > 🔴 **A/B gemessen, gleicher Client, gleiche Methode, Minuten auseinander:** > > | Konfiguration | avg Latenz unter Upload-Last | > |---|---| > | ohne CAKE | **42,4 ms** | > | CAKE 8,5 Mbit (88 % der Linerate) | 43,3 ms | > | CAKE 7 Mbit (73 %) | 44,0 ms | > | **CAKE 5 Mbit (52 %)** | **44,6 ms** | > > idle 12,9 ms. **Selbst bei halber Linerate aendert sich nichts** — und dort muss die > Warteschlange zwingend im Router liegen, der Modem-Puffer also leer bleiben. Die ~30 ms > Zusatzlatenz entstehen damit **stromabwaerts**: im Arris/DOCSIS-Uplink, wo das > Request/Grant-Scheduling auf MAC-Layer sitzt und gar keine Warteschlange ist. > **Das kann keine Queue im Router beheben.** Nebenbefund: die max-Werte *steigen* mit > haerterer Drosselung (53 → 94 → 148 ms) — die eigene Queue wird laenger, die DOCSIS-Latenz > bleibt. ⚖️ **Empfehlung: TP6 schliessen — nicht mangels Nutzen, sondern weil das Werkzeug > nicht an die Ursache reicht.** Das ist ein staerkeres Ergebnis als „marginal“ von 2026-06-16. > > ✅ **Dass CAKE technisch funktioniert, ist dabei bewiesen, nicht angenommen:** Global-HTB > zaehlte 6.040.563 Bytes, und bei `cake-bandwidth=7000000` fiel der Durchsatz von 9,3 auf > **7,78 Mbit**. Sie zaehlt *und* drosselt. Es scheitert nicht am Bau. > > 🔴 **KORREKTUR der ersten Fassung dieses Abschnitts (ebenfalls 2026-08-23):** dort stand > „der CRS326 fuehrt keine Software-Queue“ — **das war falsch** und stuetzte sich auf zwei > eigene Testfehler. Richtig ist praeziser: > - **`/queue tree parent=` ist bei hw-offloadetem out-interface wirkungslos.** > MikroTik dokumentiert das: *„Interface HTB will not work correctly when the out-interface > is hardware offloaded and the bridge Fast Path is not active.“* Das war die echte Ursache > der ersten, wirkungslosen Queue auf `parent=vlan100`. > - **`/queue tree parent=global` + `packet-mark` wirkt sehr wohl** — die HTB Global sitzt im > IP-Stack, nicht an der Interface-Queue. Rezept: `mangle chain=postrouting > out-interface=vlan100 action=mark-packet` (postrouting laeuft **vor** src-nat, die > LAN-Quelle ist dort noch sichtbar), dann Tree auf `parent=global`. > - **Die Simple-Queue-„Gegenprobe“ auf `10.40.0.0/16` war untauglich:** die Reihenfolge ist > mangle postrouting → **src-nat** → HTB Global → simple queues. Zum Zeitpunkt der Simple > Queue ist die Quelle laengst die WAN-IP; das Target konnte nie matchen. > - `ipv4-fast-path-active: no` und `ipv4-fasttrack-packets: 0` belegen zudem: die CPU sieht > jedes Paket im Slow Path. Die Hardware war nie die Grenze. > Details und die restlichen Sackgassen: Memory `ref_queue_htb_platzierung_docsis`. > > ✅ **Was aus beiden Anlaeufen bleibt:** die **ComplianceRule `queue`** (Feature + Rule, > `match_config` = `/queue type` + `/queue tree`) schliesst die blinde Stelle aus §4.1 — > dieselbe, die TP26 fuer `/ip firewall filter` geschlossen hat. Wirksamkeit bewiesen: erst > 99/100 mit der fehlenden Queue als `missing`, nach dem Deploy 100/100. Zaehler jetzt > **25 Rules x 4 Geraeten = 100 Checks**. > ⚠️ **Am Geraet steht weiterhin die wirkungslose Interface-Queue** (`qos-wan-up` auf > `parent=vlan100`) — in der SoT, compliant, **aber ohne jede Funktion**. Sie gehoert entfernt, > sobald der Abschluss entschieden ist; bis dahin NICHT fuer aktives QoS halten. > > 💡 **Falls QoS hier je wieder Thema wird:** weder `hw-offload` opfern noch Chip-QoS ist > noetig — der Global-HTB-Weg funktioniert ohne beides. Es lohnt nur nicht. *(Vorheriger Kopf, als Messbefund weiter gueltig:)* 🟡 **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. ### TP6.1 — Saettigung vermeiden statt Bufferbloat bekaempfen ▸ ✅ **LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last** > **Der Gedanke kam von Christoph** beim TP6-Abschluss („dann koennten wir zumindest den Sync > von der NAS zurueckhalten“) — und er hat mein TP6-Urteil zu Recht angezweifelt. > > 🟢 **Abnahme, echte Saettigung, Kontrollmessung inklusive:** > > | gleiche NAS-Last, gleicher Messclient | avg | max | > |---|---|---| > | Priorisierung **aus** | **40,8 ms** | 53,4 ms | > | Priorisierung **an** | **11,0 ms** | 14,6 ms | > > **−73 %.** Und die Last war echt, nicht behauptet: `wan-up-total` fuhr **9,0 Mbit** > (das max-limit greift exakt), `wan-up-bulk` 8,87 Mbit mit **1581 Drops** — der Sync wurde > aktiv gebremst, waehrend `wan-up-rest` frei durchlief. `vlan100 tx = 9,0 Mbps`. > Die Kontrolle mit abgeschalteter Markierung war noetig, um auszuschliessen, dass die > Verbesserung bloss daher kommt, dass Bulk und Ping jetzt von verschiedenen Hosts stammen. > > 🔴 **Damit ist die DOCSIS-Erklaerung aus TP6 widerlegt.** Dort stand, die ~30 ms > entstuenden stromabwaerts im Uplink und seien unabaenderlich. Sie sind es nicht — sie > loesen sich auf, sobald der Verkehr in **getrennte Klassen** faellt. Der Fehler steckte in > der TP6-**Methode**: Bulk und Messverkehr kamen vom **selben Host**, also gab es fuer CAKEs > Host-Fairness (`triple-isolate`) nichts zu trennen — alles lag in derselben Klasse, und > Shapen allein aendert an einer Reihenfolge nichts. Was CAKE nicht schaffte, schafft > **explizite Klassentrennung mit Prioritaet**. Merkregel: nicht die Bandbreite ist das > Problem, sondern **wer vor wem drankommt**. > > **Was gebaut ist** (Template [11a], via GC, 104/104 compliant): > - zwei `mangle`-Regeln in `postrouting` (**vor** src-nat — nur dort ist die LAN-Quelle noch > sichtbar): NAS → `bulkup`, alles andere → `restup` > - `wan-up-total` (`parent=global`, `max-limit=9M`) mit zwei Klassen darunter: > `wan-up-rest` `priority=1 limit-at=6M` und `wan-up-bulk` `priority=8 limit-at=1M` > > **Drei Entscheidungen, die begruendet bleiben sollen:** > 1. **Prioritaet statt hartem Cap** — ein Limit haette die NAS auch nachts gebremst und > Bandbreite verschenkt (DSM drosselt von sich aus nicht, User-bestaetigt 2026-08-23). > `limit-at=1M` sorgt dafuer, dass der Sync nie ganz verhungert. > 2. **`src-address=10.40.20.10`, nicht VLAN20** — in INFRA haengen auch HA, Nautobot und > pve. Wer das je auf `10.40.20.0/24` erweitert, verlangsamt seine eigenen Werkzeuge. > 3. **`parent=global`, nicht `parent=vlan100`** — Interface-HTB ist auf diesem Geraet tot > (hw-offloadetes out-interface, s. TP6). > > ✅ **Governance-Vorbedingung erfuellt:** ComplianceRule **`ip-firewall-mangle`** angelegt > (`nautobot/ingest-mangle-rule.py`), **`config_ordered=True`** — die NAS-Regel muss vor dem > Catch-all stehen, sonst waere die Priorisierung **still** wirkungslos. Zaehler jetzt > **26 Rules x 4 Geraeten = 104 Checks**. > > 💡 **Offen, klein:** ob weitere Bulk-Quellen dazugehoeren (pve-Backups? Time Machine?). > Erst messen, ob sie ueberhaupt saettigen — die Regel dafuer ist eine Zeile. ### 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.~~ 🔴 **WIDERRUFEN 2026-08-25:** die Flagge war der Fehler — sie schreibt in `multicast-relay.py` Z. 630 die Quelle jedes SSDP-Pakets um, worauf Z. 691 die M-SEARCH beim Senden auf `eth1` **selbst verwirft** (`10.40.10.21` ist nicht im VLAN40-Netz). Der Hinweg lief damit **nie**; ausgebaut, Rückweg wird geroutet (`TP5: MEDIA->MAIN`). Stand + Merkregeln: Ist-Stand in `teilprojekte.md` und Gotcha in `CLAUDE.md`. - 🔑 **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 ✅. ### 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. ### 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.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. > 📦 **Nachtrag 2026-08-31:** die vier Skripte, die den Cutover begleitet haben > (`apply-core-cutover-l3`, `backup-core`, `restore-core`, `backup-ap1`), liegen jetzt unter > [`routeros/archiv-tp15-cutover/`](../routeros/archiv-tp15-cutover/README.md). Sie zeigten alle > auf tote Adressen — `backup-ap1.py` allerdings auf eine **lebende falsche** (`10.40.10.1` ist die > VLAN10-SVI des Core, nicht ap1) und hätte still das falsche Gerät gesichert. > ### 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.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`. ### TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` ▸ ✅ **KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30** — Geräte-PW *(war TP17.2, seit 2026-08-24 **TP30** Posten #3)* — `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). - **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`. - ➡️ **Nachtrag 2026-08-23: die Projektion „TP17.1 — Gen4 auf Thread/Matter" ist keine Projektion mehr.** Christoph hat sie aktiviert; sie steht als **offenes TP17.1** in [`teilprojekte.md`](teilprojekte.md), die Umsetzung liegt im HA-Projekt (`~/dev/ha-xstate/docs/shelly-gen4-thread-migration.md`). Der damals notierte Vorbehalt „Firmware-Verfügbarkeit + Thread-Border-Router offen" hat sich **halb** aufgelöst: der Border Router läuft (~30 Matter-Knoten), die **Firmware gibt es von Shelly aber bis heute nicht** — offiziell ist Gen4 nur Matter *over WiFi*, Thread nur über Dritt-Firmware. Auch der zweite Vorbehalt gilt unverändert: **Matter = neue Entities**, der Konflikt mit „HA-Entities unverändert" ist real und in der Übergabe ausgeführt. - 📊 **Bestandszahl, live 2026-08-23:** 22 der 26 Shellys erreichbar — **12 Gen4**, 8 Gen3, 2 Gen2. Die „~11 Gen4" aus der Schätzung von 2026-07-11 waren also nah dran. - 🔑 **Nachtrag 2026-08-23 — der IOT-PSK steht in der „Ziel"-Zeile oben im KLARTEXT, und das bleibt vorerst so (User-Entscheid).** Der Wert ist am selben Tag gegen den Manager geprüft worden und ist **noch der live konfigurierte**. Bewusst **nicht** redigiert und **keine** History-Rewrite: - Redigieren allein hilft nicht — die Zeile liegt seit `e76289c` (2026-07-11) in der Historie. - Ein `git filter-repo` wäre hier Aufwand ohne Gegenwert: **eine** getrackte Fundstelle, drei Commits, Remote **nur** `gitea.emsenhuber.net` (kein GitHub), **ein** lokaler Klon — der Wert hat die eigene Infrastruktur nie verlassen. - **Der wirksame Schritt ist die Rotation**, und die kostet heute ~26 Geräte. Sie ist deshalb bewusst **hinter TP17.1** eingereiht (Gen4 → Thread), s. Follow-ups in [`teilprojekte.md`](teilprojekte.md). ⚠️ **Wer diesen Abschnitt liest, behandelt den Wert als aktives Geheimnis** — nicht zitieren, nicht in Transkripte, nicht in Tickets. Zum Abgleich reicht ein Fingerprint (Länge + Stichproben), genau so ist er am 08-23 verifiziert worden. ### 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.** ### 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~~ → **alle fünf erledigt, letzter live gegengeprüft 2026-08-23** (s. Nachtrag am Ende) > **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. - ✅ **Nachtrag 2026-08-23 (Doku-Session): die Aufräum-Liste ist vollständig abgearbeitet** — deshalb steht TP21.1 jetzt im Archiv. Punkt für Punkt: **1** DoH-Gateway ✅ (TP23 Stufe 1, 08-18 — mit `verify-doh-cert=yes`; die Sorge „core hat kein CA" war zu pessimistisch, es brauchte **einen** Root, nicht das Bundle) · **2** intended config zurückgeschrieben ✅ (TP22-Re-Baseline 08-18; belegt durch **104/104 compliant, 0 Drifts**, live gegen die Nautobot-API geprüft am 08-23) · **3** branch-`192.168.88.1`-Rest ✅ **live gegengeprüft am 2026-08-23**: `/ip address print where address~"192.168.88"` auf `crs326-branch` liefert **leer** — der Bootstrap-Rest ist weg · **4** `ether4`/Sideboard ✅ (08-13/08-14, s.o.) · **5** AP-Neuprovisionierung ✅ (TP3-Rebuild 08-08, seit TP28 08-17 ohnehin überholt — beide APs sind reine CAPs). ### 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).** - ✅ **Nachtrag 2026-08-23 — Governance-Lücke geschlossen: ComplianceRule `wifi-access-list`.** `/interface wifi access-list` stand unter **keiner** Regel: Einträge dort erzeugten keinen Drift und ließen sich nicht als Transaktion führen (`gc-transaktion.py` verlangt eine ComplianceRule als `--scope`). Seit `docs/tp25-pin-s22.sh` schreibt jeder Messrundgang dort Regeln, und **TP25 Fund E** (`signal-range`-Boden) würde dauerhaft genau dort landen. Angelegt per `nautobot/ingest-wifi-access-list-rule.py` (idempotent), Template um den dokumentierten Abschnitt **[6c]** ergänzt (**nur Kommentare** — der Soll-Zustand ist *leer*, und das ist die Aussage). - 🔢 **Zählerkorrektur: 92 → 96, nicht 94.** Die Regeln gelten **plattformweit**, nicht gerätespezifisch: 24 Rules × 4 Geräte. Die Annahme „nur 2 Geräte betroffen" war falsch. - ✅ **Wirksamkeit bewiesen, nicht behauptet** (die TP22-Regel „Job-Status ist kein Wirksamkeitsnachweis"): Test-Eintrag mit einer nicht existierenden MAC gesetzt → **95/96 mit exakt dieser Zeile als `extra`** → Eintrag entfernt → **96/96, 0 Drifts**. Zusätzlich geprüft: `gc-transaktion.py` akzeptiert `--scope wifi-access-list` jetzt. - 🔎 **Nebenbefund beim Start:** ein `bridge-vlan`-Drift auf `crs326-core` stand noch in der Anzeige (ether3 als Trunk), obwohl der Sideboard-Rollback am 08-22 gelandet war. Das Gerät war live korrekt; die **Compliance vergleicht gegen das letzte Backup**, und das war älter als der Rollback. Mit dem Refresh verschwunden. Merkregel bleibt: bei einem Drift zuerst fragen, wie alt das Backup ist. ### 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` ▸ ↩️ **WIEDER GEÖFFNET 2026-08-30** > Der Volltext steht seit dem 2026-08-30 wieder in > [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter > Access-Point dazukommt. **Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder > geöffnet werden** (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur > ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen. ### 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. --- --- ## TP25 — Langfassung (Messchronik + Funde im Volltext, Stand 2026-08-22) > ⚠️ **TP25 ist NICHT abgeschlossen.** Der geltende Stand, das Offene (Fund F, die > 20-MHz-Entscheidung) und die Merkregeln stehen in [`teilprojekte.md` → TP25](teilprojekte.md). > Hier liegt **wortgleich** die Fassung, die bis zum 2026-08-23 dort stand — 394 Zeilen Herleitung: > die Messchronik (A)–(G) mit allen zwischendurch korrigierten Irrtümern, die Funde A–F im Volltext, > die beantworteten Fragen (Beobachtung 5 „CAPsMAN oder Nautobot GC?", Herkunft der ch149-Notiz, > `2ghz-g` für IoT, Deckenmontage-APs) und die Umsetzungsreihenfolge mit den Deploy-Gotchas. > > 📌 **Warum das nicht gelöscht wurde:** ein guter Teil des Werts steckt in den *widerlegten* > Erklärungen — zweimal stand hier eine plausible Ursache, die die nächste Messung gekippt hat > (ETSI-Band C bei Fund B; „ap1 ans Sideboard" bei (F)/(G)). Wer TP25 fortsetzt, muss sehen können, > welche Wege schon abgelaufen sind. ### TP25 — WLAN-Stabilisierung ▸ 🟢 **KERN ERLEDIGT (2026-08-22): Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). Offen nur noch **Fund F** (5:05-Takt IoT) und die Entscheidung **5 GHz auf 20 MHz**. Nicht mehr kritischer Pfad — 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. > 🔴🔴 **2026-08-22, Werkzeug-Fund mit Rückwirkung auf ALLE bisherigen Messungen: die > `cap-wifiN`-Nummern sind NICHT stabil.** Der Manager vergibt sie dynamisch in der > Reihenfolge, in der sich die CAPs anmelden — nach dem ap1-Rückbau auf `ether15` sind heute > `cap-wifi1/2` = **ap2** und `cap-wifi3/4` = **ap1**, also genau umgekehrt zu der festen > Tabelle, die in `tp6-marker/server.py` und `tp25-gehweg-auswertung.py` stand. > ⚠️ **Daraus folgt NICHT, dass die alten Auswertungen falsch sind** — der erste Verdacht in > diese Richtung war voreilig. Die Gegenprobe spricht dagegen: wendet man die *heutige* Karte > auf die Raumkarte vom 08-21 an, wird **Technik OG von ap1 mit −43 dBm versorgt** — in dem > Raum, in dem **ap2 steht**. Die alte Tabelle war zu ihrer Zeit also plausibel richtig und > ist erst durch das Umstecken falsch geworden. > ⇒ **Die Zuordnung gehört zum Messlauf, nicht zur Auswertungszeit.** `tp25-walk-messung.sh` > friert sie jetzt als `ap-map.txt` im Lauf ein; die Auswertung nimmt ausschließlich diese > Datei und lässt die Namen sonst roh (`cap-wifiN`) — unbeschriftet ist ehrlicher als falsch > beschriftet. **Läufe vor dem 2026-08-22 sind nicht rekonstruierbar**; wer ihre AP-Namen > zitiert, muss sie an der Physik prüfen (der AP im Raum liefert dort das stärkste Signal). > > 🆕 **Messaufbau neu gedacht (Christoph, 2026-08-22) — eine Runde statt zwei.** Das frei > roamende iPhone zeigt ohnehin, welchen AP ein normaler Client wählt; interessant ist der > **andere**. Das S22 wird deshalb per `access-list` automatisch auf den **Gegen-AP** des > iPhones gezwungen (`docs/tp25-pin-s22.sh`, Automatik in der Klick-Konsole auf Port 3031). > Pro Standort fallen so **zwei** Messpunkte an. Ein Doppelton meldet „beide angemeldet, > verschiedene APs" = weitergehen. > 🔴 **Die Falle dabei, live erlebt:** ein vom Pin **abgewiesener** Verbindungsversuch ist in > den Ping-Daten **nicht** von einem Funkloch zu unterscheiden, und der Manager protokolliert > ihn auf `info`-Ebene **gar nicht** (Beleg: zwischen `disconnected, access-list changed` und > dem manuellen Reconnect stehen vier Minuten ohne eine einzige Zeile). Android quittiert die > Abweisung mit „Verbindung fehlgeschlagen" und **wählt das Netz ab** — danach misst der Rest > des Rundgangs Stille. Die Konsole schlägt deshalb mit Ton, Vibration und Vollbanner Alarm, > sobald das gepinnte Gerät nirgends hängt, und schreibt nach 45 s vergeblicher Suche > `UNERREICHBAR=apX` — denn „der Gegen-AP trägt hier nicht" **ist das Messergebnis**. > ✅✅ **2026-08-22, 23:30 — DIE TERRASSE IST REPARIERT.** Erster Lauf mit beiden Ping-Strecken > ([`tp25-runde-20260822-2330-auswertung.md`](tp25-runde-20260822-2330-auswertung.md)): > **43 % Verlust / 20,4 s längste Lücke (08-21) → 9 % / 1,0 s** — gleiches Gerät, gleiche Rolle. > Das Anheben von ap2 auf 2,5 m hat gewirkt; 1,0 s liegt unter der Hörbarkeitsschwelle. > **Der Dead Spot, der TP25 seit dem 08-20 getragen hat, ist damit weg.** > 🔎 **Neuer Kernbefund: das Netz ist gut, die Redundanz ist es nicht.** Die frei roamende > Referenz hat in 17 von 21 Raumabschnitten **0 % Verlust**; der Gegen-AP dagegen liefert > Schlafzimmer −84,6/38 %, Bad −82,3/24 %, Küche −82,0/23 %, Terrasse −78,7/19 %. Fällt ein AP > aus, ist die halbe Wohnung im Zustand „verbunden, aber tot". > ⇒ **Dritter AP nach der eigenen Randbedingung derzeit NICHT begründet** — er kaufte Redundanz, > keine Abdeckung. Das ist eine andere Frage und Christophs. > ⇒ **Fund E weiter abgestuft:** der Sticky-Client vom 08-20 reproduzierte sich nicht (Treppe: > sauberer Wechsel ap2→ap1, 0 % über 57 s). **Nicht umsetzen, bevor ein Sticky-Fall erneut > gemessen ist.** > ⚠️ **Korrektur zum 22:43-Lauf:** „Küche ist ein Loch für beide APs" gilt nur für die äußerste > Ecke — im normalen Aufenthaltsbereich hat die Referenz dort **1 % Verlust bei −71 dBm**. > ▶️ **Start ohne Werkzeugkette: `bash docs/tp25-runde.sh [name]`.** Legt den Lauf unter > `docs/messdaten/tp25-runde-/` an, friert die AP-Karte ein, startet Konsole (Port 3031) > und Messung, sagt die **vom Interface gelesene** Adresse fürs Telefon an und löst beim > Beenden (Ctrl-C) den Pin wieder. Auswertung danach mit `tp25-gehweg-auswertung.py --dir …`. > ⏸️ **Stand bei Sitzungsende 2026-08-22: Rundgang noch nicht gegangen.** Pin gelöst, > Automatik aus, `crs326-core` unverändert (die `access-list` steht unter keiner > ComplianceRule, es gibt also nichts zu committen). Erster Messwert nebenbei: am Arbeitsplatz > iPhone **ap2 −68**, S22 auf ap1 gezwungen **−77…−78**; ohne Pin geht das S22 sofort auf > **ap2/5 GHz −71**. > 📋 **Übergabe: [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md).** > ✅ **Transaktion `TEST-ether3-trunk-ap1-sideboard` am 2026-08-22 zurückgerollt und geschlossen** > (Scope `bridge-vlan`) — `ether3` ist wieder untagged VLAN40, Compliance zurück auf 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.