Files
bf-infra/docs/teilprojekte-archiv.md
Christoph Emsenhuber a38ed0fe1d TP41 (✅) + TP43 (❌) + TP42 (✅) ins Archiv; TP41-Titel entschärft
- TP41 abgeschlossen: Gelis WARP via Android eingerichtet (letzter Blocker weg)
- TP43 verworfen: 420 = public CF-Worker, kein BF-Split-Horizon nötig; keine Altlasten
- TP42-Prosa nachgezogen (war closed in Gitea, Prosa stand noch in teilprojekte.md)
- Status-SSoT-Änderungen in Gitea gesetzt (#30 done, #32 dropped, #31 title-Fix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 12:19:51 +02:00

271 KiB
Raw Blame History

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 — 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 Punkt F.


Inhalt

TP Thema Status bei Abschluss
TP1 ALLNET Core-Switch finalisieren ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt)
TP2 Omada Access-Switch + Controller ✅ FERTIG (2026-06-15)
TP2.1 Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) ✅ FERTIG (2026-07-16, User solo)
TP3 WLAN 📜 VOLLSTÄNDIG HISTORISCH
TP3.1 ap2 VLAN-Trunk-Fix ✅ Config live (2026-07-01)
TP3.2 WLAN Coverage / P750W-Discovery ✅ ABGESCHLOSSEN (2026-08-17, User-Test erfolgreich)
TP3.3 WLAN Coverage-Kur 📜 IN TP25 AUFGEGANGEN (2026-08-17)
TP4 Router-Cutover (hAP#1 = Router + WAN-Edge) ✅ FERTIG (2026-07-01)
TP5 Firewall & Inter-VLAN-Policy ✅ DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf · + E5 (2026-08-19)
TP6 QoS/CAKE + Call-Quality-A/B-Test ✅ GESCHLOSSEN 2026-08-23 — ⚠️ Begründung darin von TP6.1 überholt
TP6.1 Saettigung vermeiden statt Bufferbloat bekaempfen ✅ LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last
TP7 Media-Discovery (mDNS) für VLAN 40 ✅ FERTIG (2026-07-02)
TP7.1 SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) ✅ LÄUFT (2026-08-18)
TP7.2 HA↔Sonos static-host binding + VLAN1/GUEST-Discovery ✅ FERTIG (2026-07-03)
TP9 Gateway-/Router-HA ❌ OBSOLET (User-Entscheid 2026-08-08)
TP10 Multi-Site VPN ✅ KERN DURCH (2026-06-27)
TP14 Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08)
TP14.1 Session A: NAS fertigstellen ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT
TP14.2 Session B: pve-Docker-LXC (.126) → VLAN20 ✅ DURCH (2026-07-10) (ipvlan)
TP14.2a pve-mcp Single-Session-Fix + VLAN20-Host-DNS ✅ (2026-07-10)
TP14.3 Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt
TP14.5 Session E: Freigabe-Gate → TP5-Firewall ✅ ERFÜLLT (2026-08-08)
TP15 HW-Refresh: Switching + Router ✅ ABGESCHLOSSEN (2026-08-08)
TP16.1 Private Access via native Cloudflare Mesh (WireGuard) auf crs326-core ✅ ABGESCHLOSSEN (2026-08-17) — privater Zugang via nativem WireGuard cf-mesh
TP17 Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + shelly-mcp ✅ KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30
TP19 cudy-Teardown + VLAN1-Client-Retirement ✅ GESCHLOSSEN (2026-08-18)
TP20 Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) ✅ Plattform live (2026-07-16)
TP21 Nautobot Golden Config (config-as-code Control-Plane) 🟢 Plattform + Datenmodell + Templates live
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 Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) ✅ DURCH (2026-08-13) — Compliance heute 104/104, 0 Drifts (live 2026-08-23)
TP26 /ip firewall filter unter Drift-Kontrolle ✅ DURCH (2026-08-17)
TP27 RouterBOOT-Firmware auf 7.24 nachziehen ✅ DURCH (2026-08-17/18)
TP28 WLAN über CAPsMAN auf crs326-core ↩️ wieder geöffnet 2026-08-30 (ap3) — Volltext in teilprojekte.md
TP-R Router-Capabilities-Sweep FERTIG (docs/research/routeros-capabilities.md)
TP39 LXC-Hygiene pve: Resolver-Altlasten, networking.service, DB-Reverse-Lookup ✅ ABGESCHLOSSEN (2026-09-01) — pve+NAS-Sweep grün
TP34 Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell
TP44 Cloudflare-Estate als Dokumentations-SSoT in Nautobot ✅ DURCH (2026-09-10)
TP41 CF-Access-Reparatur + Zero-Trust-Konsolidierung ✅ ABGESCHLOSSEN (2026-09-10) — Gelis WARP via Android eingerichtet, letzter Blocker weg
TP43 dw-Standort-DNS-Uplink (site-spezifische Overrides) ❌ VERWORFEN (2026-09-11) — 420 ist public CF-Worker, kein Split-Horizon nötig; Zugang via WARP+CF Access
TP42 Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net ✅ DURCH (2026-09-02) — .bf.lan raus, .16 abgebaut, Compliance 140/140

