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