Dazu am Ende: TP25 (Langfassung) — 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=<token> (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=<interface> 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 §5.

idle / C0 Upload-Last Download-Last
Durchlauf 1 (ohne Call) 12,9 ms 60,4 ms 41,3 ms
Ort 1 Küche (mit Call) 13,2 ms 42,8 ms 37,1 ms
Ort 2 Terrasse (mit Call) 12,8 ms 40,4 ms 36,3 ms
Referenz 2026-06-16 12 ms 17,5 ms nie gemessen

Linerate erstmals dokumentiert: Upload 9,6 Mbit · Download 54,6 Mbit. Der Anstieg liegt in der Senderichtung bei +28 bis +47 ms und damit über der §3-Schwelle von ≲30 ms → formal Fall B, CAKE ist begründet. 🔴 Aber: durch alle sechs Lastphasen lief ein Telegram-Videocall, und es wurde kein einziger Problem-Button gedrückt. Messbar ja, wahrnehmbar nein. waveform gibt zeitgleich Grade A — kein Widerspruch, sondern ein kürzerer Test (Erklärung in §5.3). Empfehlung: TP6 bleibt offen und begründet, hat aber niedrigere Priorität als TP25 — was die Calls tatsächlich zerreißt, sind die WLAN-Übergaben, und die repariert keine Queue. Wenn gebaut wird: erst /queue ins GC-Template + ComplianceRule (§4.1), dann die Queue, dann dieselbe Messung als A/B-Nachweis. 📋 Eigene Session, Einstieg: docs/handoff-tp6-session.md (Übergabe 2026-08-19), Arbeitsplan: docs/tp6-praemisse-messrunbook.md. Abbruchkriterium steht vor der Messung fest.

  • 🔴 Der Widerspruch, der zuerst aufzulösen ist: dieser TP wird seit Monaten als „DAS Ziel" geführt — aber docs/call-quality-diagnosis.md hat den Bufferbloat bereits gemessen und kommt zum gegenteiligen Schluss. Wörtlich in dessen Ergebnistabelle: CAKE / QoS | kein Bufferbloat-Spielraum | ❌ nicht deployt (wäre marginal), und im Kopf: TP6 „hat sich als Nicht-QoS-Problem entpuppt". Messwerte (2026-06-16): 3 Flows = 10,3 Mbit Upload gesättigt · idle 12 ms · Vollast avg 17,5 ms / max 26 ms → A/A+. Bei solchen Zahlen holt CAKE nichts mehr.
  • ⚖️ Was das relativiert: die Messung stammt aus der FritzBox-Ära. Seither ist der Edge komplett getauscht (Arris im Bridge-Mode, NAT und Queuing liegen auf crs326-core). Die Leitung ist dieselbe, das Pufferverhalten des neuen Edge ist ungemessen.
  • ✅ ERLEDIGT 2026-08-20/21 — gemessen statt vermutet (die frühere Fassung dieser Zeile verlangte genau das): der Bufferbloat-Test gegen den heutigen Edge ist gefahren, dreimal, davon zweimal mit laufendem Videocall an verschiedenen Orten. Es ist schlechter geworden als 2026-06-16 (60,4 statt 17,5 ms avg unter derselben Sättigung), die Ursache ist damit neu und liegt nicht an der Core-CPU (~10 % unter Upload-Last). Zahlen und Einordnung: Runbook §5.
  • 🔎 Fasttrack ist auf dem Core faktisch tot — und das ist kein Entwurf, sondern ein Unfall (Befund 2026-08-18): die Regel defconf: accept est,rel,untracked steht vor defconf: fasttrack, also wird Fasttrack nie erreicht. Live: 32.979.171 Pakete auf der accept-Regel, 0 auf der Fasttrack-Regel, 0 von 445 Verbindungen fasttracked.
    • Herkunft: ap1 hatte die Reihenfolge vor dem Cutover richtig (Fasttrack zuerst, wie im MikroTik-defconf). Umgedreht wurde sie beim handgebauten Core-Aufbau (TP21.1).
    • Für TP6 ist das günstig: Fasttrack umgeht die Queues, CAKE braucht sie — ein Konflikt, den man sonst erst auflösen müsste, existiert hier gar nicht.
    • ✅ ENTSCHIEDEN (Christoph, 2026-08-19): der Zustand bleibt — und gilt ab jetzt als Absicht, nicht als Unfall. Begründung: bei ~10 Mbit WAN kostet der Verzicht nichts (die Core-CPU trägt den Forward-Pfad ohnehin, 32,9 Mio Pakete sind der Beleg), und für CAKE ist er die bequemere Ausgangslage. Damit ist auch die TP21-Warnung „das Re-Baseline hat den Fehler zum Soll erklärt" aufgelöst: die Reihenfolge in der SoT ist jetzt gewollt. Sie war es bei der Aufnahme nur nicht.
      • ⚠️ Gegenbedingung, unter der neu bewertet wird: wenn Inter-VLAN je auf Gigabit soll (NAS ↔ MAIN läuft geroutet über dieselbe 800-MHz-CPU), ist der Verzicht teuer. Die Messung dafür steht aus — und sie, nicht die WAN-Zahl, ist die Vorbedingung jeder Neubewertung.
    • ⚠️ Ungemessen ist der Preis: ohne Fasttrack läuft jedes geroutete Paket durch die volle Kette samt Conntrack, auf der Core-CPU. Für 10 Mbit WAN irrelevant, für Inter-VLAN-Transfers (NAS ↔ MAIN, Synology Drive) möglicherweise nicht. Das ist messbar und sollte gemessen werden, bevor jemand die Reihenfolge „repariert".
    • 🔴 Und es steht seit 2026-08-18 als ABSICHT in der SoT: das Re-Baseline (ingest-core-firewall.py --apply) hat die Reihenfolge unverändert nach Nautobot geschrieben, Rule ip-firewall-filter mit config_ordered=true. Entscheidung nötig: Reihenfolge korrigieren (Fasttrack lebt wieder) · Fasttrack-Regel entfernen (ehrlich: wir nutzen sie nicht) · so lassen und hier begründet stehen lassen. → entschieden am 2026-08-19, s. oben: so lassen. Damit stimmen SoT und Absicht wieder überein — bisher stimmten sie nur zufällig überein.
  • 📋 Runbook liegt bereit: docs/tp6-praemisse-messrunbook.md (2026-08-19). Es reproduziert die Methode von 2026-06-16 (3 Flows + 24 Pings idle/Last), misst erstmals auch die Download-Richtung (die 2026-06-16 nie gemessen wurde — beim asymmetrischen Kabelanschluss die wahrscheinlichere Problemseite), und trägt das Abbruchkriterium vor der Messung: A/A+ → TP6 wird geschlossen statt gebaut. Enthält außerdem den Bau-Kontext für den Fall B, damit er nicht neu recherchiert wird — vor allem: /queue fehlt im GC-Template und hat keine ComplianceRule (dieselbe blinde Stelle, die TP26 für /ip firewall filter geschlossen hat), eine handgesetzte Queue wäre also driftend und ungeschützt.
  • 📌 Nicht im Repo dokumentiert und deshalb Teil der Messung: die tatsächliche Liwest-Linerate. Die „~10 Mbit" stammen aus der Sättigungsmessung von 2026-06-16, nicht aus einem Tarifdatenblatt. Ohne diese Zahl ist die übliche CAKE-Vorgabe „85–95 % Linerate" geraten.
  • Abh.: TP5 ✅ (keine Blocker mehr). Vorgelagert: die Messung oben.

TP6.1 — Saettigung vermeiden statt Bufferbloat bekaempfen ▸ ✅ LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last

Der Gedanke kam von Christoph beim TP6-Abschluss („dann koennten wir zumindest den Sync von der NAS zurueckhalten“) — und er hat mein TP6-Urteil zu Recht angezweifelt.

🟢 Abnahme, echte Saettigung, Kontrollmessung inklusive:

gleiche NAS-Last, gleicher Messclient avg max
Priorisierung aus 40,8 ms 53,4 ms
Priorisierung an 11,0 ms 14,6 ms

−73 %. Und die Last war echt, nicht behauptet: wan-up-total fuhr 9,0 Mbit (das max-limit greift exakt), wan-up-bulk 8,87 Mbit mit 1581 Drops — der Sync wurde aktiv gebremst, waehrend wan-up-rest frei durchlief. vlan100 tx = 9,0 Mbps. Die Kontrolle mit abgeschalteter Markierung war noetig, um auszuschliessen, dass die Verbesserung bloss daher kommt, dass Bulk und Ping jetzt von verschiedenen Hosts stammen.

🔴 Damit ist die DOCSIS-Erklaerung aus TP6 widerlegt. Dort stand, die ~30 ms entstuenden stromabwaerts im Uplink und seien unabaenderlich. Sie sind es nicht — sie loesen sich auf, sobald der Verkehr in getrennte Klassen faellt. Der Fehler steckte in der TP6-Methode: Bulk und Messverkehr kamen vom selben Host, also gab es fuer CAKEs Host-Fairness (triple-isolate) nichts zu trennen — alles lag in derselben Klasse, und Shapen allein aendert an einer Reihenfolge nichts. Was CAKE nicht schaffte, schafft explizite Klassentrennung mit Prioritaet. Merkregel: nicht die Bandbreite ist das Problem, sondern wer vor wem drankommt.

Was gebaut ist (Template [11a], via GC, 104/104 compliant):

  • zwei mangle-Regeln in postrouting (vor src-nat — nur dort ist die LAN-Quelle noch sichtbar): NAS → bulkup, alles andere → restup
  • wan-up-total (parent=global, max-limit=9M) mit zwei Klassen darunter: wan-up-rest priority=1 limit-at=6M und wan-up-bulk priority=8 limit-at=1M

Drei Entscheidungen, die begruendet bleiben sollen:

  1. Prioritaet statt hartem Cap — ein Limit haette die NAS auch nachts gebremst und Bandbreite verschenkt (DSM drosselt von sich aus nicht, User-bestaetigt 2026-08-23). limit-at=1M sorgt dafuer, dass der Sync nie ganz verhungert.
  2. src-address=10.40.20.10, nicht VLAN20 — in INFRA haengen auch HA, Nautobot und pve. Wer das je auf 10.40.20.0/24 erweitert, verlangsamt seine eigenen Werkzeuge.
  3. parent=global, nicht parent=vlan100 — Interface-HTB ist auf diesem Geraet tot (hw-offloadetes out-interface, s. TP6).

✅ Governance-Vorbedingung erfuellt: ComplianceRule ip-firewall-mangle angelegt (nautobot/ingest-mangle-rule.py), config_ordered=True — die NAS-Regel muss vor dem Catch-all stehen, sonst waere die Priorisierung still wirkungslos. Zaehler jetzt 26 Rules x 4 Geraeten = 104 Checks.

💡 Offen, klein: ob weitere Bulk-Quellen dazugehoeren (pve-Backups? Time Machine?). Erst messen, ob sie ueberhaupt saettigen — die Regel dafuer ist eine Zeile.

TP7 — Media-Discovery (mDNS) für VLAN 40 ▸ ✅ FERTIG (2026-07-02) — docs/tp7-mdns.md

  • ✅ Built-in mDNS-Repeater aktiv — Live-Stand crs326-core (verifiziert 2026-08-14): mdns-repeat-ifaces=vlan10,vlan20,vlan30,vlan40,vlan90 → MAIN/INFRA/IOT/GUEST ↔ MEDIA. Reversibel (Feld leeren). 📌 bridge (=VLAN1) ist nicht mehr dabei — mit dem VLAN1-Retirement erwartbar und korrekt, war aber nie nachgezogen. Historische Fassung (ap1, bis 2026-08-08): …,vlan90,bridge — bridge=VLAN-1 seit TP7.2 2026-07-03. Realbestand: MAIN=iOS-Client + MacBook, INFRA=Home Assistant, MEDIA=10× Sonos (siehe docs/sonos-inventar.md).
  • ✅ Funktional durchgetestet (2026-07-02): iOS-Sonos-App (vlan10) und Home Assistant (vlan20) finden die 9 online-Sonos per _sonos._tcp.local. (Bonjour) automatisch. Household nicht mehr gesplittet.
  • ✅ TP5-Anschluss live: forward accept vlan10↔vlan40 + forward accept vlan20↔vlan40 + input accept udp/5353 auf vlan10/20/30/40 sind gebaut (mit „TP5:"-Kommentar), damit Discovery + Steuerung + Streaming ohne Umwege gehen.
  • ⚠️ SSDP/Cast (UDP 1900) bewusst NICHT gebaut (bräuchte Reflector-Container auf NAS/pve; hAP hat kein USB/Flash). Konsequenz: macOS-Desktop-Sonos-App nutzt SSDP → quert die VLAN-Grenze NICHT und findet die Sonos nicht. iOS-App (mDNS) und HA-Integration decken den Nutzungsbedarf ab; Spotify Connect läuft über Cloud.
  • Abh.: TP4 ✅, TP5 (Sonos-Holes) ✅ live.

TP7.1 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) ▸ ✅ LÄUFT (2026-08-18) — User-bestätigt: „sonos ist da!"

  • ✅ Umsetzung: eigener LXC 108 mcast-relay auf pve (Ubuntu 24.04, 1 Kern, 256 MB, onboot), zweibeinig: eth0 = 10.40.10.21 (VLAN10), eth1 = 10.40.40.21 (VLAN40), Default-Route nur über VLAN10, kein IP-Forwarding. Dazu core ether13 (pve-Port) um tagged vlan10, vlan40 erweitert (routeros/tp7.1-core-ether13-trunk.rsc). Software: alsmith/multicast-relay als systemd-Unit multicast-relay.service.
    • --noMDNS ist Pflicht: mDNS reflektiert bereits der Core (TP7). Zweimal zu reflektieren erzeugt Duplikate. --ssdpUnicastAddr 10.40.10.21 ist ebenfalls noetig — die Sonos antworten auf einen M-SEARCH per Unicast, und diese Antworten muessen zurueckgereicht werden. 🔴 WIDERRUFEN 2026-08-25: die Flagge war der Fehler — sie schreibt in multicast-relay.py Z. 630 die Quelle jedes SSDP-Pakets um, worauf Z. 691 die M-SEARCH beim Senden auf eth1 selbst verwirft (10.40.10.21 ist nicht im VLAN40-Netz). Der Hinweg lief damit nie; ausgebaut, Rückweg wird geroutet (TP5: MEDIA->MAIN). Stand + Merkregeln: Ist-Stand in teilprojekte.md und Gotcha in CLAUDE.md.
  • 🔑 Warum zwei Beine zwingend sind (Christophs Frage „vlan40 sollte keinen relay benoetigen?!?"): SSDP ist link-lokales Multicast mit TTL 1. Der M-SEARCH des MacBooks verlaesst VLAN10 nie, die ssdp:alive-NOTIFYs der Sonos verlassen VLAN40 nie. Ein Relay kann nur kopieren, was es selbst hoert — es muss Mitglied beider Broadcast-Domaenen sein. Routing hilft wegen TTL 1 nicht, und forward accept vlan10<->vlan40 ist laengst offen: es scheitert vor dem Verbindungsaufbau, an der Entdeckung. Dass AirPlay funktionierte, passt genau ins Bild — das ist mDNS.
  • 🎯 Beweis im Log: [Sonos Discovery] Relayed 268 bytes from 10.40.10.183:49358 on eth0 [ttl 64] to 255.255.255.255:1900 via eth1/10.40.40.21 — echte Suchrufe beider Macs (.10.183, .10.82).
  • ⚠️ Falle beim Selbsttest: ein M-SEARCH vom Relay-Host selbst wird nicht weitergereicht (Loop-Schutz: das Werkzeug ignoriert eigene Quelladressen). Mein synthetischer Test meldete deshalb 0 Antworten, waehrend die App bereits funktionierte. Aus dem Relay heraus laesst sich der Relay nicht pruefen — entweder am echten Client testen oder am Log mitlesen.
  • Keine Firewall-Arbeit noetig: der Relay spricht in beiden VLANs jeweils lokal; das trifft die forward-Kette nicht.
  • 📌 Status-Update 2026-08-14: Symptom vom User erneut bestätigt — iOS-App ✅, HA-Sonos-Panel ✅ (steuern/gruppieren), macOS-Desktop-App findet die Umgebung nicht. Damit ist das Bild vollständig: alles, was mDNS spricht, funktioniert; nur der SSDP-Weg fehlt. Der Fix wird Bestandteil des TP5-Pakets (gemeinsamer Test mit Deny-by-default statt zweimal dieselbe Strecke prüfen). ⚠️ Nicht als Firewall-Problem fehldiagnostizieren: forward accept vlan10↔vlan40 ist längst offen — es scheitert an der Discovery, nicht am Datenpfad.
  • Bedarf: macOS-Desktop-Sonos-App auf VLAN 10 findet die Sonos in VLAN 40 nicht (Discovery per SSDP UDP 1900, RouterOS reflektiert kein SSDP). iOS-App + HA decken den Alltag ab, aber User will die Desktop-App nutzen.
  • Lösung: kleiner SSDP-Reflector-Container mit Bein je Ziel-VLAN. Auf NAS oder pve (nicht am hAP, 128 MB Flash + kein USB). Kandidaten: bkupreck/ssdp-repeater, scyto/multicast-relay, oder eigenes Python-Skript.
  • Scope: vlan10 ↔ vlan40 (MacBook → Sonos). Optional vlan20 ↔ vlan40 falls HA je SSDP-Discovery brauchen sollte (aktuell nicht — HA nutzt zeroconf/mDNS).
  • Anschluss TP5: input accept udp/1900 auf den bedienten VLANs (falls Router-lokal reflektiert wird) bzw. Container-Port-Freigaben je Bein.
  • Alternative (verworfen): macOS-Client temporär in VLAN 40 hängen (unpraktikabel für Alltagsnutzung).
  • Abh.: TP7 ✅. Kein Blocker für TP5-Deny-by-default.

TP7.2 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery ▸ ✅ FERTIG (2026-07-03) — docs/sonos-inventar.md

  • Root Cause (widerlegt frühere Doku): HA-media_player.*-Sonos alle unavailable. HA sitzt nicht auf VLAN 20, sondern noch auf 10.40.41.105 (VLAN-1-Transfernetz, IP-Migration steht aus). Der mDNS-Repeater deckte VLAN 1 nicht ab, und die HA-Sonos-Integration ist SSDP-abhängig (manifest dependencies:[ssdp], soco 0.31.1) → quert die VLAN-Grenze nicht. Die alten „HA nutzt mDNS/zeroconf, fixed 2026-07-02"-Notizen waren falsch.
  • ✅ HA-Fix (discovery-unabhängig): sonos: media_player: hosts: mit allen 11 Sonos-IPs + advertise_addr: 10.40.41.105 in configuration.yaml (Backup .bak-sonos-20260703). advertise_addr fixt die UPnP-Event-Callbacks trotz HA-Multi-Homing (.105/172.16.0.18/docker). Core-Restart → alle 6 Online-Zonen idle/playing (Wohnzimmer-Push verifiziert = Callbacks OK). Terrasse .198 offline = kein Entity (kommt beim Einschalten). Kurzes Flapping direkt nach Restart = Settling, danach stabil.
  • ✅ Nebeneffekt: mit bridge im Repeater (s.u.) meldet HA jetzt „Automatic discovery is working, hosts not needed" — die statischen Hosts sind also Redundanz (Gürtel+Hosenträger), bleiben aber als Absicherung.
  • ✅ VLAN 1 (temp) + GUEST (perm) Discovery/Reach (User-Wunsch 2026-07-03):
    • mDNS-Repeater erweitert → mdns-repeat-ifaces=vlan10,vlan20,vlan40,vlan30,vlan90,bridge (VLAN-1-untagged=bridge).
    • Firewall: input accept udp/5353 vlan90 + forward accept vlan90↔vlan40 (ganzes MEDIA, beide Richtungen; User-Wahl). VLAN 1/bridge brauchte nichts (schon via TP10-seal-lan + permissiver Forward).
    • Bewusst in Kauf: flacher Repeater → Guests sehen auch MAIN/INFRA/IOT-mDNS-Announcements (Discovery-Leak; echte Verbindungen regelt TP5-Deny-by-default).
  • Offen/später: HA→VLAN 20 (IP-Migration nach Omada-PVID-Fix) → dann nur advertise_addr auf neue IP; macOS-Desktop-App weiter via SSDP-Reflector TP7.1. Abh.: TP7 ✅, TP5-Holes ✅.

TP9 — Gateway-/Router-HA ▸ ❌ OBSOLET (User-Entscheid 2026-08-08)

  • ❌ Verworfen (User-Begründung 2026-08-08, „ganz klar nein"): am crs326-core führt ohnehin nie ein Weg vorbei — er ist der unumgängliche L3-Durchleitungspunkt. Ein zweites Gerät / VRRP beseitigt diesen SPOF nicht, sondern baut nur zusätzliche Sollbruchstellen ein (2× Config-Sync für DHCP/DNS/Firewall/NAT, Split-Brain-Risiko, VRRP-Eigenfehler) → netto ein Zuverlässigkeits-Verlust, kein -Gewinn. Die Frage ist nicht „Aufwand vs. Nutzen", sondern „mehr Fehlerquellen einbauen?" → nein. DR-Weg = Config-Backup-Restore (+ ggf. AP-Reprovisionierung). VRRP-Skizze unten = nur noch historische Recherche.
  • „Was, wenn ap1 ausfällt?" → VRRP löst Gateway-Failover je VLAN ~gratis (kreuzweise Master/Backup). ⚠️ Haken (nicht Last): DHCP/Firewall/NAT/DNS müssen auf BEIDEN APs identisch gepflegt sein, sonst „Gateway up, Internet tot". Empfehlung: VRRP nur für MAIN/INFRA, sonst ap2 als Kalt-Ersatz + Backup. DHCP/DNS schon redundant (Split + 2 Resolver).
  • Abh.: TP4. (Research erledigt, s. TP-R + Config-Skizze im Report.)

TP10 — Multi-Site VPN ▸ ✅ KERN DURCH (2026-06-27) — beide WG live auf ap1-Hub (docs/tp10-vpn-migration.md)

Details/Cutover/Gotchas/Eckdaten → docs/tp10-vpn-migration.md (Single Source of Truth). Research: docs/research/vpn-comparison.md.

  • ✅ KERN DURCH (2026-06-27) + EDGE-CUTOVER (2026-07-01): beide AVM-Tunnel → 2× WireGuard mit Hub auf ap1 migriert. Live: peer fm (10.101.0.0/24, bidirektional, myfritz-Endpoint) und peer dw (10.95.10.0/24, CGNAT-Dial-in) mit frischem Handshake + Traffic. Public-Edge direkt — ap1 hört UDP/51821 auf vlan100 (Edge-Cutover 2026-07-01), keine FB-Portfreigabe mehr, kein Double-NAT. FM-IPsec gekillt, Gelis alter FB↔FB-WG sauber geswappt.
  • ✅ DW/Geli on-site verifiziert (2026-06-27): Geli auf 10.95.10.0/24 renumbert, FB-WG single 10.95.10.1/24, Keepalive 25, Endpoint bf4…:51821. End-to-End von macmini/Linz → Gelis 24/7-APs 10.95.10.246/.247 sauber (echte LAN-IPs, kein 10.255.255.x-NAT-Leak).
  • 💡 Root Cause (FM-PoC bewiesen): FB masqueradet WG, wenn die Interface-Address eine Transfernetz-IP ist → muss die FB-LAN-IP sein (AVM Proxy-ARP). War der Schlüssel zum sauberen LAN-LAN.
  • Hub: wg-hub (UDP 51821, Key P/bzt…, MTU 1412, keine IF-IP). Endpoint = DDNS bf4.emsenhuber.net:51821 (Cloudflare, DNS-only → Public-IP). Apply gebündelt in routeros/tp10-cutover-apply.rsc.
  • ✅ Permanenter Edge-Cutover durch (2026-07-01) — Details in TP4 (WAN via sw1 vlan100, ap1 = Public-Edge, WAN-Input-Seal live). BF-FB-eigenen WG-Server 51820 entfällt (FB physisch weg).
  • Offen: DDNS-Auto-Updater für bf4.emsenhuber.net auf NAS/pve, damit der A-Record der Public-IP folgt (falls Liwest sie tauscht). Aktuell manuell in Cloudflare gepflegt.

TP14 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) ▸ ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) — docs/nas-vlan20/RUNBOOK.md, Handoff docs/nas-vlan20/handoff/

  • ✅ ABGESCHLOSSEN — Migration komplett (Live-Check 2026-08-08 vom core: NAS .20.10, pve .20.100, Docker-LXC .20.126, platform-pg .20.20, Nautobot .20.30 alle 0% Loss). Alle Hosts + Container auf VLAN20. TP14.4 (CF-Access-Token-Rotation) = kleiner, nicht-blockierender Standalone-Follow-up (CF/TP11-Umfeld); TP14.5 war nie ein eigener Change (nur ein Reihenfolge-Gate) → erfüllt, TP5 damit entblockt.
  • ✅ NAS (DS923, ep14) migriert (2026-07-09/10): Docker von default_external (macvlan .41) auf ein vlan20-Macvlan (10.40.20.0/24, gw .1, parent ovs_eth0); DSM-Cutover .20.10; Portainer-Endpoint .20.10:9001. Root-Cause der Dauerfehler = Portainer Config-Only-Macvlan-Falle (Driver:null, kein Container-Attach) + Macvlan-Parent-Exklusivität (default_external muss weg für vlan20) + default_internal (172.18) weg. Container laufen auf .20.x.
  • ✅ CF-Ingress v116 (2026-07-10): 6 NAS-Origins .41→.20 (SSoT docs/cf-tunnel-mapping.md §8); vw 200 via Tunnel = pve→VLAN20-Routing steht (502 direkt nach Ingress-PUT = transient).
  • ⚠️ Abh./Reihenfolge: verzahnt mit Omada-PVID-Fix/IP-Migration (Reihenfolge #3); Sessions = Sub-TPs TP14.1–14.5, sequenziell A→B→C→(D jederzeit)→E, keine parallelen Branches (geteilte Live-Infra). Anschlussstatements: docs/nas-vlan20/handoff/README.md.
  • ✅ TP14.1 (Session A) durch 2026-07-10: alle ep14-Container auf vlan20 + pve-mcp neu; nur Omada-Inform-Adresse offen (Controller-UI-Task, s.u.).
  • ✅ TP14.2 (Session B) durch 2026-07-10: pve-LXC (400/docker) auf VLAN20 (.20.126, ipvlan parent=veth20); omada-mcp/adguard→.20.x; CF-Connector jetzt VLAN20 = #1033 gelöst. Nächste Session = TP14.3 (Session C, dormante Stacks + Referenz-Sweep ap1-DNS).
  • ✅ Netz-Abgleich 2026-07-11 (live via MikroMCP/pve-mcp): NAS .20.10, Mac mini .20.167, pve-LXC .20.126 alle reachable auf VLAN20 (+ Container .20.x populiert; iobroker .107/ccu .232/z2m .106/vw .127 jetzt auch VLAN20). Sonst in VLAN1 (bridge) nur noch: sw1 .11/sw2 .12/ap2-Mgmt .2 (Switch-/AP-Mgmt, interim bis MGMT-VLAN99) + Streu-Clients übers cudy-Alt-WLAN (MacBook .182, iPhone .186, SwitchBot .184, Sonoff) + die 16 VLAN1-Shellys (TP17). defconf/pool-v1-DHCP auf bridge läuft noch (VLAN1 aktiv).
  • ✅ pve-Host-Mgmt → VLAN20 (2026-07-13): letzter verbliebener Host-Umzug. vmbr0 bleibt VLAN1 .41.100 (ohne Default-Route, interim), neues vmbr0.20 (bereits vorbereitet, aber mit kaputter address 10.40.20.0/24 vorgefunden) korrigiert auf 10.40.20.100/24 → ifreload -a live (ifupdown2, kein Reboot, LXC 400 durchgehend unbeeinflusst) → Default-GW auf 10.40.20.1 geflippt. SSH-Key-Auth bestätigt auf .20.100. Nachgezogen: ap1-DNS (pve.bf.lan→.20.100; dabei Fund+Fix eines Alt-Bugs: pve-mcp.bf.lan zeigte noch auf .41.173 statt .20.173), CF-Tunnel bf-pve Ingress (pve+j44d6tua-SSH-Hostname .41.100→.20.100, PUT via CF_API_TOKEN, Smoke 200/200), CLAUDE.md, docs/ip-plan.md. ~/.claude.json war schon sauber.

TP14.1 — Session A: NAS fertigstellen ▸ ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT

  • ✅ Alle ep14-Container auf vlan20 redeployed + verifiziert running/.20.x (via Portainer-Stack-Redeploy, Env preserved, Volumes intakt): oc-stack(112 → prometheus .151/node-exporter .154/grafana .109), default(72 → hms-mqtt .124), nginx(101 → dashboard-web .148), oc-central(160 → qdrant .159), oc-charlie(158 → .162+cf), oc-johnny2(150 → .161+cf), gitea(111 → .157, DB .20.23:3306 erreichbar ✓), drawio(107 → .139, intern; CF-CNAME war eh gelöscht).
  • ✅ pve-mcp NEU als Stack (id 202, vlan20-only .20.173), verbunden mit Proxmox .41.100 (v9.0.11) — ⚠️ StreamableHTTP am Root-Pfad /, NICHT /sse (mcp-pve v0.4.8, MCP_TRANSPORT=http). ~/.claude.json mcpServers.pve-mcp NEU angelegt {type:http, url:http://10.40.20.173:3000/} (war vorher gar nicht drin) → wirkt nach Claude-Code-Restart.
  • ✅ Tunnel-Smoke (CF-Ingress v116 zeigte schon auf .20): gitea 200, grafana 302, vw 200 — Origins existieren jetzt real.
  • ⚠️ alertmanager .153: korrekt an vlan20, aber crash-loop wegen PRE-EXISTING Config-Bug (${TELEGRAM_CHAT_ID}-Literal in /etc/alertmanager/alertmanager.yml → int64-Parse-Fehler; Alertmanager macht kein Env-Substitution). NICHT migrationsbedingt, braucht echte chat_id. Out-of-scope.
  • ⚠️ openclaw-DB 10.40.41.122:3306 down (shared MariaDB, pre-existing; Bots kamen trotzdem hoch). DB_HOST-Env unverändert gelassen. OLLAMA_BASE_URL=.41.167=Mac-mini bleibt korrekt .41 (migriert nicht → kein Blanket-Env-.41→.20!).
  • ⚪ OBSOLET (2026-07-16) — omada-controller Inform-Adresse + sw2 re-adopt: ursprünglich vertagt (2026-07-10, Controller-Login schlug fehl). Gegenstandslos seit TP2.1: der Controller-Login-Ausfall eskalierte zum Werks-Reset von sw2, User hat daraufhin entschieden, sw2 dauerhaft standalone ohne Controller zu betreiben — und den Omada-Controller-Container samt omada-mcp am 2026-07-16 komplett gelöscht (overkill für 1 Switch auf der Abschussliste, s. TP15/CRS326). Kein Re-Adopt mehr vorgesehen.
  • 🐞 Gotcha gefunden: Portainer-Stack 111 (gitea) hatte den vlan20-Networks-Block auskommentiert (Vorsession-Artefakt) → Container landete auf gitea_default/172.18. Fix = Redeploy mit Disk-File docs/nas-vlan20/stacks/111-gitea.yml als Override.

TP14.2 — Session B: pve-Docker-LXC (.126) → VLAN20 ▸ ✅ DURCH (2026-07-10) (ipvlan)

  • ✅ ep2-Container auf vlan20: omada-mcp .20.176 (OMADA_BASE_URL→.20.175), adguard_bf1 .20.16, adguard_fm2 .20.17 — alle running/reachable (health 200, adguard 302). Transf. Files docs/nas-vlan20/stacks/{195-adguard,200-omada-mcp}.yml.
  • ✅ LXC 400 (docker) net0 → tag=20, ip=10.40.20.126/24, gw=10.40.20.1 (⚠️ Retag brauchte Christoph — Token claude@pam!claude hat kein VM.Config.Network; UI-Edit, dann Reboot via API mit VM.PowerMgmt). Portainer wandert auf https://10.40.20.126:9443.
  • 🐞 Gotcha: in-Container-Iface heißt jetzt veth20, NICHT eth0 (UI setzte Name analog net1=veth16) → Docker vlan20=ipvlan parent=veth20 (nicht eth0 wie im Runbook). cloudflared(host)/portainer(bridge) unberührt.
  • ✅ CF-Connector db713b30 jetzt in VLAN20 (Tunnel bf.emsenhuber.net healthy/8 conns) = löst TP11-Track-B/C (#1033-Zielbild). Ingress portainer .41.126→.20.126. Smoke: portainer/gitea/vw 200, grafana 302.
  • ✅ Refs nachgezogen: mcp/portainer/.env (PORTAINER_URL), ~/.claude.json (omada-mcp .20.176), CLAUDE.md, docs/ip-plan.md, docs/cf-tunnel-mapping.md. Next = TP14.3 (Session C, dormante Stacks + Referenz-Sweep ap1-DNS).
  • ✅ Live-Smoke re-verifiziert 2026-07-10: alle 6 VLAN20-IPs ping OK + dienstseitig grün — Portainer .20.126:9443 HTTP 200 (LXC 400 running, net0 veth20 tag=20 gw/ns=.20.1), omada-mcp .20.176→Controller .20.175 v6.2.10.17 (HTTPS :8043 200), pve-mcp .20.173 liefert Proxmox-Config, adguard .20.16/.20.17 HTTP :80 302→login (nicht HTTPS/:3000).

TP14.2a — pve-mcp Single-Session-Fix + VLAN20-Host-DNS ▸ ✅ (2026-07-10)

  • pve-mcp „Failed to connect": Ursache = mcp-pve v0.4.8 kann im HTTP-Transport nur eine MCP-Session (ein globaler StreamableHTTPServerTransport, sessionIdGenerator gesetzt) → erster Client nach Boot belegt den Slot, jeder weitere Init (neue Claude-Code-Session, claude mcp list-Healthcheck, Reconnect) = 400 "Server already initialized". omada-mcp NICHT betroffen (frische Session-ID pro Init → Per-Session-Transport). Fix = Upgrade auf 0.6.1 (Per-Session-Map, serverFactory() je Session, gleiche Env-Vars = Drop-in); Stack 202 neu deployed, Container .20.173 = 0.6.1, Connected to Proxmox VE 9.0.11, tools/list liefert pve_*, Claude Code ✔ Connected. 3× init hintereinander = 3× 200 mit verschiedenen session-ids (verifiziert).
  • Kollateral-Fund — VLAN20-Host-DNS kaputt (beide Docker-Hosts): nach dem VLAN20-Umzug zeigten NAS und pve-LXC /etc/resolv.conf noch auf totes .41.254 → docker pull ghcr.io scheitert (EAI_AGAIN / 5s-Resolver-Timeout; Egress zu ghcr-IP 140.82.x:443 = OK 31 ms, also nur Resolver tot, nicht Egress). Fixes: LXC .20.126 → pct set 400 --nameserver 10.40.20.1 (Christoph). NAS → DSM-„Allgemein"-DNS war .20.1, ABER die Netzwerkschnittstelle zog DNS per DHCP und überschrieb resolv.conf; Fix = Schnittstelle „DNS manuell" 10.40.20.1 (Christoph) → NAS pullt wieder direkt. Merkregel Synology: GUI-DNS ≠ was dockerd liest (Quelle /etc/resolv.conf, per DHCP überschreibbar). Workaround solange NAS-DNS tot: docker save(LXC, hat DNS) → gzip (191-MB-tar reißt am Portainer-Agent-Socket-Proxy „context canceled"; ~70 MB gz geht) → Portainer POST /endpoints/14/docker/images/load → Stack-Redeploy pullImage:false. Gotcha: laufende Container frieren den 127.0.0.11-Upstream zum Erstellzeitpunkt ein (Recreate nötig; für pve-mcp egal — Proxmox per IP .41.100). Memory ref_pve_mcp_singlesession_and_vlan20_dns.

TP14.3 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep ▸ ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt

  • ✅ Dormante Stacks bereitgestellt (2026-07-11): transform.py auf core(43)/openclaw(92)/openclaw2(97)/oc20260307(119)/openspeedtest(74)/speedtest_tracker(80)/technikscheiss(42) → .new.yml + .orig.yml in docs/nas-vlan20/stacks/ (gitignored). Nicht deployt (User: „im Bedarfsfall später", Container-Zukunft ungewiss). 42/43 waren default_internal → jetzt vlan20-only (DHCP-.20.x). ⚠️ Deploy-Caveats: externe Refs bewusst NICHT umgeschrieben (DB_HOST=.41.122 [pre-existing tote openclaw-DB, in 80+119], OLLAMA_BASE_URL=.41.129, REDIS_URL=.41.111 in 119); Kollision 43-core will .20.23 = gitea-DB-IP (nicht gleichzeitig deploybar).
  • ✅ Referenz-Sweep .41→.20 (2026-07-16, im Rahmen der TP19-Backup-Vorbereitung live gefunden + gefixt): ap1 /ip dns static hatte noch nas.bf.lan→.41.10 und docker.bf.lan→.41.126 stale — korrigiert auf .20.10/.20.126 (MikroMCP manage_dns_entry, dry-run+apply). Zusätzlich per User-Entscheid Omada-Controller + omada-mcp komplett gelöscht (overkill für 1 Switch auf der Abschussliste) → DNS-Einträge omada.bf.lan/omada-mcp.bf.lan entfernt statt migriert. fritzbox.bf.lan (totes Gerät, physisch weg seit 2026-07-01) ebenfalls entfernt. docs/ip-plan.md + CLAUDE.md nachgezogen.
  • ✅ ERLEDIGT/GEGENSTANDSLOS (live geprüft 2026-08-16): ~/.claude.json (omada-mcp-Eintrag verwaist), CF-Ingress-Rest (omada/omada-mcp-Hostnames, ungeprüft)
    • ~/.claude.json: omada-mcp ist nicht mehr als MCP-Server registriert (mcpServers enthält ihn nicht). Er steht nur noch in disabledMcpServers einiger Projekt-Scopes — ein reiner Merkposten „bewusst aus", keine Verbindungsversuche, kein Handlungsbedarf.
    • CF-Ingress: alle 9 Tunnel durchgesehen (cfd_tunnel + /configurations) — kein einziger omada-Hostname mehr. Sauber.
  • ↪️ Shelly-Geräte-Übersiedelung ausgegliedert → TP17 (die ~26 IoT-Shellys sind eine eigenständige WLAN-Migration, nicht Teil des Docker-Ref-Sweeps).

TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ ✅ ERFÜLLT (2026-08-08) — kein eigener Change

  • Gate „TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20)" ist erfüllt → TP5 (Deny-by-default) ist entblockt. Reines Reihenfolge-Gate, kein eigener Change.

📦 Nachtrag 2026-08-31: die vier Skripte, die den Cutover begleitet haben (apply-core-cutover-l3, backup-core, restore-core, backup-ap1), liegen jetzt unter routeros/archiv-tp15-cutover/. Sie zeigten alle auf tote Adressen — backup-ap1.py allerdings auf eine lebende falsche (10.40.10.1 ist die VLAN10-SVI des Core, nicht ap1) und hätte still das falsche Gerät gesichert.

TP15 — HW-Refresh: Switching + Router ▸ ✅ ABGESCHLOSSEN (2026-08-08) — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) und Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — docs/hw-refresh-requirements.md, Memory project_mikrotik_commit_nautobot_gc

🔌 Scope-Änderung 2026-08-16 (User-Entscheid): die DC-USV ist aus diesem TP herausgelöst und heißt jetzt TP24. Sie war der letzte offene Rest hier, hat mit Switching/Routing sachlich nichts zu tun und trägt eigene Entscheidungen (Elektrik, Batterie, Standort). TP15 ist damit inhaltlich zu. Vorherige Header-Fassung (Stand 2026-08-05, überholt): „🟢 CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23); Core-Tausch/L3-Umzug vorbereitet, Cutover-Config materialisiert (2026-08-05), Cutover = nächstes Wartungsfenster."

  • ✅ BRING-UP BEIDER CRS326 + BRANCH-TAUSCH gegen sw2 — DURCH (2026-07-23, Claude via REST unter Governance-Ausnahme #2). Ablauf/As-built:
    • Zugriffsweg (Henne-Ei gelöst): Erst-Reihenfolge war core zuerst (User-Korrektur — core hängt an sw1 und ist der L2-Pfad, über den branch überhaupt erreichbar wird; branch war isoliert). Zugriff auf die Werks-192.168.88.1 über temporäre ap1-Zweit-IP 192.168.88.2/24 + srcnat-Masquerade 192.168.88.0/24 (beide TP15-temp, nach Bring-up wieder entfernt — Rückweg nötig, weil die Werks-Box keine Route zu VLAN10 hat). Config dann komplett per REST (/system/script PUT+run), kein Winbox-Paste. Winbox-MAC-Connect scheiterte reproduzierbar („macconnection sync timeout" über WLAN/quer durch den Fabric — braucht selben Draht).
    • Identitäten am Gerät verifiziert (nicht nur Doku): core = sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76, Serial HKA0AREXJFM, PW X9…; branch = sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58, Serial HKA0AJJA52F, PW PHR… (ap2 hängt an branch ether24 — bridge-host bestätigt = definitiv branch). PWs in ~/.secrets/credentials.env.
    • branch = produktiv, volle SOLL-Portbelegung (User-manuell erhoben, maßgeblich): ether1 Uplink-Trunk→sw1 #8 (native VLAN1 + tagged 10/20/30/40/50/90/99, kein 100), ether2+ether8 = NAS 2×tagged-VLAN20 (zwei unabhängige Links), ether3 Drucker/VLAN10, ether4 Dock/VLAN10, ether5+ether6 Sonos/VLAN40, ether7 Mac mini/VLAN20, ether24 ap2-Trunk; ether9–23+sfp disabled. vlan-filtering=yes. Mgmt 10.40.99.13 (VLAN99) erreichbar über sw1 #8 (+ VLAN1-Temp .41.213 während Bring-up). Post-Swap-Abnahme grün: NAS .20.10, Mac mini .20.167 (VLAN20), Dock .10.194 (VLAN10), Sonos .40.191/.40.192 (VLAN40).
    • core = grundkonfiguriert, NICHT produktiv (bleibt an sw1 #12): identity/vlan-filtering/volle VLAN-Tabelle, ether1 Uplink, ether2 Temp-Downlink (war Config-Pfad für branch), Mgmt 10.40.99.11 (VLAN99, erreichbar erst wenn sw1 #12 99T trägt) + VLAN1-Temp .41.211. Port-Layout ab ether3 tentativ (final in Core-Swap-Session).
    • Beide in MikroMCP routers.yaml (crs326-core/-branch, ENV ROUTER_CRS326_* in ~/.claude.json — live erst nach MCP-Neustart, diese Session lief alles per REST-curl). Nautobot modelliert + GraphQL-verifiziert (nautobot/ingest-crs326-ports.py): branch Active + volle Ports + primary_ip4 .99.13; core Planned + tentatives Layout; sw1 verifizierte 16-Port-Belegung (löst „UNVERIFIZIERT"); sw2 → Decommissioning.
    • 🐞 Gotchas festgehalten: (1) Kabel ether3↔ether5 waren beim Umstecken vertauscht → ein AZ-Sonos landete als Access-VLAN10 in 10.40.10.190 und blutete via SonosNet nach VLAN40 (= Dual-Homing-Muster, project_dual_homing_slowness_incident) → Kabeltausch gefixt, beide AZ-Sonos jetzt sauber VLAN40 (.191/.192 bound). (2) RouterOS-Script-policy-Feld per REST wählerisch → weglassen (admin-Default = volle Policy). (3) Disable-Regex mit ^…$-Ankern griff nicht → [find name!="ether1" and …] nutzen. (4) REST-PUT-Antwort mit mehrzeiligem source ist kein sauberes JSON → ID separat via ?.proplist=.id holen. (5) Claude-Code-Auto-Mode-Classifier blockt Device-Config-Writes per Bash (Reads ok) → autoMode.allow-Regel in .claude/settings.local.json nötig (kann Claude sich nicht selbst setzen).
  • ✅ ERLEDIGT 2026-08-08 (Durchführung + Recovery s. TP21.1). 🔜 Als Nächstes (eigene Session, Übergabe): Core-Tausch = L3-Umzug (Routing/DHCP/DNS/CF-Mesh von ap1 → crs326-core) — Vollausfall-Fenster, s. docs/tp15-core-swap-session-handoff-prompt.md. Danach WLAN-Neuprovisionierung (TP3, CAPsMAN-Abbau). Finaler Mgmt-Renumber dann: branch .99.13→.99.12, core bleibt .99.11. ⚠️ Anders gekommen (Ist seit 2026-08-08, live bestätigt): core .99.1 · branch .99.2 · ap1 .99.11 · ap2 .99.12.
  • ✅ Core-Swap VORBEREITET (2026-07-23, reine Prep-Session, kein Netz-Eingriff): ap1s Live-L3-Config vollständig gelesen (MikroMCP) und als staged Config materialisiert → routeros/tp15-core-l3-staged.rsc (alles Betriebsumlenkende disabled=yes, im Cutover in einem Rutsch enable; ~50 Reservierungen + WG-Privkey bewusst erst im Fenster maschinell von ap1 geholt statt abgetippt). Runbook/Rollback/Verifikation im Plan. 3 Fenster-Start-Entscheide festgelegt: (1) WG-Hub (TP10 FM/DW-VPN) migriert MIT — war im Handoff-Prompt übersehen, hängt an ap1s Public-Edge und kann dort nicht bleiben; (2) Governance = frische Direkt-Ausnahme #3 (REST/MikroMCP), danach intended→Nautobot; (3) Rettungspfad = iPhone-Hotspot.
  • ✅ Nautobot-Modell + Golden-Config-Template für L3-Cutover vervollständigt (2026-08-05): crs326-core jetzt Rolle Router, alle VLAN-SVIs (vlan10/20/30/40/50/90/99/100) + Gateway-IPs (.1) modelliert; nautobot/mikrotik_routeros.j2 (Gitea bf-golden-config) erzeugt für crs326-core die vollständige staged L3-Konfig (Interface-Lists, GW-IPs, WAN-DHCP, DHCP-Server/Pools, DNS, NAT, Firewall, WG-Hub) und für alle anderen MikroTik-Geräte nur die Identität. Git-Sync + Generate Intended Config erfolgreich getestet; intended config für crs326-core liegt in Nautobot vor.
    • ✅ ap1-Cutover-Daten exportiert + in Cutover-Config eingebaut (2026-08-05): 54 statische DHCP-Leases aus ap1 (dhcp-v10/v20/v30/v40) nach routeros/ap1-leases-to-core.rsc überführt; WG-Hub-Privkey von ap1 geholt und in die lokale Cutover-Config routeros/tp15-core-cutover-intended.rsc eingesetzt (der WG-Privkey landet NICHT im Git-Template — Secret).
    • 🐞 Prep-Gotchas: (a) MikroMCP kann core noch nicht schreiben — ROUTER_CRS326_CORE_* im MCP-Prozess nicht geladen (jeder Zugriff „Missing environment variable") → vor dem Fenster MCP neu starten oder REST-curl gegen http://10.40.41.211/rest. (b) Mgmt-.99.11 tot (sw1 #12 trägt kein 99T), nur VLAN1-Temp .41.211 erreichbar. (c) WG-Hub: Privkey von ap1 mitnehmen (sonst FM/DW-Reconfig nötig). (d) core-vlan99 wird GW .1; Bring-up-.11 dadurch redundant (Fenster-Reconcile). (e) VLAN1 raus: Hybrid-VLAN1-Drops sw1 GE3/7/10 beim Umstecken einem echten VLAN zuordnen oder stilllegen.
    • ✅ WG-Endpoint-Frage geklärt (2026-07-23): Spokes adressieren den Hub über bf4.emsenhuber.net:51821 = CF-A-Record (proxied=false, TTL 120, aktuell 90.146.35.38). Kein Updater auf ap1 → nichts zu migrieren; bf4 folgt der IP manuell (offener TP10-Follow-up). Zieht core am WAN eine neue IP (Lease sticky, eher nicht), via CF_API_TOKEN nachziehen: bf4-Record + Access-Policy bf4-home-ip (90.146.35.38/32). Chance: core ist nicht device-mode=home → scheduler/fetch gehen → bf4-DDNS-Auto-Updater dort bauen (schließt TP10-Follow-up).
    • 📋 Session-Handover für die Cutover-Session: docs/tp15-core-swap-session-handover.md (Einstieg + Pre-Window-Checkliste + Kurz-Runbook; Original-Auftrag bleibt docs/tp15-core-swap-session-handoff-prompt.md).
  • 📦 2× CRS326-24G-2S+RM werden morgen (2026-07-17) geliefert, Produktivsetzung spätestens Montag 2026-07-20 (User-Entscheid 2026-07-16). Sequenzierung ggü. Nautobot geklärt: TP20+TP21 werden bis dahin fast-getrackt (TP18/Komodo als Blocker entfernt, s. TP20) — Ziel ist, dass Nautobot vor der CRS326-Produktivsetzung steht und die Switches direkt als Nautobot-verwaltete Geräte adoptiert werden, statt erst von Hand konfiguriert und später importiert. sw1/sw2-VLAN99-Umzug (TP19.1) läuft parallel dazu on-demand, unabhängig davon.
  • 🧭 Grundsatzentscheid (2026-07-14, korrigiert 2026-07-15): reines MikroTik-Ecosystem — Routing + Switching + WLAN. Omada bekommt KEINE Rolle (weder Switching noch WLAN — der ursprüngliche „Omada=WLAN"-Satz war ein Fehlschluss beim Dokumentieren, User-Korrektur 2026-07-15: „WLAN war NIE von Omada die Rede"). Begründung Switching: Projektziel #1 CAKE nur RouterOS/OpenWrt/OPNsense (Omada-Gateway kann kein CAKE) → Switching = MikroTik CRS. WLAN bleibt auf hAP ax² (/CAPsMAN — CAPsMAN am 2026-08-08 abgeschafft, Standalone aus der Golden Config; die Hardware-Aussage gilt), kein EAP-Kauf, Coverage-Fix bleibt Platzierungsfrage (TP3.3). Verworfene Vollvarianten (Omada-only, MikroTik-only) samt Begründung + Ansible/Nautobot-Recherche → Memory. Anlass der Korrektur: Omada-Controller↔sw2-Link beim VLAN20-Umzug (TP14.1) zerbrochen, versehentlicher Werks-Reset bei Rettungsversuch, fehlerhafter Wiederherstellungsversuch → sw2 wird standalone neu eingerichtet, s. TP2.1.
  • 🛒 Switches: 2× CRS326-24G-2S+RM bestellt (2026-07-14 — CSS326-Fehlbestellung storniert → CRS). 24×1G + 2×SFP+, lüfterlos, DC-input (USV-tauglich). Lösen ALLNET-Core (sw1) + Omada-ES208G (sw2, s. TP2.1 — Solo/Interim bis CRS-Tausch) + Cudy ab.
    • ✅ CRS = RouterOS-fähig → in-model: MikroMCP-Write + Nautobot Golden Config (TP21) managen auch die Switches, nicht nur den Router → config-as-code deckt den ganzen Kabel-Fabric. (SwOS-Boot bliebe als Fallback möglich, aber nicht genutzt.)
    • ⚠️ kein PoE (Sonoff/PoE-Verbraucher via Injektor, falls nötig).
  • 📶 WLAN bleibt hAP ax² — kein Hardware-/Ecosystem-Wechsel; die Hardware-Aussage gilt unverändert. ⚠️ Die Betriebsart hat zweimal gewechselt, letzter Stand ist CAPsMAN: CAPsMAN abgeschafft (2026-07-22/08-08, TP3-Rebuild → Standalone-APs) → wieder eingeführt am 2026-08-17 (TP28), Manager ist crs326-core, ap1/ap2 sind reine CAPs. Wer hier nur bis zum ersten Pfeil liest, baut auf einem überholten Stand auf. Coverage-Schwachstellen (P750W-Fall, Terrasse/Kanten) bleiben Platzierungs-/Roaming-Thema von TP3.3, nicht durch neue Hardware gelöst.
  • 🧭 Router-HW final (User-Entscheid 2026-07-22): kein RB5009. Kein Zukauf einer dedizierten Router-HW.
  • 🔄 KORREKTUR (User-Entscheid 2026-07-22, später am selben Tag): der CRS326-Core WIRD der neue L3-Router. Die frühere Fassung dieser Zeile („Router bleibt dauerhaft ap1/ap2, L3/CAKE/Firewall bleiben auf ap1") ist damit überholt. Routing, DNS und DHCP wandern vollständig von ap1 auf crs326-core; ap1/ap2 werden zu reinen Access Points. Auch der CF-Mesh-Endpoint terminiert dort (s. TP16.1, war schon vorher auf CRS326 retargetet — passt jetzt konsistent zusammen). Damit entfallen die device-mode=home-Einschränkungen des hAP (kein scheduler/fetch) für die Router-Rolle. Reihenfolge (User): erst Switches aufsetzen und in Betrieb nehmen, danach WLAN-Neuprovisionierung der APs. Betroffen/nachzuziehen: TP4 (Router-Rolle), TP5/TP6 (Firewall + CAKE landen auf dem CRS326), TP21 (Scope-Grenze).
  • 📍 CRS326 Live-Stand (2026-07-22, via MNDP von ap1): beide Geräte sind bereits am Netz und laufen — RouterOS 7.19.5, Werkszustand. crs326-core = MAC 04:F4:1C:B4:17:76 (software-id 4W04-URHT), crs326-branch = MAC 04:F4:1C:B4:15:58 (RGZJ-UK0T). ⚠️ Beide auf Werks-IP 192.168.88.1 im selben L2-Segment = IP-Konflikt → beim Bring-up nur eines nach dem anderen adressieren, Unterscheidung ausschließlich per MAC. Die frühere Notiz „Uplink-Port bislang ungenutzt/down" ist überholt. Standorte: crs326-core = „Technik UG" (virtueller Technikbereich im UG/Diele, KEIN Keller), crs326-branch = „Technik OG".
  • 🔌 PoE-Anschluss der APs (User-Vorgabe 2026-07-22): Verkabelung/Portbelegung bleibt sonst unverändert; ap1/ap2 sollen künftig aber am PoE-Port hängen. PoE selbst ist noch kein Thema, die Config soll es aber schon vorsehen.
    • ✅ Port = ether1. RouterOS nummeriert Interfaces ab 1 (ether1…ether5), nie ether0 — die Unix-Konvention eth0 gilt hier nicht. Auf dem hAP ax² ist ether1 der PoE-Port (live bestätigt: /interface/ethernet/poe listet ausschließlich ether1).
    • ⚠️ Aber: passives PoE 18–28 V, NICHT 802.3af/at. Ein normaler 802.3af/at-Switch oder -Injektor speist den hAP ax² nicht. Es braucht einen passiven PoE-Injektor (MikroTik-Standard). Dazu passend: der CRS326 hat gar kein PoE (bereits dokumentiert) → Strom kommt ohnehin aus einem Inline-Injektor zwischen Switchport und AP, nicht aus dem Switch.
    • ⚠️ Config-Konsequenz: ap1s Trunk liegt heute auf ether2 → wandert bei der Neuprovisionierung auf ether1. Und ether1 steht aktuell auf poe-out=auto-on (der hAP kann über denselben Port auch Strom abgeben) — beim Umbau auf „ether1 = versorgter Uplink" muss poe-out auf diesem Port off gesetzt werden, sonst speisen Injektor und AP gegeneinander.
  • 🔋 DC-USV → ausgelagert nach TP24 (2026-08-16). Inhalt unverändert gültig (MeanWell LRS-150-48 + DDR-120-48 + LiFePO4 20 Ah, ~€190, 48-V-Bus; die CRS326 haben passenden DC-Input) — steht ab jetzt dort, samt der offenen Entscheidungen (Mac mini/Inverter, Bus-Standort nach dem Core-Umzug, Batteriegröße, Shutdown-Signalisierung).
  • ✅ Standort-/Ablösungs-Mapping final (User 2026-07-22) — sauberes 1:1:
    Bereich heute wird abgelöst durch
    Technik UG (virtueller Bereich in der Diele, kein Keller) sw1 (ALLNET, 16 Ports) crs326-core (= neuer L3-Router)
    Technik OG sw2 (ES208G, 8 Ports) + ap2 + cudy2 crs326-branch
    Wohnzimmer Sideboard ap1 — (bleibt, wird reiner AP)
    Kapazität unkritisch: 16+8 belegte Ports heute gegen 2× 24 GbE + 2× SFP+ künftig. Vollständig in Nautobot gepflegt (alle 8 Devices haben einen Bereich).
  • 📻 cudy-Hardware (User 2026-07-22): beide sind Cudy WR3600E V1.0, Firmware 2.3.15-20250905-191956. cudy1 = Technik UG, am Core #11, aktiv als Übergangs-WLAN (elektrosmog.c, 10.40.10.246) — und Christophs aktueller WLAN-Pfad. cudy2 = Technik OG, außer Betrieb (Nautobot-Status Decommissioning), wird nicht reaktiviert.
  • ⚠️ Etappe 1 (Erstzugriff auf die CRS326) ist NICHT kritisch (User 2026-07-22): Werkszustand, Gerät steht griffbereit neben ihm → der IP-Konflikt auf 192.168.88.1 wird physisch/seriell aufgelöst, kein Lockout-Szenario.
  • ✅ Portplan-Widerspruch AUFGELÖST (User 2026-07-22): am Core ist #15 = ap1 und #11 = cudy1. Damit gilt die TP4-Variante (gi16 Arris/VLAN100 → gi15 Trunk → ap1 ether2); switch/cli/03-trunk-ap-ports.cli (gi1 = ap1 / gi2 = ap2) ist STALE — die Datei bezeichnet sich selbst als „provisorisch, 2026-06-09" und wurde vom Edge-Cutover am 2026-07-01 überholt. Nicht mehr als Quelle verwenden.
    • Zielport nach dem Tausch: ap1 wandert auf crs326-core Port 4 (User-Vorgabe). Gleichzeitig wandert ap1s Uplink geräteseitig von ether2 auf ether1 (PoE-Port, s.o.).
    • ⚠️ Nach wie vor NICHT vom Gerät gelesen: die restliche sw1-Belegung. switch/state-backups/sw1-running-config-20260716-*.txt sind 214-Byte-Fehlschläge (Incorrect User Name!!, toter claudecode-Login), switch/cli/running-PRE.txt ist Pre-TP1. gi16 (Arris) und gi8 (Trunk → sw2) stehen in Nautobot daher explizit als „UNVERIFIZIERT" markiert.
  • 🔴 Betriebliches Kernrisiko (Analyse 2026-07-22): der Core-Tausch ist KEIN Switch-Tausch, sondern ein Vollausfall. crs326-core ersetzt sw1 und wird gleichzeitig L3-Router — und an sw1 hängt auf #15 ap1, also Gateway, DHCP, DNS und der WAN-Trunk fürs gesamte Netz. Core-Tausch und L3-Umzug sind damit faktisch dasselbe Ereignis und brauchen ein geplantes Ausfallfenster. → Reihenfolge-Entscheid (User 2026-07-22): crs326-branch zuerst (gegen sw2, Technik OG), crs326-core in einer eigenen, späteren Session.
  • 🛟 Rettungspfade liegen günstig auf verschiedenen Switches: Christophs Notebook hängt per WLAN über cudy1 → Core #11; der kabelgebundene Plan B (Dock/Monitor Arbeitszimmer) hängt am sw2/Branch (P4). Beim Branch-Tausch stirbt also der Kabelweg, das WLAN überlebt. (Beim Core-Tausch stirbt beides — s. Kernrisiko oben.)
  • Hinweis: die A/B/C/D-Switch-Varianten in hw-refresh-requirements.md sind durch die CRS326-Beschaffung überholt (Doc = Recherche-Archiv + USV/Energie-Referenz).
  • Abh.: TP14 ✅ (IP-Migration). Verzahnt: TP20 (Platform-DB), TP21 (Nautobot GC), TP2.1 (sw2-Standalone-Recovery).

TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf crs326-core ▸ ✅ ABGESCHLOSSEN (2026-08-17) — docs/tp16.1-warp-mesh-native-crs326.md

  • ✅ Kernergebnis: ICMP ist repariert. Privater Zugang unterwegs läuft über natives WireGuard auf crs326-core statt cloudflared-QUIC — 0 % Ping-Loss auf alle BF-VLANs, vorher 100 %. Genau das Symptom aus dem Incident 2026-07-16, das dieses TP ausgelöst hat. Öffentliche FQDNs unberührt auf dem 3-Tunnel-HA (TP16). Compliance 68/68.
  • ✅ §4.0 erledigt 2026-08-17: Mesh-Node angelegt. bf-core-mesh = d1da7c8a-2736-4cbb-8af1-32db5393fb9c, Register-Token in mcp/cloudflare/.env (CF_MESH_REGISTER_TOKEN, gitignored). ➡️ Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.
  • 🔑 Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe). Der alte User-Token hatte zwei fehlende Permissions (§4.0 und §4.1 — devices/settings war schon lesend 403). Ersetzt durch einen account-owned Token (9984078812bc…, ⏰ gültig bis 2027-01-09). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an /accounts/{id}/tokens/verify — am alten /user/tokens/verify melden sie 1000 "Invalid API Token", während alle echten Endpoints 200 liefern; (2) Dashboard-Fehler "Account tag in access policy must match tag in request uri" = eine Zone-Permission (DNS) steckt in der Policy-Zeile mit Account-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in docs/tp16.1-…md §4.0. Merke: Dashboard sagt „Edit", API sagt „Write".
  • ✅ §4.1–§4.4 durchgezogen 2026-08-17 (User-Freigabe „jetzt durchziehen"). Prereqs: use_zt_virtual_ip=true, Device-Profil „Cloudflare Mesh" (4d3c0f57…), Split-Tunnel auf beiden Profilen korrigiert (100.64.0.0/10 → /11 + 100.112.0.0/12). Gerät: cf-mesh auf crs326-core, Mesh-IP 100.96.0.6/32, Peer 162.159.193.2:2408, Handshake steht, Route 100.96.0.0/12 → cf-mesh aktiv; Backup pre-tp16.1-mesh-20260817.backup vorher (Auflage #4 ✅). CF meldet Node healthy. §4.4: Weg A hat funktioniert — der befürchtete vnet-Gotcha trat NICHT ein, 10.40.0.0/16 zeigt jetzt auf bf-core-mesh statt bf-pve (Rollback = 1 PATCH zurück, bf-pve unangetastet).
  • ⚠️ Zwei Werkzeug-Fallen, real getroffen (gehören in jede künftige WG-Arbeit): (1) MikroMCPs manage_wireguard_interface kann grundsätzlich keinen privaten Schlüssel annehmen — RouterOS erzeugt ihn selbst; bei CF Mesh kommt er aber von CF → Key per SSH mit Interpolation aus der gitignoreten .env setzen, nicht über das Tool. (2) manage_wireguard_peer schreibt endpoint: "IP:Port" komplett in endpoint-address und lässt endpoint-port=0 → kein Handshake, sieht aus wie ein Firewall-Problem, ist ein Parameter-Bug. Nach jedem Peer-Add print detail gegenlesen. Auch persistent-keepalive fehlt dem Tool.
  • ✅ §4.5 ABNAHME BESTANDEN 2026-08-17 (MacBook am Hotspot, WARP connected): Ping 0 % Loss auf 10.40.10.1 / 10.40.20.105 / 10.40.99.12 / 10.40.20.10 / 10.40.99.1 (47–63 ms) — vorher 100 %. TCP mit echtem Datenfluss (pve 8006 → 200, Nautobot → 302, ssh macmini → ok). FQDN-Smoke gitea/hass/pve/nautobot/vw → 200/200/302/302/200 ⇒ cloudflared-Public-Teil unberührt.
  • ✅ Auflage Ausnahme #4 erfüllt: Ist-Stand als intended config zurück nach Nautobot → zuerst 65/68 (drei extra-Drifts = die Mesh-Zeilen), Zeilen byte-genau aus dem Backup ins Jinja-Template, Push nach bf-golden-config, Repo-Sync → 68/68, keine Drifts. Privkey bleibt draußen (<<REDACTED>>-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 <<REDACTED>> enthält, generell vom GC-Push aus, und /interface wireguard ist genau so eine Sektion. Ein privater Schlüssel ist über Golden Config also gar nicht deploybar — die frühere Zeile „CRS326-Zeilen laufen regulär über Nautobot GC" trägt nicht mehr. Auflage (wie Ausnahme #2): Ist-Stand danach als intended config zurück nach Nautobot; vorher /system backup (Regel 3). Scope-Grenze: nur der Mesh-Bring-up auf crs326-core — nicht spätere WG-Änderungen, nicht andere Geräte.
  • 🔑 Damit die Ausnahme ein einmaliger Bootstrap bleibt — Secret-Handling-Design (Runbook §4.3): ein privater Schlüssel ist Geräte-Identität, keine Konfiguration (wie ein SSH-Hostkey). Regel: der private Teil entsteht am Gerät und verlässt es nie; nur der öffentliche geht in die SoT und wird in die Peer-Configs gerendert. RouterOS kann das nativ (/interface wireguard add erzeugt den Privkey selbst). Compliance bleibt grün, weil die ConfigReplace-Maske aus TP22 Schritt 0 byte-identisch zu der im Template ist. ⚠️ Vorbehalt: bei CF Mesh erzeugt CF das Schlüsselpaar — dann fällt die On-Device-Erzeugung weg, das Prinzip („nie in Git, nie in der intended config") hält trotzdem. Stufe 2 (nautobot-secrets-providers + Vault, Wert erst bei der Plan-Erzeugung einsetzen) lohnt bei 4 Geräten und einem Key nicht — eigener TP nach TP18/TP20.1.
  • 🧹 Sofort fälliger Modellierungs-Nachzug, unabhängig vom Mesh: die WG-Peers fm/dw stehen mit hartkodierten public-key="…" im Jinja-Template. Public Keys sind Daten, keine Templatezeilen — gehören nach Nautobot und von dort gerendert.
  • ⚠️ Für TP5 vormerken: sobald deny-by-default greift, braucht der Mesh-Verkehr explizit forward accept 100.96.0.0/12 ↔ 10.40.0.0/16 — heute passiert er nur, weil die forward-Kette ohne Drop endet.
  • ⏭️ Was die Scharfschalt-Session macht: POST warp_connector (Plan-Gate) → account-weite Prereqs (Unique Device IP, Split-Tunnel auf beiden Profilen) → wgcf-connector-Token → 4 WG-Zeilen auf dem Core → Route 5546dcb6… umhängen (vnet-Gotcha, Weg A/B) → Ping-Abnahme. Trifft alle Live-WARP-Geräte mit einem kurzen Reconnect → Zeitpunkt user-gated.
  • ✅ Update (User-Entscheid 2026-07-22): Ziel-Device für den WG-Mesh-Node ist jetzt der CRS326 Core-Switch statt ap1 — konsistent mit dem TP15-Endstand (kein RB5009) — ⚠️ die damalige Klammer „Router bleibt ap1/ap2 rein für L3/WLAN" ist seit dem Core-Cutover 2026-08-08 überholt: L3 liegt auf crs326-core, ap1/ap2 sind reine APs. Der Retarget auf den Core war damit im Ergebnis richtig, nur anders begründet als hier notiert und TP21s "ganzer Kabel-Fabric via RouterOS"-Modell (CRS326 ist ohnehin RouterOS-fähig und wird Nautobot-verwaltet). Reine Ziel-Doku-Änderung — das Scharfschalten bleibt weiterhin eine eigene, spätere Session und bleibt gated, bis TP21 den CRS326 als Device abdeckt. Keine neue Governance-Ausnahme: die bestehende ap1-Ausnahme (s. Ist-Stand-Zeile 31) gilt explizit nicht automatisch für den CRS326 — CRS326-Konfiguration soll regulär über Nautobot GC laufen, sobald TP21 so weit ist. Die CRS326 selbst sind aktuell noch Werkszustand (physisch bereits mit dem aktuellen Core verkabelt, aber die betreffende Uplink-Port bislang ungenutzt/down) — konkrete Management-IP/Interface-Zuordnung für den CRS326 als L3/WG-Endpoint ist noch nicht getroffen (TBD nach TP15-Bring-up).
  • Trennung: Public FQDNs bleiben auf cloudflared-3-Tunnel-HA (TP16); Private Access (unterwegs → 10.40.0.0/16 per IP inkl. ICMP) wandert vom cloudflared-QUIC-Proxy auf einen nativen WireGuard-Mesh-Node auf dem CRS326 Core-Switch.
  • Warum CRS326-nativ: Cloudflare Mesh spricht WireGuard, RouterOS kann natives WireGuard (TP10-Hub auf ap1 ist einer, Muster überträgt sich) → Mesh-Node = WG-Peer zu CF-PoP, kein Container/TUN/LXC/neue HW. Verlagert die Rolle vom Router (ap1, der weiterhin Public-Edge + WLAN-Anker bleibt) auf den Core-Switch, der als zentraler Kabel-Fabric-Knoten ohnehin alle VLANs sieht. RouterOS-Container-Weg bleibt tot (kein /dev/net/tun-Passthrough auf JEDER Router/Switch-HW) — daher weiterhin natives WireGuard, kein Container-Ansatz.
  • Verworfen: WG-Roadwarrior (User will keine Extra-App/Profil je Gerät), alle Helper-Host-ZT-Anbieter (SPOF-Verletzung).
  • Warum heute nur Konzept: TCP-Pfad funktioniert (nur ICMP-Komfort fehlt), und die CF-Prerequisites sind account-weit (Unique-Device-IP, Split-Tunnel-Exclude, Device-Profile) → treffen Live-WARP-Geräte (1× kurzer Reconnect) → nicht unter Zeitdruck. Zusätzlich jetzt gated durch den Retarget: CRS326-Bring-up (Management-IP, VLAN-Filtering) muss erst stehen.
  • Nächste Session (nach CRS326-Bring-up): Plan-Probe POST warp_connector (Free-Plan-Gate; sonst LXC-Fallback) → account-weite Prereqs → wgcf-connector-Token→WG-Config → 4 WG-Zeilen auf dem CRS326 → Subnetz-Route + vnet-Route 5546dcb6… von bf-pve umhängen (Rollback trivial) → Verifikation (Ping verlustfrei). Team-Domain emsenhuber.cloudflareaccess.com. Governance „Config nur via Nautobot" → CRS326-Zeilen regulär via TP21/GC, sobald das Device dort adoptiert ist.
  • ✅ Recherche-Härtung 2026-07-17 (Primärquellen animmouse + offizielle CF-Docs + OpenAPI-Spec, alles verifiziert, gilt unabhängig vom Zielgerät): (1) Produkt heißt seit 2026-04-14 offiziell Cloudflare Mesh (API-Pfade bleiben warp_connector, im Spec bestätigt inkl. Token- und sogar HA-Endpoints); (2) ICMP-Proxy ist bereits AN (User-Korrektur: fm-Ping 10.101.0.0/24 beweist es — kein To-Do; mein „fehlt" war ein Fehlschluss aus dem API-Spec, das Flag ist Dashboard-only); (3) Split-Tunnel-Exclude auf beiden Device-Profilen (Default + Mesh); (4) vnet-Gotcha beim Route-Umzug („virtual networks only for cloudflared") → Weg A PATCH, Weg B DELETE+POST dokumentiert; (5) v4-only-Entscheid (Mesh-IPv6-site-to-site laut Guide broken, NAT66-Gefrickel nicht nötig); (6) keepalive 1m statt 25s ok. Alles in docs/tp16.1-warp-mesh-native-crs326.md §2/§4 eingearbeitet (Config-Block dort als Platzhalter markiert, IP/Interface TBD).
  • Abh.: TP16 ✅ (Public-Teil), TP10 ✅ (WG-Grundmuster, jetzt als Vorbild statt Direktziel), TP15 (CRS326-Bring-up), TP21 (Nautobot-Adoption des CRS326, Voraussetzung fürs Scharfschalten). SSoT: docs/tp16.1-warp-mesh-native-crs326.md, docs/cf-tunnel-mapping.md §14.

TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + shelly-mcp ▸ ✅ KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30 — Geräte-PW (war TP17.2, seit 2026-08-24 TP30 Posten #3) — mcp/shelly/, Runbook docs/tp17-shelly-go-runbook.md

  • ✅ Abschluss 2026-08-13 (User-Bestätigt): auch die zuletzt offenen saisonalen Shellys (s1/s2/s3/s12) sind inzwischen umgeklemmt — alle Shellys auf dirtyvader-iot/VLAN30, kein VLAN1-Shelly-Rest mehr. Migration-Teil von TP17 damit vollständig abgeschlossen.

  • ✅ GO durchgezogen 2026-07-13 (16 Shellys VLAN1→VLAN30, 0 Fehler): Pilot s26 + Welle A (s4–s9) + Welle B (s10/11/13/15/17/19/20/21/25) → alle auf dirtyvader-iot/10.40.30.<octet> 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*-<mac>-AP erreichbar, kein physischer Reset). Nach Verify alle 22 AP-Anker wieder deanchort. 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.<octet>, gw/dns .30.1) + in-pool DHCP-Reservierung als Backup. pool-v30 auf .100-.250 erweitert (Reservierungen jetzt in-pool); set_sta auf static=true umgestellt; s24-Inventar + .30.204-Reservierung angelegt. Vollständiger HA-MAC-Scan: 2 verpasste Media-Geräte Apple TV 4K + Philips TV (aus, → VLAN40) — vor cudy-Abschuss VLAN verifizieren; Apple TV = vmtl. Thread-Border-Router (TP17.1). .41.246/.247 = cudy1/cudy2 (Teardown-Ziele). Tasmota bestätigt tot.

  • Offene Schritte (morgen, „nur noch go", Runbook docs/tp17-shelly-go-runbook.md): die 16 verbliebenen VLAN1-Shellys (s4–s11, s13, s15, s17, s19–s21, s25, s26 auf 10.40.41.2xx) per WiFi.SetConfig→dirtyvader-iot umklemmen (Batch, je Gerät .30.x verifizieren) → Geräte-PW setzen → HA/ioBroker-Re-Point → SwitchBot/Tasmota/3. Nicht-Shelly → saisonale s1/s2/s3/s12 beim nächsten Einstecken → cudy-Alt-WLAN abbauen → DNS/Inventar/ip-plan nachziehen.

  • Abh.: SSID/VLAN30 (TP3/TP4 ✅), DHCP-v30 ✅. Verwandt: project_vlan1_dhcp_incident (cudy-Rest-Leak), TP5 (IOT-Firewall danach). Memory project_shelly_migration_tp17.

  • ➡️ Nachtrag 2026-08-23: die Projektion „TP17.1 — Gen4 auf Thread/Matter" ist keine Projektion mehr. Christoph hat sie aktiviert; sie steht als offenes TP17.1 in teilprojekte.md, die Umsetzung liegt im HA-Projekt (~/dev/ha-xstate/docs/shelly-gen4-thread-migration.md). Der damals notierte Vorbehalt „Firmware-Verfügbarkeit + Thread-Border-Router offen" hat sich halb aufgelöst: der Border Router läuft (~30 Matter-Knoten), die Firmware gibt es von Shelly aber bis heute nicht — offiziell ist Gen4 nur Matter over WiFi, Thread nur über Dritt-Firmware. Auch der zweite Vorbehalt gilt unverändert: Matter = neue Entities, der Konflikt mit „HA-Entities unverändert" ist real und in der Übergabe ausgeführt.

  • 📊 Bestandszahl, live 2026-08-23: 22 der 26 Shellys erreichbar — 12 Gen4, 8 Gen3, 2 Gen2. Die „~11 Gen4" aus der Schätzung von 2026-07-11 waren also nah dran.

  • 🔑 Nachtrag 2026-08-23 — der IOT-PSK steht in der „Ziel"-Zeile oben im KLARTEXT, und das bleibt vorerst so (User-Entscheid). Der Wert ist am selben Tag gegen den Manager geprüft worden und ist noch der live konfigurierte. Bewusst nicht redigiert und keine History-Rewrite:

    • Redigieren allein hilft nicht — die Zeile liegt seit e76289c (2026-07-11) in der Historie.
    • Ein git filter-repo wäre hier Aufwand ohne Gegenwert: eine getrackte Fundstelle, drei Commits, Remote nur gitea.emsenhuber.net (kein GitHub), ein lokaler Klon — der Wert hat die eigene Infrastruktur nie verlassen.
    • Der wirksame Schritt ist die Rotation, und die kostet heute ~26 Geräte. Sie ist deshalb bewusst hinter TP17.1 eingereiht (Gen4 → Thread), s. Follow-ups in teilprojekte.md. ⚠️ Wer diesen Abschnitt liest, behandelt den Wert als aktives Geheimnis — nicht zitieren, nicht in Transkripte, nicht in Tickets. Zum Abgleich reicht ein Fingerprint (Länge + Stichproben), genau so ist er am 08-23 verifiziert worden.

TP19 — cudy-Teardown + VLAN1-Client-Retirement ▸ ✅ GESCHLOSSEN (2026-08-18) — docs/tp19-vlan1-cudy-teardown-runbook.md

  • ✅ Abschluss-Sweep 2026-08-18, alles live gegengeprueft (reine Reads):
    • VLAN1 ist leer: /ip arp auf bridge = 0 Eintraege, 0 reachable. Nicht Restverkehr, sondern gar nichts. Der Dual-DHCP-Race aus project_vlan1_dhcp_incident hat damit endgueltig keine Quelle mehr.
    • Prometheus: kein einziges 10.40.41.x-Ziel mehr. Aktiv sind localhost:9090, n8n (.20.167:5678), grafana (.20.109:3000); die uebrigen acht Jobs sind auskommentiert. Der in der alten Restliste genannte Punkt ist damit verifiziert erledigt, nicht nur behauptet.
    • Interim-Kruecken sind weg: /interface bridge port auf ap2 hat keinen Port mit eingeschraenkten frame-types mehr — die TP3.1-Haertung admit-only-vlan-tagged ist beim Rebuild verschwunden. Nichts abzuraeumen. st-main ist seit TP3.3 fester Templatebestandteil.
    • DNS-Statics zeigen ausschliesslich auf .99.x/.20.x, kein .41.x.
    • cudy1: core ether11 ohne Link seit 2026-08-08 19:33, in Nautobot Offline. cudy2 tot.
  • 🔜 Was NICHT mehr TP19 ist (bewusst dorthin verschoben, wo es hingehoert, statt den TP offen zu halten):
    • Der physische Ausbau von cudy1 — Hands-on-Aufgabe, blockiert nichts, kein Netzeffekt.
    • Die 4 toten CF-Tunnel (coolify, openclaw …) — das sind Karteileichen in der Cloudflare-Config, kein Netzthema → TP16.
    • HA-Portainer setup_retry (docker system df auf ep14 braucht 33–120 s, struktureller Plattformnachteil) → Monitoring-Thema, TP11. Aussichtsreichster Weg bleibt ein eigener Portainer-Benutzer ohne Zugriff auf ep14.
  • 📌 Nebenbefund beim Abschluss, sofort behoben: der Standort hatte keine funktionierende Zeitquelle. ap1/ap2 zeigten per NTP auf 10.40.99.1, wo kein Server lief (status=waiting), branch hatte gar keinen eingetragen. ap2 war dadurch 17 Stunden versetzt — aufgefallen, weil ein Logeintrag von 04:57 als 2026-08-17 12:19 gestempelt wurde. Der Core ist jetzt NTP-Server (routeros/tp19-ntp-core-als-zeitquelle.rsc, inkl. der noetigen input-Regel — ohne die waere der Server seit Etappe 4 eingeschaltet und trotzdem unerreichbar gewesen), branch ist Client (routeros/tp19-ntp-branch-client.rsc).
    • 🔎 Die Wurzel lag eine Ebene tiefer: auch der Core hatte keinen NTP-Client (enabled: no, keine Server) bei use-local-clock: no. Ein Server ohne eigene Synchronisation liefert Stratum 16, das jeder Client verwirft — die Anfragen kamen also an (die Firewall-Regel zaehlte) und wurden trotzdem nicht brauchbar beantwortet. Der Standort hatte damit an keiner Stelle eine funktionierende Zeitsynchronisation; dass drei von vier Uhren stimmten, war Restglueck vom letzten manuellen Stellen. Behoben mit routeros/tp19-ntp-core-client.rsc (time.cloudflare.com,at.pool.ntp.org) → Core synchronized.
    • 🔴 Und der letzte Schritt war der unauffaelligste: der Server hielt Stratum 16 fest. Er war eingeschaltet worden, waehrend sein eigener Client noch unsynchronisiert war — RouterOS bewertet das nicht von selbst neu. Die Anfragen kamen an (die Firewall-Regel zaehlte mit), wurden aber unbrauchbar beantwortet, und alle drei Clients blieben auf waiting. Ein enabled=no / enabled=yes nach der Synchronisation hat es geloest. Der entscheidende Test dorthin: branch einmal auf at.pool.ntp.org zeigen lassen — es synchronisierte in 20 Sekunden. Damit war bewiesen, dass der Client heil ist und der Server schweigt, statt weiter im Nebel zu stochern. ✅ Endstand: alle vier Geraete synchronized. (ap2s Uhr musste einmalig von Hand gestellt werden — 17 h liegen ueber der NTP-Panic-Schwelle, so weit springt kein Client von selbst.)
    • ✅ Governance nachgezogen (2026-08-18): NTP steht jetzt im GC-Template (Core als Quelle mit oeffentlichem Upstream, branch/APs als Clients auf 10.40.99.1), wird per DHCP-Option 42 (ntp-server= je Netz, Christophs Vorschlag) an alle Clients ausgeliefert — nicht nur an die MikroTik-Flotte — und ist mit der neuen ComplianceRule system-ntp drift-ueberwacht. Das war exakt der blinde Fleck, den TP26 fuer /ip firewall filter geschlossen hat.
  • ✅ Live-Nachprüfung 2026-08-16 (MikroMCP + CF-API, reine Reads):
    • cudy1 ist tot, nicht nur „offline gemeldet": core ether11 steht auf running=false, letzter Link-Down 2026-08-08 19:33, seither kein Link. Die Formulierung „aktiver VLAN1-Leak" ist damit endgültig hinfällig — der Dual-DHCP-Race hat keine Quelle mehr. Offen bleibt nur der physische Ausbau + Inventar/Doku.
    • crs326-core hat keine VLAN1-IP mehr: /ip/address listet nur noch die 7 SVIs + die WAN-DHCP-Adresse — die Bring-up-Altlast 10.40.41.211 ist weg. Damit fällt einer der 5 Compliance-Drifts aus TP22 raus, sobald wieder Backup → Intended → Compliance gelaufen ist (die Compliance vergleicht gegen das letzte Backup, nicht gegen das Gerät — s. TP22-Falle).
    • ✅ crs326-branch-Interim ERLEDIGT (2026-08-16): 10.40.10.2 + SVI vlan10 über einen GC-ConfigPlan entfernt (s. TP22). Mgmt läuft über .99.2; /interface bridge vlan blieb byte-identisch, es gibt also weder einen Traffic- noch einen Drift-Effekt.
    • .41.133 identifiziert (war TP19 B1.2 „unidentifiziertes VLAN1-Gerät"): es ist das coolify-Origin aus dem gleichnamigen, seit Monaten toten CF-Tunnel. Ebenso .41.147/.148/.139 aus dem toten openclaw-Tunnel. Keine aktiven Geräte — Karteileichen in der Cloudflare-Config, nicht im Netz. Aufräumen = die 4 toten Tunnel löschen (user-gated, s. TP16-Nebenbefund).
  • ✅ VLAN1-Adressen abgeräumt (2026-08-15) — der Weg dorthin — docs/vlan1-rueckbau-howto-manuelle-schritte.md:
    • pve 10.40.41.100 weg (/etc/network/interfaces: vmbr0 von inet static auf inet manual, Adresszeile raus; angewendet per ip addr del ohne ifreload -a, damit die Bridge nicht durchgetreten wird). Nachher nur noch vmbr0.20, keine 41.0/24-Route, alle 9 Gäste weiter running, Storage aktiv. Backup /root/interfaces.bak-tp19-20260815-183045.
    • Core 10.40.41.211 weg über Nautobot GC (routeros/tp19-core-vlan1-address-remove.rsc). Mgmt .99.1 + 10.40.10.1 intakt. Compliance damals 17/17 grün — der TP22-Backlog-Punkt „crs326-core 10.40.41.211" entfällt. (Zahl inzwischen überholt: nach TP16.1 sind es 72/72 Checks.)
    • HA zeigt auf 10.40.20.100 (User), ioBroker korrigiert (User).
    • Prometheus bereinigt. Der pauschale 41→20-Ersatz war eine Falle: nur grafana (.20.109) und n8n (.20.167:5678) liefern echte Metriken. gitea hat /metrics gar nicht aktiviert, vaultwarden gibt 404, portainer /api/status liefert JSON statt Prometheus-Format, node/alertmanager/qdrant sind exited, pihole ist abgewählt → 8 Jobs + alerting:-Block auskommentiert, 3 aktive Ziele, alle up.
    • 🔴 NAS-Falle: prometheus.yml gehört 65534:65534, das Verzeichnis adm.christoph → cat > datei scheitert still („Permission denied"), während ein cp daneben klappt. Schreiben per sudo tee + md5sum-Gegenprobe, nie der Shell-Erfolgsmeldung trauen.
  • ⏳ OFFEN: HA-Portainer-Integration bleibt auf setup_retry — docker system df auf nas-bf ist zu langsam. Aus dem HA-Debug-Log: PortainerTimeoutError … GET /api/endpoints/14/docker/system/df → ConfigEntryNotReady. Die Integration ruft system df für jede Umgebung bei jedem Update auf.
    • Container-Manager-Neustart am 2026-08-15 durchgeführt (/usr/syno/bin/synopkg stop|start ContainerManager — Binary nicht im sudo-PATH; DSM meldet den Status danach weiterhin falsch als stop). Alle 14 Container aus der Baseline zurück, zusätzlich kam hms-mqtt hoch (Restart-Policy always, lief vorher nicht).
    • ✅ hms-mqtt stillgelegt (2026-08-16, User: „wird bald platt gemacht"). Es war bereits beendet; entscheidend war die Restart-Policy always → no (docker update --restart=no), sonst wäre es beim nächsten Daemon-Neustart wiedergekommen. Ein bloßes docker stop hätte hier nicht gehalten.
    • ✅ Der Hänger ist damit weg: system df beendet sich wieder (vorher rc=124 nach Ausgabe der Tabelle). ❌ Aber es bleibt viel zu langsam und schwankt: 33 s → 92 s → 3× >120 s.
    • 🔑 Wo die Zeit hingeht (gemessen): images -q 84 ms · ps -aq 95 ms · volume ls -q 65 ms · system df 33–92+ s. Also vollständig in der Größenberechnung (du-artiger Durchlauf der overlay2-/Volume-Verzeichnisse auf NAS-Platten) — ein struktureller Plattformnachteil, kein Defekt. Die Mengen sind harmlos (25 Images/13 GB, 10 Volumes/79 MB).
    • Aussichtsreichster Weg: eigener Portainer-Benutzer für HA ohne Zugriff auf ep14 → die Integration sieht die langsame Umgebung gar nicht. User-gated (Benutzer + Token anlegen). Alternativen: nichts tun (stört nichts) · Images aufräumen (6,98 GB reclaimable, Erfolg offen) · ep14 aus Portainer nehmen (schlechter Tausch).
    • ⚠️ Zwei widerlegte Zwischenthesen, nicht wieder aufgreifen: die tote Umgebung odroid-m1 war es nicht (entfernt, Symptom blieb, alle übrigen Endpoints ≤0,16 s); und es ist kein Größenproblem der Images.
  • ✅ Restliste aus dem TP5-Log-Lauf — ABGEARBEITET (Abnahme 2026-08-16: out:bridge = 0 über 10 min, Rate von ~166 auf ~34 Zeilen/min gefallen; im 43k-Zeilen-Log vom 08-17 kommt out:bridge nicht mehr vor). Wert dieses Eintrags = die Diagnosemethode, nicht der Stand. Der Log-Lauf hatte gezeigt, dass 76 % allen Inter-VLAN-Verkehrs aus VLAN20 nach VLAN1 (out:bridge) ging, obwohl dort nichts mehr antwortete (alle Ziele im ARP failed/incomplete). Verursacher waren Konfigurationen, die nie nachgezogen wurden:
    • prometheus (10.40.20.151, NAS) scrapet 10 tote Ziele: 10.40.41.109:3000 · .126:9443 · .127:80 · .147:5678 · .149:3000 · .152:3001 · .153:9093 · .154:9100 · .159:6333 · .23:80.
    • 10.40.20.105 zieht auf 10.40.41.100:8006 = pve unter seiner alten VLAN1-Adresse (real 10.40.20.100).
    • 10.40.20.107 auf 10.40.41.10:8080 (NAS alt).
    • 📌 Umzugsregel: das letzte Oktett bleibt gleich, nur 41→20 — belegt für grafana (.109), vaultwarden (.127), Portainer (.126). Für .23/.147/.149/.152/.153/.154/.159 existiert kein laufendes VLAN20-Gegenstück (anderswo oder tot; alertmanager ist seit TP14.1 als kaputt notiert) → diese Ziele gehören geprüft und dann aus Prometheus entfernt, nicht umgeschrieben.
  • ⚠️ Kein Firewall-/Portproblem (2026-08-14 geprüft, nachdem beim Umstellen ein Timeout auftrat): pveproxy lauscht auf *:8006, pve-firewall ist disabled, keine iptables-Regel, kein /etc/default/pveproxy. https://10.40.20.100:8006 antwortet aus VLAN10 und VLAN20 mit HTTP 200 in ~0,06 s. Wer hier einen Timeout sieht, sucht an der falschen Stelle — und timeout gibt es auf macOS nicht, ein timeout bash -c '… /dev/tcp/…'-Test meldet deshalb falsch-negativ.
  • ⚠️ Eskaliert 2026-07-15 (historisch — durch den WLAN-Rebuild 2026-08-08 gegenstandslos): der unter TP3.1 dokumentierte VLAN1-Leak trat damals auch auf ap1/dirtyvader-main auf — die ap-seitige Interim-Härtung (TP3.1) milderte das Symptom, die endgültige Lösung ist hier, TP19: VLAN1 physisch weg = kein Leck mehr möglich.
  • 🟢 cudy-Stand (User 2026-07-16): cudy1 (.41.246) auf 10.40.10.246 umgehängt — bewusst kein Abbau-Ziel mehr, dient als Standalone-WLAN-Sender während der Umstellungsphase (nicht Teil des Teardowns, extern vom User selbst umkonfiguriert). cudy2 (.41.247) wird nicht mehr reaktiviert — faktisch tot, ARP-Sweep bestätigt keine Präsenz mehr auf VLAN1. B2.2 („cudy1/cudy2 stromlos/Reset/ausbauen") ist damit erledigt/gegenstandslos (cudy2 aus, cudy1 schon migriert statt abgebaut).
  • 🟢 Live-Sweep 2026-07-16 (ap1, vor geplantem Teardown): VLAN1 ist bereits viel weiter zurückgebaut als dokumentiert:
    • defconf/pool-v1-DHCP-Server ist bereits disabled=yes (Export bestätigt) — B2.4 („defconf zurückziehen") ist de facto schon erledigt, Ursprung/Zeitpunkt dieser Änderung nicht aus dieser Session bekannt (nicht in dieser Doku protokolliert).
    • ARP auf bridge (VLAN1): nur noch 2 „reachable"-Hosts — sw1 (.41.11, by design, bleibt bis MGMT-VLAN99) und pve-Interim-Fallback (.41.100) (B1.5-Aufräumziel, noch offen — Host-seitige Änderung auf pve, nicht Router). Alle anderen ~20 ARP-Einträge sind stale/„failed" (Cache-Reste längst migrierter Hosts wie macmini/.20.167, NAS/.20.10, grafana/.20.109 etc.), keine aktive Kommunikation mehr.
    • B1.1 MacBook (Claude-Sandbox-Client) bereits sicher migriert: aktive DHCP-Leases nur noch auf dhcp-v20 (10.40.20.101) und dhcp-v10 (10.40.10.187), keine defconf-Lease mehr. ⚠️ Dual-Homed über 2 VLANs gleichzeitig (project_dual_homing_slowness_incident-Muster) — funktional ok, aber im Auge behalten.
    • B1.2 unidentifizierte Geräte .41.133/.41.198: kein ARP-Eintrag mehr vorhanden — entweder migriert oder abgeklungen, gelten für den Teardown als nicht mehr blockierend.
    • ⚠️ B1.3 (Media→VLAN40) und B1.4 (saisonale Shellys) nicht in diesem Sweep verifiziert — weiterhin offen, kurz vor dem finalen Cutover gegenchecken.
  • Backup-Stand vor Cutover (2026-07-16): nur ap1 gesichert (Binary bf-ap1-pre-vlan1-teardown-20260716.backup auf dem Router + lokaler Export routeros/state-backups/ap1-export-20260716-pre-vlan1-teardown.rsc). ap2 nicht gesichert, aber unkritisch: bewusst ausgesteckt (User-bestätigt 2026-07-16, kein Ausfall) — kein Backup nötig, solange es aus bleibt. sw1 nicht per Telnet gesichert (claudecode-Login 2× deterministisch abgelehnt), aber kein Blocker: User macht sw1-Änderungen ohnehin direkt im UI/CLI selbst (effizienter als über den Claude-Runner, gemeinsame Entscheidung) — Telnet-Automation für sw1 ist hier kein Muss.
  • Ziel (Kapstein der Segmentierung, s. Zeile 27): altes cudy-WLAN weg + VLAN1 als Client-Netz stilllegen (defconf/pool-v1-Client-DHCP zurückziehen, ✅ bereits disabled). Nicht VLAN1 komplett: Switch-/AP-Mgmt (sw1 .11/sw2 .12/ap2 .2) bleiben by design in VLAN1 (statisch, kein DHCP nötig) bis zum separaten MGMT-VLAN99-Cutover (eigener TP). cudy1/cudy2 = .41.246/.247 (Teardown-Ziele) — cudy1 migriert auf .10.246 (Reuse statt Abbau), cudy2 tot/nicht reaktiviert (s.o.).
  • 🎯 User-Gate (Update 2026-07-14): Shelly ist wieder da (kein Blocker mehr) — SwitchBot ist KEIN Prereq mehr, funktioniert laut User erst NACH VLAN1-Retirement (Reihenfolge ggü. 2026-07-13 umgedreht) → wandert in die Teardown-Nachlese. TP19 damit direkt startbereit. Twinkly nebenher (saisonal).
    • Verlorene Shelly — IDENTIFIZIERT via HA 2026-07-13 (User-Hinweis „du siehst ihn im HA"): HA-Device shellyplugsg3-e4b063e41ab8, Modell = Shelly Plug S Gen3, MAC e4:b0:63:e4:1a:b8, HA-device_id 7a21e8f3349e6660f8b35974fc989a7b. NICHT im 26er-Inventar (Spare/Küchen-Steckdose, die Christoph beim Einrichten verbockt hat; er nannte sie „küche s20", aber s20=98:a3:16:7c:5f:ec läuft sauber auf .30.206 — Verwechslung). KEIN AP-Mode, nicht tot: assoziiert am Altnetz elektrosmog.c (cudy → untagged VLAN1-L2), aber mit bogus-IP 10.95.10.206 (Fremd-Subnetz, User-Fehler) → unrouting-bar, daher weder in VLAN1/30-Sweep noch normal per IP erreichbar, aber per MAC in HA/WiFi-Table sichtbar. Der .219/s12-Verdacht war Sackgasse (s12 nur saisonal aus).
    • Update 2026-07-14: Shelly wieder da (User-Meldung) — Live-Status (noch bogus-IP 10.95.10.206 oder inzwischen normal erreichbar?) zu Sessionbeginn per HA/ARP verifizieren; Recovery-Plan unten ggf. obsolet, falls sich das Gerät selbst erholt hat.
    • Recovery-Plan (falls beim Verify noch nötig): ap1 bekommt temporäre Zweit-IP 10.95.10.1/24 auf der Bridge → 10.95.10.206 per L2 erreichbar → ein WiFi.SetConfig {sta: ssid=dirtyvader-iot, pass=IOT-PSK, static, ip=10.40.30.<frei>} + 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 "<<REDACTED>>". 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 "<<REDACTED>>" 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 vianetmiko_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 <<REDACTED>> (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="<<REDACTED>>" auf die APs (= alle WLAN-Clients raus, inkl. ~26 Shellys) bzw. private-key="<<REDACTED>>" auf den Core (= WG-Hub tot, FM/DW-Spokes weg). Jede Sektion, die <<REDACTED>> 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 <<REDACTED>> sind vom Push generell ausgeschlossen, die Secrets werden also nie gebraucht. Konsequenz, bewusst akzeptiert: Secrets bleiben dauerhaft außerhalb von Modell und Push, DR läuft weiter über ~/.secrets/ + routeros/.secrets/* + routeros/ap1-wg-hub.privkey; „Gerät vollständig aus dem Modell wiederherstellbar" gilt nur ohne Secrets. Die verworfene Alternative (b) hätte Regel 1 aufweichen müssen — ein Render-Fehler hätte dann WLAN bzw. WG-Hub abgeschossen. Damit ist TP22 inhaltlich abgeschlossen.

  • Schritte: (1) AP-GC-Template: mikrotik_routeros.j2 um role == "Wireless AP"-Zweig erweitern (Bridge/vlan-filtering/ether1-Trunk/vlan99-Mgmt/6 SSIDs ohne PSK/L3) → APs komplett aus GC generierbar; (2) Nornir/NAPALM-Adoption aller 4 (SecretsGroup „RouterOS Nornir"; einheitliches Admin-PW seit 2026-08-08 vereinfacht das), Backup/Compliance-Job grün; (3) enable_plan+enable_deploy scharf — erst Dry-Run/Plan, dann kontrollierter Push; (4) Secret-Handling WG-Privkey (core) + WLAN-PSKs (bleiben aus dem Git-Template, via Nornir-Secrets injizieren); (5) Datenmodell-Nachträge: branch/core-Ports final, sw1 (außer Betrieb) + cudy1-Status.

  • Nutzen: Drift-Erkennung + reproduzierbarer Rebuild jedes Geräts aus dem Modell.

  • Abh.: TP21 ✅ (Plattform/Modell/Templates), TP20 ✅, einheitliches Admin-PW ✅. Danach ist der kritische Pfad frei für TP5 (Deny-by-default) → TP6 (CAKE).

  • ✅ Nachtrag 2026-08-23 — Governance-Lücke geschlossen: ComplianceRule wifi-access-list. /interface wifi access-list stand unter keiner Regel: Einträge dort erzeugten keinen Drift und ließen sich nicht als Transaktion führen (gc-transaktion.py verlangt eine ComplianceRule als --scope). Seit docs/tp25-pin-s22.sh schreibt jeder Messrundgang dort Regeln, und TP25 Fund E (signal-range-Boden) würde dauerhaft genau dort landen. Angelegt per nautobot/ingest-wifi-access-list-rule.py (idempotent), Template um den dokumentierten Abschnitt [6c] ergänzt (nur Kommentare — der Soll-Zustand ist leer, und das ist die Aussage).

    • 🔢 Zählerkorrektur: 92 → 96, nicht 94. Die Regeln gelten plattformweit, nicht gerätespezifisch: 24 Rules × 4 Geräte. Die Annahme „nur 2 Geräte betroffen" war falsch.
    • ✅ Wirksamkeit bewiesen, nicht behauptet (die TP22-Regel „Job-Status ist kein Wirksamkeitsnachweis"): Test-Eintrag mit einer nicht existierenden MAC gesetzt → 95/96 mit exakt dieser Zeile als extra → Eintrag entfernt → 96/96, 0 Drifts. Zusätzlich geprüft: gc-transaktion.py akzeptiert --scope wifi-access-list jetzt.
    • 🔎 Nebenbefund beim Start: ein bridge-vlan-Drift auf crs326-core stand noch in der Anzeige (ether3 als Trunk), obwohl der Sideboard-Rollback am 08-22 gelandet war. Das Gerät war live korrekt; die Compliance vergleicht gegen das letzte Backup, und das war älter als der Rollback. Mit dem Refresh verschwunden. Merkregel bleibt: bei einem Drift zuerst fragen, wie alt das Backup ist.

TP26 — /ip firewall filter unter Drift-Kontrolle ▸ ✅ DURCH (2026-08-17)

Nachgetragen 2026-08-18. TP26 existierte bis dahin nur als zwei Commits und eine .rsc — ohne Abschnitt in dieser Datei. Genau die Lücke, die eine frische Session nicht finden kann.

  • Auslöser: vor TP26 gab es eine ComplianceRule auf /ip firewall nat, aber keine auf /ip firewall filter. Die vielzitierte „72/72 grün" sagte über die Firewall damit gar nichts — und das ausgerechnet, während TP5 den Deny-by-default vorbereitete. Der Drop wäre die einzige Regel im Netz gewesen, die niemand überwacht.
  • ✅ Rule ip-firewall-filter angelegt, config_ordered=true — bei Firewallregeln ist die Reihenfolge semantisch, nicht kosmetisch. Ergebnis damals: 76/76. Damit war TP5 Etappe 3 entblockt.
  • ✅ Zweiter Teil: die defconf-Firewall auf beiden APs abgeräumt (routeros/tp26-ap-firewall-cleanup.rsc). ap1/ap2 trugen je 12 Werksregeln — input-seal, fasttrack, ipsec-Zeilen, sogar „accept to local loopback (for CAPsMAN)". Inert, weil ein reiner AP nichts routet. Entscheid: abräumen statt ins Template schreiben — sonst erklärt man Werksmüll zur Absicht. Dasselbe Argument hat 2026-08-18 auch die alten WLAN-Objekte auf den APs erledigt (TP28).
  • 📌 --assert hat dabei zwei Fehlschläge gefangen, die der Job-Status als Erfolg gemeldet hätte. Seitdem ist die Gegenprüfung am Gerät fester Bestandteil von gc-manual-plan.py.
  • Abh.: TP22 ✅. Direkte Folge: TP5 E3/E4.

TP27 — RouterBOOT-Firmware auf 7.24 nachziehen ▸ ✅ DURCH (2026-08-17/18)

Nachgetragen 2026-08-18. Auch TP27 hatte keinen Abschnitt — die Arbeit steckt in Christophs Commit b93514a plus routeros/tp27-routerboot-firmware-724.rsc und docs/routerboot-firmware-klickanleitung.md.

  • Kontext: Christoph hat alle vier MikroTik selbst auf RouterOS 7.24 stable gehoben (core/branch 7.19.5 → 7.24, ap1/ap2 7.23.2 → 7.24). Offen blieb die zweite Hälfte jedes MikroTik-Upgrades: die RouterBOOT-Firmware hängt danach zurück.
  • 🔑 Die Falle, wegen der es ein eigener TP ist: /system routerboard upgrade schreibt die Firmware nur — wirksam wird sie erst beim nächsten Reboot, und current-firmware zeigt bis dahin weiter den alten Wert. Wer direkt nach dem Befehl prüft, hält den Schritt fälschlich für gescheitert. Reihenfolge also: schreiben → gezielt rebooten → current-firmware gegenprüfen.
  • ⚠️ /system routerboard upgrade* steht auf der MikroMCP-Deny-Liste (zu Recht — es schreibt den Bootloader). Der Weg führt über Golden Config; der Reboot dann über sshpass/MikroMCP.
  • ✅ Abnahme 2026-08-18: auf allen vier Geräten current-firmware = upgrade-firmware = 7.24.
  • Abh.: keine. Berührt TP28 insofern, als CAPsMAN erst ab 7.2x das neue /interface/wifi mitbringt.

TP28 — WLAN über CAPsMAN auf crs326-core ▸ ↩️ WIEDER GEÖFFNET 2026-08-30

Der Volltext steht seit dem 2026-08-30 wieder in teilprojekte.md → TP28, weil mit ap3 (cAP ax) ein dritter Access-Point dazukommt. Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder geöffnet werden (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen.

TP-R — Router-Capabilities-Sweep ▸ FERTIG (docs/research/routeros-capabilities.md)

  • Leitsatz: nativ vor Container, NAS vor Router (128MB-Flash-Limit).
  • Sofort nativ (~0 RAM): RoMON · Netwatch · Scheduler + Auto-Backup/Mail · SNMP→externes Monitoring · mDNS-Repeater · spectral-scan · 802.11r/k/v (Fast-Roaming statt Mesh) (die Sweep-Fassung nannte hier CAPsMAN — das ist seit 2026-08-08 abgeschafft; 11r/k/v läuft standalone weiter, s. TP3/TP3.3) · Back-to-Home/WireGuard.
  • Auf NAS: FreeRADIUS(+LDAP) · NPM · icinga/Grafana · CF-Tunnel.
  • Weglassen: LoRa (keine HW) · OpenFlow · CALEA · ZeroTier (CPU-Fresser bis ~60 % → WireGuard) · lokales dude.
  • ✏️ Korrektur Reverse-Proxy: RouterOS 7.22+ HAT einen nativen (/ip/reverse-proxy, ACME/SNI) — aber ersetzt NPM NICHT (kein WebSocket/gRPC/Sticky, nur IPv4-Backends, TLS frisst Router-CPU) → NPM bleibt auf NAS.
  • Speist TP8/9/11/12.


TP25 — Langfassung (Messchronik + Funde im Volltext, Stand 2026-08-22)

⚠️ TP25 ist NICHT abgeschlossen. Der geltende Stand, das Offene (Fund F, die 20-MHz-Entscheidung) und die Merkregeln stehen in teilprojekte.md → TP25. 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 (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 §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 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): 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-<name>/ 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. ✅ 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 <if> !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 Profilobjekte 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.

TP39 — LXC-Hygiene auf pve: Resolver-Altlasten, networking.service, DB-Reverse-Lookup ▸ ✅ ABGESCHLOSSEN (2026-09-01)

Auslöser: ein 10-Sekunden-Hänger auf gitea, gefunden am Rand des TP16.3-§5-Flips. Er hatte mit dem Flip nichts zu tun, war aber nur deshalb sichtbar, weil zum ersten Mal jemand systematisch gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein. Beide sind Hygiene an denselben pve-LXCs und gehören in ein Wartungsfenster — deshalb ein TP, nicht zwei.

Was am 2026-09-01 bereits erledigt ist (Ausgangslage, nicht Aufgabe)

  • Ursache des Hängers: MariaDB (LXC 302, 10.40.20.23) hatte skip-name-resolve auskommentiert ⇒ Reverse-Lookup bei jeder neuen Verbindung; /etc/resolv.conf zeigte auf den toten 10.40.41.254 (VLAN1-Altschema). Gemessen: 10.010 ms je Lookup. Pool-Verbindungen waren schnell ⇒ das Symptom wirkte sporadisch.
  • Blast-Radius, per Verbindungstabelle belegt: an dieser DB hängen Vaultwarden (.127), Home Assistant (.105) und gitea (.157).
  • Fix ausgerollt (pct set <id> --nameserver 10.40.20.1 + Reboot) für fünf LXCs: 302 mariadb, 102 mqtt5, 106 influxdb2, 107 redis, 150 iob-exposed. Nachher: Lookup 21–24 ms, gitea 0 von 25 Proben langsam (vorher ~10 % bei 10,1 s).

Scope (alle Punkte erledigt)

  1. ✅ ERLEDIGT 2026-09-01: skip-name-resolve aktiviert. Vorher mysql.user gelesen (User gab den DB-Zugang per /root/.my.cnf) — kein einziger Grant lautet auf einen DNS-Hostnamen, alle sind % / IP-Literal / localhost. Damit kann skip-name-resolve niemanden aussperren; der Reverse-Lookup war reiner Ballast. Gesetzt als eigenes Drop-in /etc/mysql/mariadb.conf.d/90-skip-name-resolve.cnf (nicht die #-Zeile in 50-server.cnf editiert — Paket-Update-stabil), @@skip_name_resolve=1 nach Neustart verifiziert. Wirkung: gitea/vw/hass je 0 von 12 frischen Anfragen langsam, max 0,25 s. Alle drei Clients danach wieder verbunden. Die Ursache ist damit beseitigt, nicht nur entschärft. 🔴 Sicherheitsvorfall dabei: die .my.cnf trug password <wert> ohne =, die Maskierung griff nicht ⇒ das root-DB-Passwort ist ins Transkript geraten und gehört rotiert (→ TP30).
  2. ✅ ERLEDIGT 2026-09-01: networking.service + ifupdown-wait-online.service in 101, 102, 106, 107 repariert. Ursache — dieselbe Altlast wie beim Resolver: in /etc/network/interfaces standen noch auto-Stanzas aus dem alten VLAN1-Schema (eth0 → 10.40.41.x) und aus früheren Versuchen (veth16, veth24, ein Tippfehler eth01) — alle auf Interfaces, die es im Container nicht gibt. ifup -a scheiterte daran, während das echte veth20 sauber hochkam: „failed" bei funktionierendem Netz. Fix: nur die toten Stanzas entfernt (Backup je Container: /etc/network/interfaces.bak-20260901), lo/veth20/veth20.2 unangetastet. Beweis ist ein echter Neustart, nicht der Servicestatus: 106 neu gestartet ⇒ influxdb kam nach 5 s von selbst hoch, networking active, null fehlgeschlagene Units. Genau der Fall, der vorher scheiterte. 🔑 Nicht durch die Session verursacht: 101 wurde vor dem Fix nie angefasst und zeigte dasselbe Bild — die Container liefen nur 13+ Tage ohne Neustart, deshalb war es unsichtbar. 🟡 Rest, bewusst liegen gelassen: run-rpc_pipefs.mount (101, 150) und proxmox-regenerate-snakeoil.service (108) — vorbestehend, ohne Bezug zu Netz oder DNS.
  3. ✅ ERLEDIGT 2026-09-01 (im selben Fenster aufgetaucht): LXC 302 war zu 100 % voll (62/65 GB), Rootfs +30G online gewachsen ⇒ 95 GB / 69 %, MariaDB lief durch. 🔴 Das ist ein Puffer, kein Fix: 59 GB davon sind die homeassistant-DB (states.ibd allein 41 GB), die unbegrenzt wächst (~1 GB/Tag) — die dauerhafte Lösung ist HA-seitig und liegt in TP40.
  4. ✅ ERLEDIGT 2026-09-01: Resolver-Sweep durchgezogen — pve UND NAS. pve: alle 13 LXCs (101,102,105,106,107,108,110,111,112,130,150,302,400) tragen nameserver 10.40.20.1, kein .41.254 mehr — nicht nur die fünf geänderten. NAS (DS923) via Portainer-API ep14: alle 20 Container gelistet, keiner mit .41.254-DNS-Override; alle laufenden lösen über den Embedded-Resolver (127.0.0.11) in 66–110 ms auf (rc=0), inkl. Grenzfall gitea (erstellt 2026-07-10, um den Host-Fix herum) — kein eingefrorener Dead-Resolver, kein Recreate nötig. NAS-Host /etc/resolv.conf = 10.40.20.1. Als wiederkehrende Kontrolle bleibt der Sweep als CLAUDE.md-Gotcha verankert (pve-Einzeiler + Synology-DSM-Fallstrick von 2026-07-10).
  5. ✅ ERLEDIGT 2026-09-01: Abnahme-Regel festgeschrieben. „Vor jedem geplanten LXC-Neustart systemctl --failed lesen und danach die Ports gegen vorher vergleichen" steht als CLAUDE.md-Gotcha (Fall influxdb2 wäre sonst still durchgerutscht).

Abhängigkeiten

  • Keine blockierenden. Punkt 1 brauchte DB-Credentials (siehe TP30, Credential-Bestand).
  • Punkt 2 war unabhängig und lief zuerst.

Abgrenzung

  • Nicht TP16.3: dort geht es um cloudflared/Tunnel: der DNS-Fund kam nur dabei ans Licht.
  • Nicht TP14.2a: das war 2026-07-10 derselbe Fehlertyp an anderen Hosts (NAS + LXC 400) und ist abgeschlossen. Dass er hier erneut auftrat, ist genau die Begründung für Punkt 4: die Klasse wurde damals an den bekannten Stellen behoben, nie im Bestand gesucht.
  • Nicht TP31/TP34 (Caddy) und nicht TP11 (Monitoring) — obwohl gilt: ein Monitoring, das DB-Verbindungszeiten kennt, hätte diesen Fall Monate früher gezeigt.
  • Nicht TP40: der HA-Recorder-Wildwuchs (Punkt 3) ist HA-seitig und ein eigenes TP.

TP34 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ▸ ❌ VERWORFEN (2026-09-08)

🔴 Nachtrag 2026-09-08 (verworfen): Der Ansatz „Caddyfile via Golden Config rendern" ist zurückgezogen (Commits d63dc7b, bca8f6e; Gitea-Issue #24 → status:dropped, closed). Der 420.teilzeitladen.xyz-Block wurde wieder aus dem GC-gerenderten Caddyfile entfernt; der Caddyfile bleibt manuell in caddy/Caddyfile. Gründe: kein zweites SSoT-Modell für Proxy-Routen nötig, TP37 deckt die Dienst-Doku als ipam.Service bereits ab, und der einzige akute Anwendungsfall ließ sich mit einem manuellen Block lösen. Text unten unverändert als Konzept-Historie.

Konzept + Architektur-Skizze: docs/handoff-20260827-inventur-und-architektur.md — bitte dort weiterlesen (Architektur-Abschnitt).

  • Scope: Reverse-Proxy-Hosts werden im Nautobot-Datenmodell gepflegt (Site, Host, Backend-Spec). Golden Config rendert den Caddyfile aus den Daten, Diff-Check gegen die Live-Version auf caddy-bf1, Compliance-Checks + Transaktionen identisch zum bestehenden Workflow für Router-Configs.
  • Auslöser (Inventur 2026-08-27): der Caddyfile (bf-infra/caddy/Caddyfile) ist heute manuell gepflegt — kein Drift-Check, keine Transaktionen, andere Projekte können keine Sites anlegen ohne Christoph. Lösung: Nautobot/GC, kein Caddy-API-Sidecar (kein zweites SSoT).
  • Erster Anwendungsfall: terpene.teilzeitladen.xyz → http://10.40.20.167:8890 (Mac-mini-Backend, dort PostgREST + sync + web via lokalem Caddy :8890). Vor TP34: manueller Quick-Win-Caddy-Block.
  • Abh.: TP22 (GC-Pipeline) ✅ · TP23 (DNS/Caddy-Voraussetzungen) ✅ Stufe 1 · 🔴 NEU 2026-09-06: TP37 Punkt 1+2 — Caddy rendert Config aus Daten, die Backend-Dienste müssen als ipam.Service in Nautobot existieren, sonst gibt es nichts zu rendern. TP37 vor TP34.
  • Datenmodell (entschieden 2026-09-06): native Modelle — Backend = ipam.Service, Proxy-Route = Relationship/CustomField Frontend↔Backend, GC-Job rendert den Caddyfile. Keine eigene App (konsistent zu TP44).
  • Status: 🔜 Stub; wartet auf TP37 P1+2. Quick-Win terpene.teilzeitladen.xyz → 10.40.20.167:8890 als manueller Caddy-Block ist Schritt 0 des Runbooks. Plan: handoff-20260906-tp37-34-36-plan.md.

TP44 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot ▸ ✅ DURCH (2026-09-10): Etappe 1 + Etappe 2 (Teil A IPAM-Prefixe, Teil B Live-Sync als täglicher Nautobot-Job)

Entscheid (User, 2026-09-02): die volle CF-Estate (Tunnel + WARP-Connector + VPC-Service + Hyperdrive + Workers + alle Access-Apps) als strukturierte, abfragbare SSoT in Nautobot abbilden — etappenweise: manuell jetzt, Sync später. Nautobot kann CF nicht steuern → „SSoT" heißt Dokumentations-SSoT; CF bleibt operativer Master. Detail-Handoff: handoff-20260902-cf-estate-nautobot-ssot.md.

  • Warum eigenes TP, nicht TP36: TP36 ist eng auf teilprojekte.md-Live-Daten geschnitten; gleiche Idee (Nautobot als Doku-SSoT), andere Dimension. TP44 = nächste freie Nummer nach TP43.
  • Modell (native nautobot.cloud, Nautobot 3.1.7 — keine eigene App): Manufacturer "Cloudflare" → CloudAccount (393c051f…) → CloudResourceType ×6 → CloudNetwork ×4 (bf-public/fm/terpene-db/ bf-core-mesh) + CloudService ×14; Service↔Tunnel über das Through-Modell cloud-service-network-assignments (3 Rows). IDs/Ports/Hostnames/Origins je Objekt in extra_config.
  • Werkzeug: nautobot/tp44-cf-estate-seed.py — idempotenter Seeder (Re-Run = 26/26 „existiert"), Datenstand inline (Live-CF-Inventur 2026-09-02). Token aus docs/nas-vlan20/stacks/210-nautobot.yml.
  • ✅ Verifikation 2026-09-06 (live gegen die API): CloudAccount 1 · ResourceType 6 · CloudNetwork 4 · CloudService 14 — unverändert seit 09-02.
  • 🔴 Gotchas: CloudService.cloud_networks ist read_only (nur Through-Rows schreiben/lesen) · VPC-Service-API unter connectivity/directory/services (PUT, voller Body) · VPC-Objekte tragen ihren Identifier in service_id, NICHT id (kostete im Sync einen Fehl-Match) · ⚠️ DRIFT-KORREKTUR 2026-09-10: die frühere Zeile „Repo-CF_API_TOKEN darf kein Workers-VPC → über den cloudflare-MCP" war falsch. Live getestet liest der CF_API_TOKEN aus ~/.secrets/credentials.env Hyperdrive und VPC und Workers (alle 200). Der schmale Token ist CF_DNS_API_TOKEN (403). Der cloudflare-MCP ist der remote OAuth-MCP (kein API-Token) und für einen serverseitigen Job ohnehin nicht nutzbar.
  • ✅ Etappe 2 Teil A (2026-09-08): IPAM-Prefixe live. 10.40.0.0/16 (container, Route bf-core-mesh) + 10.101.0.0/24 (network, Route fm) angelegt via nautobot/tp44-ipam-prefixes.py; der Seeder-Re-Run hat die zwei cloud-network-prefix-assignments eingehängt (count=2, Idempotenz-Gegenprobe grün).
  • ✅ Etappe 2 Teil B (2026-09-10): Live-Sync als Nautobot-Job — DURCH. Governance (User 2026-09-09): periodische externe→Nautobot-Syncs leben als Nautobot-Jobs in bf-infra/jobs/, in Nautobot geplant — kein per-Task-Container/-Cron. Runtime jobs/cf_estate_sync.py (CFEstateSync, ORM, read-only Pull, 403-tolerant, Verwaisung = extra_config.stale statt Löschen). Token = Nautobot-Secret cf-api-token (Provider env → CF_API_TOKEN am Worker, in 210-nautobot.yml verdrahtet). Schedule: täglich 03:00Z (ScheduledJob "CF Estate Sync – taeglich"). Verifiziert: Dry-Run + Commit-Run sauber, 4 Networks / 14 Services, alle Endpoints lesbar, 0 Drift. Der REST-Einmalproof nautobot/tp44-cf-sync.py bleibt als workstation-seitiges Diagnose-Werkzeug liegen (nicht die kanonische Runtime).
    • 🔴 Drei load-bearing Funde (Detail-Handoff handoff-20260910-tp44-teilb-sync-live.md): (1) Wurzel-__init__.py im Repo ist Pflicht — Nautobot lädt Git-Repo-Jobs via pkgutil.walk_packages([GIT_ROOT]), das Namespace-Packages (ohne __init__.py) NICHT entdeckt; ohne die Datei bleibt bf_infra.jobs unsichtbar (Symptom: Sync SUCCESS + „No jobs were registered"). bf-infra ist das erste Git-Repo mit extras.job. (2) register_jobs() gehört ins Paket-__init__, nicht nur ins Submodul. (3) Meta.has_sensitive_variables=False nötig, sonst verweigert Nautobot das Scheduling.
  • Beziehung zu TP37: TP44 hat TP37 Punkt 3 (CF-Connectoren) vorweggenommen — dieselbe native-Modell- Linie, die TP37 P1+2 (Substrat/Dienste) fortsetzt.

TP41 — CF-Access-Reparatur + Zero-Trust-Konsolidierung ▸ ✅ ABGESCHLOSSEN (2026-09-10) — Auth-Kern seit 2026-09-01 komplett; Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7.

Auslöser: CF-Access zwang Christoph und Geli täglich zum OTP-Login (soll: 30 Tage); GitHub/Google „funktionieren nicht". Root-Causes: Team-Domain-Rename bf4→emsenhuber (Provider-Callback veraltet), App-Session 24h, diverse Ordnungs-/Sicherheitslecks. Vollständiger Verlauf + offene Schritte: docs/handoff-20260901-tp41.md. Branch tp41-cf-access-repair.

✅ Live erledigt: Session 720h (Wildcard/vw/Warp-Login) · login_method-Loch zu (reusable Policy bf-users = nur email:christoph+email:geli) · 8e44f5d1 von 4 Apps auf nur Terpenfinder entkoppelt · Access-Gruppen zt-humans/zt-home/zt-automation, Policies zeigen darauf · vw voll Bypass (Lebensader offen) + vw/admin per WAF-Regel öffentlich geblockt (CF-Edge + Mobilfunk verifiziert) · API-Token auf das vollständige …4148 konsolidiert (User hat Service-Tokens- + Zero-Trust- Write ergänzt), altes …2174 revoken.

✅ T4 DURCH (2026-09-01, Variante B): ein Access-Service-Token zt-svc (65f1821a, rotiert) über Gruppe zt-automation; Terpene-Worker per wrangler secret put TERPENE_SVC_CLIENT_ID auf zt-svc umgestellt (Worker-Pin index.js:44); terpene-verify+-v2 gelöscht; test_whoami grün. Token-Altlast geklärt: b00722 = LIVE Caddy-DNS-Token (LXC130 caddy.env, SSoT), d47435 = stale Kopie (kein Consumer) → User revoket d47435; credentials.env-Kopie auf b00722 realignt.

✅ T3 APPLIED (2026-09-01): Default-Profil (0197d1b3; das custom „Cloudflare Mesh" matcht nur den warp_connector) fallback_domains += emsenhuber.net → [10.40.20.36, 10.40.20.37]; Tunnel-Route 10.40.0.0/16 (d1da7c8a) da, kein 10/8-Exclude → routbar. ⏳ Client-Verify: WARP neu verbinden. ✅ T2 DURCH (2026-09-01): WAF-Regel f26744eb gelöscht; Caddy vw-Block respond @cf_admin 404 bei Header CF-Connecting-IP (LXC130). Verify: Edge /admin=404, lokal/WARP /admin=200. ✅

✅ T5 DURCH (2026-09-01, User-bestätigt): GitHub-Login läuft; Google-Login läuft nach dem Consent-Screen-Fix (§A des Follow-up-Handoffs: User Type Internal→External/veröffentlicht). Callback für beide https://emsenhuber.cloudflareaccess.com/cdn-cgi/access/callback. ❌ Facebook/Meta für TP41 abgesagt (2026-09-01, User-Entscheid): optional, kein Bedarf — Geli ist über OTP angelikahy@outlook.de + Google pillepalle670@gmail.com voll gedeckt. Keine Facebook-App, kein CF-Facebook-IdP angelegt → nichts aufzuräumen. Instagram hat ohnehin keinen CF-IdP. ✅ App-Launcher (D1): je eine Bookmark-Kachel vw/hass/terpenfinder, Wildcard-Kachel raus, Launcher gestylt (Follow-up-Handoff §D, Policies unverändert).

✅ Geli in WARP — CF-seitig nichts zu tun (2026-09-01 live geprüft): Device-Enrollment läuft über die App „Warp Login App" (6c32fe23…, type warp) → Policy bf-users → Gruppe zt-humans, die Gelis beide Mails schon enthält → sie darf bereits enrollen (deshalb war „Device enrollment permissions" nicht als fehlendes Setting zu finden). Terpenfinder-geli-Policy lässt sie per E-Mail durch. ✅ Client am 2026-09-10 via Android erfolgreich eingerichtet (WARP installiert, Team emsenhuber, Login OTP angelikahy@outlook.de — User-Bestätigung); damit ist der einzige verbliebene Handgriff erledigt. 🔑 Permission-Frage beantwortet: CF-seitig keine nötig. Für generelle WARP/Enrollment/User-Verwaltung per Account-Token: Access: Apps and Policies → Edit + Access: Organizations, IdPs, and Groups → Edit (User erteilt 2026-09-01). Seat-Verwaltung (/access/seats) bleibt per MCP gesperrt — 10405 Method not allowed for this authentication scheme (Endpoint will User-Token, nicht Account-Token; kein Scope-Fix, für Enroll irrelevant).

🔻 Optionale Restpunkte (bewusst nicht gezogen, kein Blocker): T6 optional Rate-Limit · 🔴 TERPENE_ANDROID_KEYSTORE_PW-Rotation (→ TP30 Posten #7, Transkript-Leak) · Security-Cleanup: die geli-Terpenfinder-Policy hat OR-Semantik und enthält den breiten CGNAT-Bereich 94.154.138.0/24 als IP-Include → jeder aus dieser geteilten Range käme ohne Login rein. Über WARP ist der Egress ohnehin Cloudflare (nicht diese IP) → der IP-Include wäre dann überflüssig und entfernbar. d47435-Revoke ✅ · T3-WARP-Client-Verify ✅ (User, 2026-09-01).

Abgrenzung: hass/gitea bleiben (diese Runde) öffentlich; Terpenfinder-Human-Policies nicht angefasst; Gitea-Least-Privilege = eigener künftiger TP (jedes Projekt repo-scoped Login, Admin nur Claude/Christoph).

  • Bezug TP11 (Monitoring): eine Plattenfüllstand-Warnung hätte das Wochen früher gezeigt.

Auslöser (2026-09-01, TP41-Follow-up): Geli soll 420.teilzeitladen.xyz am dw-Standort erreichen. Split-Horizon am dw scheitert an der FritzBox (10.95.10.1): kein Per-Host-A-Override im Standard (nur DNS-Rebind-Ausnahmen ohne IP). Christoph: „keine Chance im FritzBox-Standard? … lieber ein TP als uns verfranzen. Geli kommt rein, das zählt."

Ziel: dem dw-Standort einen eigenen Resolver/DNS-Uplink geben, der site-spezifische Overrides hält (erster Fall 420.teilzeitladen.xyz → BF-Caddy über WG), als DNS der FritzBox/Clients gesetzt.

Kernfrage (explizit): Verhalten bei WG-Disconnect. Ein Override auf eine BF-interne IP (10.40.20.40) fällt aus, wenn der Tunnel unten ist. Für einen Login-gegateten Dienst gibt es kein „kein Login und funktioniert bei WG-down" — Optionen: (a) Override nur bei WG-up (Failover-Resolver), (b) öffentlicher Pfad (CF, mit Login) als Fallback. Zu entwerfen.

Sofortlösung (nicht Teil von TP43): Geli per WARP (Session statt täglichem Login, CGNAT egal, degradiert sauber: WG-up = nahtlos, WG-down = öffentliche URL + Login).

BF-seitige Prereqs: Caddy-vhost 420.teilzeitladen.xyz → macmini-Worker (10.40.20.167:8890) mit Cert (DNS-01, …4148 hat teilzeitladen.xyz-DNS) · WG-Route dw→10.40.20.40 · Split-Horizon für 420.teilzeitladen.xyz (heute liefern auch .36/.37 public). Vorher verifizieren: FritzOS-Version + Dauerläufer am dw. Cross-Projekt (teilzeitladen.xyz). Handoff: docs/handoff-20260901-tp41-followup.md.

Status: 🔜 angelegt 2026-09-01, eigene Session.

TP42 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net ▸ ✅ DURCH (2026-09-02) — ein interner Suffix bf.emsenhuber.net, .bf.lan raus, .16 (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an TP37 gekoppelte Nautobot-Doku-Teil.

Auslöser (2026-09-01, TP41-Randbefund): live gemessen liefern die realen Client-Resolver 10.40.20.36/.37 *.emsenhuber.net → 10.40.20.40 (Caddy) — korrekt. Daneben existiert aber ein zweiter, gedrifteter interner Namensraum: der Core trägt in /ip dns static 11 Einträge auf *.bf.lan, der Caddyfile doppelt jeden internen Namen als *.bf.emsenhuber.net, und die alten AdGuard .16/.17 antworten noch mit nginx*.bf.emsenhuber.net bzw. totem VLAN1 10.40.41.123. Christoph: „aus emsenhuber.net wurde schon wieder bf.lan — das müssen wir ein für alle mal aus allen Geräten rausprügeln und in Nautobot festhalten."

Ziel: genau ein interner Namensraum emsenhuber.net (Split-Horizon .36/.37 → .40). .bf.lan und .bf.emsenhuber.net werden entfernt; Sollzustand in Nautobot dokumentiert.

Scope (Reihenfolge = eigene Session):

  1. Bestandsaufnahme aller Fundstellen: Core /ip dns static (*.bf.lan), DHCP-Option domain/Search-Domain je Netz, Caddyfile-Aliase (*.bf.emsenhuber.net), AdGuard-Rewrites .36/.37, stale .16/.17, Geräte-identity.
  2. Entscheidung Zielschema (nur emsenhuber.net, ggf. bf.emsenhuber.net als einzige interne Zone — Christoph-Entscheid) und Migrationsreihenfolge ohne DNS-Loch.
  3. Umsetzen: Core-DNS + DHCP + Caddyfile angleichen; stale .16/.17 klären/abbauen.
  4. Nautobot: interne Namen/Records als Sollzustand erfassen (Bezug TP37 Datenmodell).

Abgrenzung: kein CF-Public-DNS-Umbau (Zone emsenhuber.net öffentlich bleibt); kein Resolver-Swap (.36/.37 bleiben); .1-als-DNS-Reste auf Clients sind separat/tolerierbar (User: „bis jetzt kein Problem"). Abh.: TP37 (Nautobot-Datenmodell) für Punkt 4; sonst nichts Offenes.

✅ Bestandsaufnahme LIVE (2026-09-02) — Drift hat 80 % schon erledigt

Gegen die Geräte gemessen (MikroMCP + dig), nicht aus dem Handoff:

Fundstelle Handoff 09-01 Live 09-02
Core /ip dns static 11× *.bf.lan ✅ 0× .bf.lan — 10× *.bf.emsenhuber.net (deckt sich mit GC-Template Z.284–293)
DHCP domain (7 Netze) „prüfen" emsenhuber.net (GC-Template Z.133 = gleich ⇒ keine Drift), DNS .36/.37
.36/.37 (Soll) aktiv vw.emsenhuber.net→.40 ✅ · macmini.bf.emsenhuber.net→.20.167 ✅ · macmini.emsenhuber.net→leer ❌
Caddyfile *.bf.emsenhuber.net-Aliase unverändert, konsistent mit Core-Static
.16/.17 tot laufen (:53), liefern totes VLAN1; kein Core-Objekt (Firewall/NAT/DNS-Upstream/Addr-List) hängt an .16

Kernkonflikt: DHCP verteilt emsenhuber.net (bare), Records leben unter bf.emsenhuber.net ⇒ bare macmini löst nicht.

✅ Entscheidungen (User, 2026-09-02)

  1. Zielschema = bf.emsenhuber.net (der wenig-Arbeit-Weg): DHCP-domain → bf.emsenhuber.net, dann löst macmini bare korrekt; Core-Static + Caddyfile bleiben (tragen schon .bf.). Öffentliche Zone emsenhuber.net bleibt getrennt.
  2. .16 (adguard_bf1) abbauen, .17 (adguard_fm2 = Standort FM) NICHT anfassen.

✅ Umsetzung DURCH (2026-09-02)

  • (A) DHCP-domain emsenhuber.net → bf.emsenhuber.net über GC ✅ — kein Direkt-Write: mikrotik_routeros.j2 Z.133 gehoben → push-gc-template.py (Commit 7a4068c4, Nebenbefund: das Remote-Template hing dem lokalen hinterher — es trug noch *.bf.lan+router.lan, obwohl Gerät+lokal längst .bf.emsenhuber.net fuhren; der Push hat das GC-Repo an den Live-Stand angeglichen) → GitRepo-Sync (SUCCESS) → manueller ConfigPlan tp42-dhcp-domain (set [find] domain=…, weil add das Netz doppelt anlegen würde) → Deploy an crs326-core → Live 7/7 = bf.emsenhuber.net, Compliance 140/140, keine Drifts. Der Job steht dauerhaft in nautobot/gc-configplan.py.
  • (B) .16-Teardown ✅ — adguard_bf1 lag im geteilten Portainer-Stack 195 adguard (Docker-LXC .126, ep2 — nicht ep14) zusammen mit adguard_fm2 (.17, Standort FM!). Deshalb nicht Stack löschen, sondern compose auf nur .17 getrimmt + Stack redeployt (Prune): .16-Container weg, .16:53 tot; .17 läuft (⚠️ ~40 s mit-neugestartet durch den Redeploy — kurzer FM-DNS-Blip, danach sauber). Rollback-compose gesichert. 🧹 Daten-Dirs /opt/docker/adguard_bf1/* bleiben als harmlose Leichen auf dem LXC.
  • (C) Nautobot-Soll (Scope-Punkt 4) bleibt an TP37 gekoppelt (Datenmodell), separat — kein Blocker.

Status: ✅ Namensraum vereinheitlicht (2026-09-02) — ein interner Suffix bf.emsenhuber.net, .bf.lan raus, .16 abgebaut, Compliance 140/140. Offen nur der TP37-gekoppelte Nautobot-Doku-Teil. Handoff: docs/handoff-20260901-tp42-naming.md.