Files
bf-infra/docs/teilprojekte.md
Christoph Emsenhuber 6c2297ad95 Sideboard-Test zurueckgebaut, und was dabei nicht verschwindet
TEST-ether3-trunk-ap1-sideboard ist zurueckgerollt und geschlossen: ether3
faellt aus allen tagged-Listen und ist in VLAN40 wieder untagged (Sideboard-
Access-Port). Am Geraet gegengeprueft, Snapshot-Diff leer. crs326-core ist
damit transaktionsfrei.

TP25 Fund (G): ap1 am Sideboard richtet das Wohnzimmer nicht und kostet das
Schlafzimmer (-61 -> -83 dBm). Die Idee "ap1 ersetzt den Sideboard-Switch" ist
damit entschieden -- nein, Platzierung vor eingespartem Geraet. ap2 haengt
jetzt auf 2,5 m, Terrasse subjektiv besser, Nachmessung steht aus.

Korrektur im selben Zug: die erste Fassung von (G) behauptete, der Steering-
Fall entfalle mit dem Rollback. Falsch -- es entfaellt nur die konkrete
Auspraegung. Fund B und C wurden am 08-20 mit ap1 auf ether15 gemessen, also
in genau der Stellung, in die wir zurueckgebaut haben; der Defekt ist
platzierungsunabhaengig und verlagert sich nur. Fund E bleibt die Massnahme
und ist jetzt oberster offener Punkt.

TP12.1 neu (unter TP12, kein Duplikat daneben): Matter/Thread-Commissioning
scheitert an fehlendem IPv6-Routing zwischen VLAN10 und VLAN20 -- nicht an der
Firewall. Der Kostenpunkt des TP ist aber nicht die Adressvergabe, sondern
dass /ipv6 firewall filter leer ist: heute harmlos, ab der ersten Adresse ein
Loch an TP5 vorbei. Reihenfolge deshalb zwingend v6-Firewall vor Adressen.

Runbook + Kommandodateien fuer die temporaere dirtyvader-infra-SSID
(cfg-infra existiert bereits, haengt nur in keiner provisioning-Zeile).
Vorbereitet, nicht deployt.

Repo-lokalen projektstatus-Skill geloescht: er war seit 11. Juli stale
(harte Themenblöcke, kritischer Pfad von TP14.3) und wurde nicht geladen --
aktiv ist der globale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 19:13:26 +02:00

306 KiB
Raw Blame History

BF-Netzwerk — Teilprojekte (kanonische Steuerung, Stand 2026-06-10)

Zweck: Das große Netz-Projekt in einzeln durchziehbare Teilprojekte (TP) zerlegt — jedes mit klarem Scope, aktuellen Entscheidungen, Abhängigkeiten und Status. Jedes TP ist so geschnitten, dass es eine frische Session mit sauberem Kontext straight abarbeiten kann. Architektur/Historie: ~/.claude/plans/ich-will-das-delegieren-atomic-sunrise.md.

Kanonisches VLAN-Modell (gültig — überschreibt ältere Tabellen)

VLAN Name Subnetz (Ziel) Zweck
10 MAIN 10.40.10.0/24 Trusted (Handy/Laptop/Apple Watch)
20 INFRA 10.40.20.0/24 NAS/VMs/Docker/HA
30 IOT 10.40.30.0/24 Smart-Home (HA-managed), Waage, Fitness-Tracker
40 MEDIA 10.40.40.0/24 Sonos, AppleTV, TV, VuPlus, Cast-Ziele (getauscht ggü. früher)
50 UNTRUSTED 10.40.50.0/24 Spielekonsole, eBook-Reader, Low-Trust-Internet
90 GUEST 10.40.90.0/24 nur Internet
99 MGMT 10.40.99.0/24 Switch/AP/Hypervisor-Mgmt
100 WAN (Arris-Transfer) zum Arris-Kabelmodem (Liwest, Bridge-Mode, Public-IP an ap1)
IP-Blöcke: .1–10 Router/Gateways · .11–20 Switches/APs. GW je VLAN = .1 (crs326-core).
Mgmt (VLAN99, Ist 2026-08-16): core .99.1 · branch .99.2 · ap1 .99.11 · ap2 .99.12.
(Historisch, überholt: „ap1=.1, ap2=.2 · ALLNET=.11, Omada=.12" — aus der Zeit vor dem Core-Cutover.)

Ist-Stand (Kurz)

Stand 2026-08-18 — live gegen die Geräte geprüft (MikroMCP/REST), nicht aus der Doku abgeschrieben. (Die Fassung vom 2026-08-16 stand bis zum 18. hier und war an vier Stellen überholt — Standalone-APs, RouterOS-Versionen, „plain 1.1.1.1", 68/68. Wer diesen Block liest, liest ihn als Erstes: er wird ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)

  • Alle vier MikroTik laufen auf RouterOS 7.24 (stable, 2026-08-17 von Christoph selbst gehoben, TP27), RouterBOOT ebenfalls 7.24 auf allen vieren (current-firmware = upgrade-firmware, geprüft 08-18).
  • L3 + WAN-Edge = crs326-core (CRS326-24G-2S+, Technik UG): Routing, DHCP (7 Server, 54 Reservierungen), DNS, NAT, NTP-Server für den Standort und der WG-Hub (TP10, Peers fm/dw) laufen hier. Mgmt-SVI 10.40.99.1 (vlan99), Gateway .1 je VLAN. WAN = vlan100 — DHCP-Client hält die öffentliche Liwest-IP 90.146.33.177/24 direkt vom Arris (Bridge-Mode) → kein Double-NAT. DNS-Upstream = DoH zum CF-Gateway mit verify-doh-cert=yes (TP23 Stufe 1 ✅ 08-18); servers=1.1.1.1 steht nur noch als Bootstrap für den DoH-Hostnamen.
  • 🔥 Die Firewall ist deny-by-default — in BEIDEN Ketten (TP5 E3/E4 ✅ 08-18). TP10-seal-lan ist disabled; Mgmt-Input kommt nur noch aus VLAN10, von HA (8728), von Nautobot (22) und über cf-mesh. Ein neuer Dienst über eine VLAN-Grenze braucht ab jetzt eine explizite Regel.
  • L2-Branch = crs326-branch (Technik OG): reiner L2-Switch, Mgmt 10.40.99.2, Uplink ether1 → core ether8.
  • WLAN = CAPsMAN auf dem Core (TP28 ✅ 08-17, GC-Modell nachgezogen 08-18): ap1 10.40.99.11 (an core ether15), ap2 10.40.99.12 (an branch ether24), beide reine CAPs — cap enabled=yes, discovery-interfaces=vlan99. Die APs halten keine eigene WLAN-Config mehr; Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, ft-over-ds=yes.
  • SSDP-Relay (TP7.1 ✅ 08-18): LXC 108 mcast-relay auf pve, zweibeinig 10.40.10.21 (VLAN10) + 10.40.40.21 (VLAN40) — dafür trägt core ether13 beide VLANs. Die macOS-Sonos-App findet damit ihr System; mDNS macht weiterhin der Core.
  • Ausgebaut / gelöscht: ALLNET-sw1 und Omada-sw2 sind physisch weg (2026-08-08 bzw. 2026-07-23, in Nautobot Decommissioned); Omada-Controller + omada-mcp gelöscht (2026-07-16); FritzBox 6660 raus seit 2026-07-01 (Liwest-Tausch auf Arris). VLAN1 ist leer (/ip arp auf bridge = 0 Einträge, 08-18) — TP19 ist geschlossen; cudy1 hängt stromlos an core ether11, physischer Ausbau ist hands-on.
  • Kontrolle / Governance: MikroMCP für Reads/Debug (alle 4 Geräte in der Registry), Config-Änderungen ausschließlich über Nautobot Golden Config (TP21/TP22 — Deploy-Pipeline scharf, 92/92 Compliance-Checks grün, 0 Drifts bei 23 Rules, Stand 2026-08-18).
📜 Historische Fassung (Stand bis 2026-08-08, Topologie vor dem Core-Cutover — bewusst erhalten, nicht als Ist lesen)

ALLNET-Core .11 (FW2.2.1, VLANs 10/20/30/40/50/90/99/100; GE1/GE2/GE8 Trunk; TP1 ✅ 2026-06-15). Omada-Switch .12 (adopted, TP2 ✅ 2026-06-15; Trunk-ALL auf P1+P2, Access-Profile auf P3-P8; Profile noch PVID=1 bis Omada-PVID-Fix). Omada-Controller .175 (DS923/macvlan, mbentley 6.2, 🟢 live). omada-mcp .176 (pve-LXC, StreamableHTTP, 🟢 live, 84 read-Tools; Controller-API v2 intern via christoph@emsenhuber.com). 2× hAP ax² (7.23.1): ap1 .1 (ALLNET P1) — TP4 ✅ 2026-07-01: Router + WAN-Edge (VLAN-Subinterfaces, DHCP, DNS, NAT, Public-Edge). ap2 .2 (Omada P2 — Trunk). Edge-Cutover ✅ 2026-07-01: FB6660 physisch entfernt (Liwest-Kabelmodemtausch, kein Zurück), Arris-Kabelmodem im Bridge-Mode; WAN = sw1 gi16 Access VLAN100 → gi15 Trunk → ap1 ether2 → vlan100 auf bridge (NICHT ether1); ap1 hält Public-IP direkt (Liwest, kein CGNAT), Double-NAT weg. Telefonie war auf der FB und wird nicht weitergeführt. Vorarbeit: TP10-Kern durch (2026-06-27, WG-Hub live) → Bridge-Gate vorher gelöst. Runbook: routeros/wan-cutover-runbook.md. Kontrolle: MikroMCP (MikroTik) live · ALLNET = Klickanleitungen · Omada → Controller + omada-mcp 🟢.

Nächster Schritt (Stand 2026-08-21 abends): TP25 — Steering-Test in den Schlafräumen.

📋 Einstieg: handoff-tp25-session-20260821.md — dort steht der verifizierte Ist-Stand, die Messfallen und der nächste Test. ⚠️ Auf crs326-core ist eine Config-Transaktion OFFEN (TEST-ether3-trunk-ap1-sideboard, Frist 2026-09-04): ap1 hängt testweise am Sideboard, ether3 wurde dafür zum Trunk. Die Compliance ist deshalb absichtlich nicht 92/92 — python3 nautobot/gc-transaktion.py status. 🆕 TP29 (Anwesenheitserkennung fürs Energiesparen) ist als Idee angelegt, nicht auf dem Pfad. 📌 Die TP6-Prämisse ist am 2026-08-20/21 gemessen (Runbook §5): formal Fall B (+28…+47 ms unter Dauer-Upload), aber im Videocall nicht wahrnehmbar — kein einziger Problem-Klick über sechs Lastphasen. Laut war stattdessen der Funk: Sticky Client + Band-Ping-Pong, zwölf Wechsel in neun Minuten, je Wechsel bis zu 4 s ohne Durchsatz → tp25-wlan-roaming-messung-20260820.md. Deshalb steht jetzt TP25 vor TP6; TP6 bleibt offen und begründet, aber nachrangig.

Vorherige Fassung (Stand 2026-08-18, erledigt): TP6 wird hier seit Monaten als „DAS Ziel" geführt, aber docs/call-quality-diagnosis.md hat den Bufferbloat 2026-06-16 bereits gemessen (A/A+, „CAKE wäre marginal, nicht deployt"). Die Messung stammt aus der FritzBox-Ära, der Edge ist seither getauscht — also einmal gegen den heutigen Edge nachmessen und dann entscheiden. Blocker gibt es keine mehr. TP5 ist in der Nacht auf den 18. abgeschlossen worden (Etappe 3 + 4: deny-by-default in forward und input, MGMT isoliert, seal-lan abgeschaltet), TP23 Stufe 1 (DoH auf dem Core) ebenfalls. Nebenher offen, blockiert nichts: TP25 (Signal-Boden + Re-Walk), TP28-Rest (die fünf wifi-*-Features vom AP-Scope auf den Core umhängen — die einzigen verbliebenen Compliance-Drifts), TP7.1 (SSDP-Relay für die macOS-Sonos-App), TP16.3, TP24. Vorherige Fassung (Stand 2026-08-14, überholt): „TP5 — Deny-by-default am Ende von forward." Alles Davorliegende ist durch: CRS326-Bring-up + L3-Umzug ap1→crs326-core (TP15/TP21.1 ✅ 2026-08-08), WLAN-Neuprovisionierung ohne CAPsMAN (TP3 ✅ 2026-08-08), Nautobot-Plattform + Fleet-Adoption + Deploy-Pipeline (TP21/TP22 ✅ 2026-08-13). Danach TP6 (CAKE) = das Projektziel. Kleine Follow-ups nebenher: DoH-Cloudflare auf core, DC-USV (TP24, seit 2026-08-16 eigener TP). ⚪ Omada-PVID-Fix ist obsolet (User-Entscheid 2026-07-22 — Omada spielt seit der TP15-Korrektur keine Rolle mehr im Netz, nie mehr aufgreifen). Vorherige Fassung (Stand 2026-07-22, erledigt): „TP15 CRS326-Bring-up als neuer L3-Core, danach WLAN-Neuprovisionierung ohne CAPsMAN; TP5 folgt nach TP21/CRS326-Adoption." 🎯 Ziel: sauberes System „from scratch", keine Leichen/Altlasten. Migrations-Session-Endstand = VLAN1 + altes cudy-WLAN komplett weg (VLAN1 nur noch Mgmt, defconf-Client-DHCP zurückgezogen, cudy-Uplinks aufs Ziel-Client-VLAN getaggt oder cudy abgebaut). Unterpunkte/Aufräum-Liste bei Migration: (a) die Interim-Härtung ap2 wifi1/wifi2 frame-types=admit-only-vlan-tagged (TP3.1, 2026-07-06) bleibt gültig — verschwindet automatisch mit VLAN1; (b) st-main rrm/wnm = reines Roaming-Provisorium → kein Provisorium mehr (2026-08-13, TP3.3): Steering ist fester Bestandteil des Golden-Config-Templates + ComplianceFeature wifi-steering — es war die Neuprovisionierung, die es stillschweigend verloren hatte; (c) cudy1 = verbleibender aktiver VLAN1-Leak → ERLEDIGT (User-bestätigt + verifiziert 2026-08-14): cudy1 ist offline. core ether11 trägt kein Link (kein R-Flag), Nautobot-Status Offline (TP22 Schritt 5). Damit existiert kein aktiver VLAN1-Leak mehr; der Dual-DHCP-Race aus project_vlan1_dhcp_incident ist Historie. Der formale Teardown (Gerät ausbauen, Doku/Inventar) bleibt TP19. ⚠️ HW-Constraint hAP ax²: 1 GB RAM, aber nur 128 MB Flash + KEIN USB → Container auf dem Router NICHT praktikabel. Folge: native RouterOS-Features bevorzugen (z.B. Built-in-mDNS statt Container); Container (mDNS-Reflector, BIND9-DNS, Omada-Controller) laufen auf NAS/pve, nicht am hAP. 🔒 Governance-Entscheid (User, 2026-07-16, ausgeweitet 2026-07-16): Config ausschließlich über Nautobot — keine Direkt-Changes mehr an einzelnen im Netz verstreuten Geräten. Ursprünglich als ap1/ap2-Config-Freeze bis TP21 formuliert, noch am selben Tag verallgemeinert: jede Netz-Gerätekonfiguration (Router, APs, künftig auch die CRS326-Switches) läuft ab jetzt exklusiv über Nautobot Golden Config (TP21), sobald das steht — keine ad-hoc MikroMCP-Writes/UI-Klicks mehr an Einzelgeräten. TP19.1 war die letzte Ausnahme (ap1-DNS-Fix + ap2/sw1/sw2-Mgmt-VLAN-Umzug, s.u.) — davon ist nur der risikolose ap1-Teil noch direkt gemacht worden, ap2 wurde bewusst auf „nach Nautobot" vertagt. Macht Nautobot + alle Prerequisites (TP18→TP20→TP20.1→TP21, jetzt auch Komodo) zum aktuell kritischen Pfad, vor TP5/TP6. Betrifft nicht Software-Migrationen auf NAS/pve (anderes Substrat) und die Frage, ob sw1/sw2 vor der CRS326-Ablösung noch angefasst werden (offen, s. TP15/TP19.1). ✅ Genehmigte Ausnahme (User, 2026-07-17): der zentrale CF-Tunnel-Zugang auf ap1 (TP16/TP16.1). Alle für den zentralen Cloudflare-Zugang auf ap1 notwendigen (Vor-)Arbeiten dürfen direkt via MikroMCP/CF-API gemacht werden, ohne auf Nautobot GC zu warten — konkret die nativen WireGuard-Mesh-Zeilen aus TP16.1 (und analoge cloudflared/WG-Anpassungen am ap1-Edge). Begründung: Remote-Erreichbarkeit ist die Voraussetzung, um überhaupt sicher am Netz zu arbeiten (Henne-Ei zu Nautobot). Scope-Grenze: Die Ausnahme gilt nur für ap1. Sobald der zentrale Zugang später auf die CRS326 wandert (TP15), gilt die Ausnahme dafür vorerst NICHT — CRS326-Config läuft dann regulär über Nautobot GC (oder braucht eine eigene, dann neu zu erteilende Ausnahme). Nachtrag der ap1-Ausnahmezeilen in die GC als Follow-up, sobald Nautobot steht. ✅ Genehmigte Ausnahme #4 (User, 2026-08-16): die WireGuard-Zeilen des Cloudflare-Mesh-Bring-ups auf crs326-core (TP16.1). Dürfen direkt via MikroMCP/REST gesetzt werden. Begründung ist strukturell, nicht bequem: TP22-Regel 1 schließt jede Sektion mit <<REDACTED>> generell vom GC-Push aus, und /interface wireguard ist genau so eine — ein privater Schlüssel ist über Golden Config nicht deploybar. Damit ist die frühere TP16.1-Zusage „CRS326-Zeilen laufen regulär über GC" gegenstandslos. Auflage: vorher /system backup, danach Ist-Stand als intended config zurück nach Nautobot. Scope-Grenze: nur der Mesh-Bring-up auf crs326-core — nicht spätere WG-Änderungen, nicht andere Geräte, und sie verlängert sich nicht automatisch. Langfristige Auflösung = das Secret-Handling-Design in TP16.1 (Privkey ist Geräte-Identität, nicht Config). ✅ Genehmigte Ausnahme #2 (User, 2026-07-22): der CRS326-Bring-up (TP15). Die Grundkonfiguration beider CRS326 — Management-IP, Bridge, VLAN-Filtering, Trunks/Access-Ports — darf direkt via MikroMCP/Winbox gemacht werden. Begründung = Henne-Ei: Golden Config kann nicht auf ein Gerät pushen, das im Werkszustand keine erreichbare IP hat, und die GC-Templates existieren noch nicht (TP21 „offen: Templates/intended config"). Auflage: der so entstandene Ist-Stand wird anschließend als intended config nach Nautobot zurückgeschrieben, danach übernimmt GC — die Ausnahme deckt das Bootstrapping, nicht den Dauerbetrieb. Scope-Grenze: gilt für die beiden CRS326 im Bring-up. Sie hebt die Regel für ap1/ap2 nicht auf (deren Neuprovisionierung läuft weiterhin über GC) und verlängert sich nicht automatisch auf spätere CRS326-Änderungen. Damit ist die frühere Formulierung in TP16.1 („CRS326-Config läuft regulär über Nautobot GC oder braucht eine eigene, dann neu zu erteilende Ausnahme") erfüllt — dies ist diese Ausnahme.


Teilprojekte

TP1 — ALLNET Core-Switch finalisieren ▸ ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt)

  • ✅ GE8 Trunk (native vlan1, tagged 10/20/30/40/50/90/99/100) — war bereits aktiv.
  • ✅ VLAN 40 umbenannt: UNTRUSTED → MEDIA (switch/cli/04-vlan50-media-untrusted.cli).
  • ✅ VLAN 50 angelegt + benannt: UNTRUSTED.
  • ✅ GE1/GE2 (AP-Trunks) um VLAN 50 ergänzt; GE8 auto-includiert (Trunk).
  • Ergebnis: Core trägt alle VLANs (10/20/30/40/50/90/99/100) an P1/P2/P8.

TP2 — Omada Access-Switch + Controller ▸ ✅ FERTIG (2026-06-15)

  • ✅ Omada Controller 6.2.10.17 auf DS923 (macvlan .175): omada/docker-compose.yml, Stack via Portainer endpointId=14. Wizard abgeschlossen (configured=true), Creds in mcp/omada/.env.
  • ✅ pve-mcp (samik081) in Claude Code eingetragen (~/.claude/settings.json, SSE .173:3000).
  • ✅ omada-mcp live auf pve-LXC (.126, endpointId=2), ipvlan-IP .176, Stack Id=200, URL in ~/.claude/settings.json.
    • Transport: StreamableHTTP http://10.40.41.176:3000/mcp (Health /health → 200). Settings-Bug (doppelter Eintrag) behoben 2026-06-15.
    • Auth: Omada Open API Client-Credentials. Creds in mcp/omada/.env. Token-Format: Authorization: AccessToken=<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).

TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt

  • Kontext (2026-07-08/09): Airfryer „airschreier" (Philips/Versuni, ESP, 10.40.30.180) bekam per DHCP-Option 6 dns 1.1.1.1 als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — kein Content-Block, via Gateway-Logs bewiesen, s. memory/ref_esp_ota_doh_not_block). Nebenwirkung: public 1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH (px35ynlol3.cloudflare-gateway.com) → DNS-Sicht auf das Gerät weg, Gerät umgeht zudem die Filterung.
  • (a) DNS-Redirect statt Geräte-Override (der dauerhafte Fix): dst-nat „UDP/53 (+TCP/53) von Low-Trust-Src → auf einen plain Resolver umbiegen" gibt Logging zurück UND funktionierendes OTA (kein langsamer DoH-Pfad fürs Gerät). Redirect-Ziel-Optionen: (i) ap1-Resolver mit nicht-DoH-Upstream für dieses Src-Set, (ii) direkt public 1.1.1.1 (dann nur Flow-Logging), (iii) eigener plain-Resolver. Sauberer als „Gerät verschwindet auf 1.1.1.1". Verortung: Teil des UNTRUSTED-Internet-only-Pinnings (TP5-„Offen"); Entscheidung v50-DNS-Policy (gefiltert vs. plain) hängt dran.
  • (b) Live-Torch als Ad-hoc-Observability: DNS ist bei direktem 1.1.1.1 blind, Flows nicht (NATen über ap1). torch auf vlan30/vlan50 gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS eu-west-1 IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln.
  • Abh.: UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker.

TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ 🟡 GEMESSEN 2026-08-20/21 — Fall B, aber „messbar, nicht hörbar"

✅ Die Prämisse ist geprüft. Ergebnis + Rohdaten: tp6-praemisse-messrunbook.md §5.

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

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

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

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

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

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

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

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

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

TP8 — Identity & Access-Control ▸ research-gated

  • RADIUS = FreeRADIUS auf der NAS (✏️ User-Manager v5 kann KEIN LDAP → openldap nur via FreeRADIUS anbindbar).
  • EAP-TLS (Zert) je Gerät + dynamische VLAN-Zuweisung (RADIUS-Attribut Tunnel-Private-Group-ID). Einfachste Heim-Alternative: pro VLAN eine SSID mit WPA3-PSK (kein RADIUS) — Abwägung Komfort vs. Admin-Aufwand.
  • Abh.: TP3. (Research erledigt, s. TP-R.)

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

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

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

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

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

TP11 — Monitoring & Mgmt-Control-Plane ▸ research-gated

  • MikroMCP + omada-mcp + SNMP/icinga-mikrotik, RoMON, NTP, E-Mail-Alerts, Netwatch. → Teil von docs/research/routeros-capabilities.md.
  • CF-Tunnel #1033 Track A ✅ live (2026-06-30): Round-Robin-1033er über 3 asymmetrische Connectors behoben durch Reduktion auf pve-only Connector (NAS + macmini scheiterten an pve-lokalen/stalen Origins). SSoT: docs/cf-tunnel-mapping.md. Track B/C läuft über TP14 (Ingress bereits v116 .41→.20 für NAS; pve-Connector→VLAN20 via TP14 Session B = #1033-Zielbild).

TP12 — IPv6 (intern + extern, 3 Standorte) ▸ research-gated

  • Konzept für FM/BF/DW. → TP-R.

TP12.1 — IPv6 im LAN (ULA) + v6-Firewall ▸ 🟠 KONKRET GEWORDEN (2026-08-22), Treiber: Matter/Thread

Anlass: der runde IKEA-Fühler TIMMERFLOTTE (Matter over Thread) ließ sich nicht anlernen — „pairing failed" in der Companion-App. Ursache ist nicht die Firewall, sondern dass zwischen VLAN10 und VLAN20 überhaupt kein IPv6 geroutet wird. Damit ist TP12 vom Konzept-TP zu einem Punkt mit einem konkreten, wiederkehrenden Schaden geworden.

  • Befund am crs326-core (live gelesen 2026-08-22):
    • IPv6-Adressen: ausschließlich Link-Local (fe80::…) auf jedem Interface. Keine ULA, keine GUA.
    • IPv6-Routing-Tabelle: nur ::1/128 und fe80::/64 je Interface. Keine Route auf den Thread-OMR-Prefix fd92:505a:c64e:1::/64 und keine auf fd03:cace:953f:e13d::/64 (den On-Link-Prefix, den der OTBR auf VLAN20 wirbt).
    • /ipv6 nd: Default-Eintrag, advertise-dns=no — es gibt aber ohnehin keine Prefixe zu werben.
  • 🔴🔴 Der eigentliche Kostenpunkt ist nicht IPv6, sondern die Firewall: /ipv6 firewall filter ist komplett leer. Heute harmlos, weil ohne Adressen nichts geroutet wird. In der Sekunde, in der Adressen vergeben werden, steht IOT→MAIN offen, GUEST→INFRA offen — an TP5 vorbei. Das gesamte TP5-Regelwerk muss vorher in v6 nachgebaut sein, sonst reißt dieser TP genau die Segmentierung auf, die TP5 am 2026-08-18 geschlossen hat. Das ist die Arbeit, nicht die Adressvergabe.
  • 🔎 Warum Matter das braucht: Commissioning läuft in zwei Etappen. Etappe 1 (BLE zum Gerät) ist netzunabhängig. In Etappe 2 muss der Commissioner — das iPhone — das Gerät auf dem operativen Thread-Netz wiederfinden (DNS-SD _matter._tcp, dann CASE). Dafür braucht das Telefon eine IPv6-Route in den OMR-Prefix hinein. iPhone hängt auf VLAN10, OTBR auf VLAN20 → Etappe 2 bricht ab. Die ~30 bereits laufenden Matter-Knoten widersprechen dem nicht: HA und OTBR sitzen auf demselben Host (wpan0 ist lokal), im Betrieb quert nichts die VLAN-Grenze. Nur das Anlernen tut es.
  • ⚠️ Zwei Fallen, die den naiven Weg verbauen:
    • (a) Der OMR-Prefix ist generiert und wandert. Am 2026-08-22 um 17:33 (OTBR-Neustart) sind alle Knoten von fd92:505a:c64e:1::/64 auf Mesh-Local fd66:986:170:bb1a::/64 gefallen — und Mesh-Local ist per Definition nicht off-mesh routbar. Eine statische Route auf den OMR-Prefix ist also nur so stabil wie der nächste Neustart. Ihn im Thread-Dataset festzunageln formiert das Netz neu — mit 30+ produktiven Knoten daran kein Nebenbei-Change.
    • (b) RouterOS lernt fremde RAs nicht. Der OTBR wirbt den OMR-Prefix per RA mit Route-Information-Option auf VLAN20. Der Core müsste die annehmen — als forwardender Router für dieses Interface tut er das nicht sauber.
  • 📡 Dritter Baustein: DNS-SD über die VLAN-Grenze. mdns-repeat-ifaces am Core (vlan10/20/30/40/90) ist IPv4. Für Matter-Discovery bräuchte es zusätzlich einen v6-fähigen Reflector — der mcast-relay-LXC (108) kann das prinzipiell, aber als zweite Instanz mit Beinen in VLAN10+VLAN20 (SSDP-Instanz ist VLAN10+VLAN40, s. Gotcha in CLAUDE.md).
  • ✅ Der Workaround, der das umgeht statt löst: iPhone zum Anlernen auf VLAN20 — dann sind Telefon und Border Router auf demselben L2-Link, der OTBR wirbt dort On-Link-Prefix und Route, und iOS wertet beides aus. cfg-infra / SSID dirtyvader-infra (dp-infra, VLAN20, sec-infra mit PSK) existiert am Manager bereits, ist nur in keiner provisioning-Zeile als Slave hinterlegt. Runbook: docs/matter-pairing-infra-ssid-runbook.md.
  • ✅ Der Weg, der das Problem dauerhaft erledigt: Matter-Server-Option ble_proxy: true (ab HA 2026.06, hier 2026.8.3). Dann kommissioniert HA selbst über seinen Bluetooth-Stack; das Telefon ist raus und die VLAN-Grenze wird nie berührt. Braucht eine connectable BLE-Quelle — Shelly-Proxies sind reine Advertisement-Scanner (kein GATT), ein ESP32 als ESPHome-BT-Proxy (active: true) reicht. Der ESPHome Device Builder ist bereits installiert. Damit wird TP12.1 für Matter optional.
  • Abh.: hängt an nichts. Blockiert nichts — außer künftige Thread-Geräte, solange weder ble_proxy noch die -infra-SSID steht. Reihenfolge innerhalb des TP ist zwingend: v6-Firewall nachbauen → dann erst Adressen vergeben. Gehört unter TP12 (Gesamtkonzept 3 Standorte), ist aber allein für BF ausführbar.

TP13 — „Botschafts-WLANs" ▸ 🔜 AUSFÜHRBAR (Runbook 2026-08-19) — docs/tp13-botschafts-wlan-runbook.md · fun/low-prio

📌 Am 2026-08-19 überarbeitet: der Technikteil beschrieb noch Standalone-APs (SSIDs entstehen seit TP28 am Manager, also ein Deploy statt zwei), und der Entwurf „nur ssid=, kein Security" war ein Denkfehler — eine SSID ohne Security ist ein offenes WLAN, also genau der Fall, den der Rechtsvorbehalt ausschließen soll. Richtig: PSK, den niemand kennt. Dazu die Datapath-Falle (Slave-VAP ohne vlan-id landet ungetaggt in der Bridge — dieselbe Mechanik wie der 14-h-Ausfall).

  • Idee: eine/mehrere Broadcast-only-SSIDs als „schwarzes Brett" an die Funk-Nachbarschaft — der SSID-Name ist die Botschaft (z.B. Kanal-11-bitte-freihalten, WLAN-Tuning-siehe-Portal). Optional ein offenes WLAN mit Captive-Portal auf eine statische Info-HTML (Kanalabsprache, Auto-Kanalwechsel-Bitte). Leitgedanke: wer die SSID sieht, kann auch stören → die Infoseite erreicht genau die Zielgruppe.
  • Technik (jetzt einfach, da WLAN standalone seit TP3-Rebuild): reine Namens-SSIDs = zusätzliche /interface/wifi Slave-VAPs auf wifi1/wifi2 mit eigener configuration (nur ssid=, kein Security/Datapath) ⚠️ Überholt 2026-08-19, in zwei Punkten:
    • (a) WLAN ist seit TP28 nicht mehr standalone. SSIDs entstehen am Manager crs326-core (9 configuration-, 6 datapath-Objekte, 4 provisioning-Zeilen — live gelesen 08-19). Das ist für TP13 sogar einfacher: ein Deploy statt zwei.
    • (b) 🔴 „kein Security" ist ein offenes WLAN — genau das, was der Rechtsvorbehalt unten ausschließen will. Richtig ist eine SSID mit WPA2/WPA3-PSK, den niemand kennt: sie beacont den Namen, ist aber nicht beitretbar.
    • Captive-Portal-Variante (RouterOS-Hotspot, isoliertes VLAN, walled-garden) bleibt als Option bestehen — s. Rechts-Gate unten.
  • Umsetzungs-Skizze: … Beacon-SSIDs in routeros/.secrets/ap{1,2}-prov-base.rsc ergänzen … → diese Dateien sind für den WLAN-Teil Historie (die CAPs sind auf die cap-Zeile eingedampft). Aktuelle Umsetzung: docs/tp13-botschafts-wlan-runbook.md (2026-08-19).
    • 🔴🔴 Die Falle, die dort ausführlich steht: ein Slave-VAP ohne explizite vlan-id am Manager landet ungetaggt in bridge — weil der CAP vom Manager-Datapath nur die vlan-id übernimmt. Das ist dasselbe Leck wie TP3.1 (cfg-main-2g ohne datapath) und dieselbe Mechanik wie der 14-h-Ausfall vom 08-18. Die Botschafts-SSID braucht deshalb einen Datapath mit totem vlan-id — PSK und toter Datapath, nicht eines von beidem.
    • ✅ VAP-Budget geprüft (live 08-19), unkritisch: max-interfaces=16 je Radio, belegt sind 4 (5 GHz) bzw. 5 (2,4 GHz). Die Grenze ist Airtime, nicht die Zahl — jeder VAP beacont eigenständig. Empfehlung: genau eine SSID, auf 2,4 GHz (mehr Reichweite = mehr Nachbarn, und 5 GHz bleibt für Nutzverkehr frei).
  • ⚠️ Recht vor Scharfschalten prüfen (User privat, AT): offenes WLAN + Portal berührt Impressumspflicht/Störerhaftung — reine Namens-SSIDs ohne offenen Zugang sind unkritisch (kein Fremd-Datenverkehr); im Zweifel nur die Beacon-Variante.
  • Abh.: TP3-Rebuild ✅ (Standalone-APs — SSID-Änderung = 1 Deploy/GC-Job) → TP28 ✅ (CAPsMAN auf dem Core) + TP22 ✅ (GC-Deploy-Pipeline). SSID-Änderung = ein Deploy auf den Core; Direkt-Edit am Gerät ist durch den Governance-Entscheid ausgeschlossen.

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

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

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

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

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

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

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

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

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

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

TP14.4 — Session D: CF-Access-Profil für Claude ▸ 🔜 AUSFÜHRBAR (Runbook 2026-08-19) — docs/tp14.4-cf-access-token-runbook.md · nicht cutoff-kritisch

📌 Ist-Stand geprüft: in mcp/cloudflare/.env stehen weder CF_ACCESS_CLIENT_ID noch _SECRET — es ist also nichts halb erledigt. ⚠️ Das Secret existiert nur im Moment der Rotation. Abnahme braucht beide Fälle (mit Headern antwortet / ohne Header 403), sonst ginge ein bypass-Hostname wie vw fälschlich als Erfolg durch. Kein CF-Write erfolgt.

  • CF-Token „claude code mcp" hat jetzt Access: Service Tokens: Write+Read → claude-mcp rotieren (POST …/access/service_tokens/{id}/rotate) → CF_ACCESS_CLIENT_ID/_SECRET in mcp/cloudflare/.env (chmod600). Memory ref-cf-access-claude-profile.
  • 📋 Runbook: docs/tp14.4-cf-access-token-runbook.md (2026-08-19). Ist-Stand verifiziert: mcp/cloudflare/.env enthält bisher weder CF_ACCESS_CLIENT_ID noch _SECRET — es ist nichts halb erledigt. 🔴 Das Secret wird nur bei der Rotation ausgegeben, wer es nicht sofort schreibt, muss erneut rotieren. Abnahme = ein Aufruf mit den Headern (antwortet) und einer ohne (403) — der Erfolgsfall allein beweist nichts, der Hostname könnte auf bypass stehen.

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

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

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

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

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

TP16 — CF-Tunnel: Round-Robin killen + reziprokes HA ▸ ✅ KERN UMGESETZT (2026-07-10) — ⚠️ HA aktuell degradiert (Live-Befund 2026-08-16) — docs/cf-tunnel-mapping.md §10, #1033

  • 🟠 BEFUND 2026-08-16 (beim Doku-Aufräumen live über die CF-API erhoben, kein Eingriff): das reziproke HA läuft nur noch auf einem von drei Tunneln.
    • bf-mm = 2 Connectoren (je 4 conns) ✅ · bf-nas = 1 Connector (3 conns) 🟠 · bf-pve = 1 Connector (4 conns) 🟠.
    • Bedeutung: die Dienste hinter bf-nas (gitea, grafana, pve-mcp, vw, ds-bf, nautobot) und bf-pve (pve, ccu, iobroker, z2m25, portainer, hass, ha-mcp) hängen jeweils an einem einzigen cloudflared-Prozess. Fällt dessen Host aus, sind sie weg — genau der Zustand, den TP16 beseitigt hat. Kein 530/Round-Robin-Problem (das bleibt strukturell gelöst), sondern Redundanzverlust.
    • Bekanntes Muster: identisch zum Incident vom 2026-07-16 (macmini-Connectoren flappten unter QUIC → Fix TUNNEL_TRANSPORT_PROTOCOL=http2). Diesmal sind die macmini-Connectoren oben (bf-mm hat 2), es fehlt je ein Gegenstück auf NAS/pve. Nächster Schritt (eigene Session): auf NAS und pve nachsehen, welcher der beiden cloudflared-Container nicht läuft, und ob es der http2-Konventionsfall ist.
  • 🧹 Nebenbefund beim selben Sweep — Alt-Tunnel mit VLAN1-Origins (alle down, 0 Connectoren, daher aktuell harmlos): openclaw (→ 10.40.41.147/.148/.139), coolify (→ 10.40.41.133), johnny2, TeilzeitCharlie. ⚠️ openclaw beansprucht n8n.emsenhuber.net, das produktiv auf bf-mm läuft — käme der Tunnel je wieder hoch, entstünde exakt das alte Round-Robin-Muster auf diesem Hostname. Empfehlung: die vier toten Tunnel löschen (user-gated, Löschen ist nicht umkehrbar) — gehört fachlich zu TP19 (VLAN1-Retirement: .41.133 war eines der „unidentifizierten VLAN1-Geräte" aus TP19 B1.2 — hiermit als coolify-Origin identifiziert).

✅ Umgesetzt (2026-07-10, eigene Session): reziprokes 3-Tunnel-HA live (bf-nas 70d6a4cf / bf-pve f7fdf3a6 / bf-mm 7a322261), 6 Connectoren (2/Host, je Fremdhost), 19 FQDNs Loop-Smoke 0×530, HA per Host-Ausfall-Sim bewiesen. Round-Robin-#1033 strukturell weg. As-Built = docs/cf-tunnel-mapping.md §10. Schritt 5 Access-Strang: vw bypass→geschützt via WARP — 🔴 GESTRICHEN (User-Constraint 2026-08-14, s. TP16.2). Verbleibend offen: hass/ha-mcp 400/403; gateway-dw (WG→404) klären.

  • Symptom (User): FQDNs über CF werden sporadisch / „sehr unsicher" aufgelöst = #1033-Muster zurück („jeden 3. Schuss eine Niete"). User hat alle 3 cloudflared-Images + Container aktualisiert/neu gestartet.

  • ✅ Root-Cause endgültig (User-Diagnose bestätigt): macvlan/ipvlan-Host-Isolation. Ein cloudflared-Connector ist blind für macvlan/ipvlan-Container auf seinem EIGENEN Host (Linux-Kernel-Regel), erreicht aber Fremdhost-L2 + co-lokierte Bridge-Container. Mehrere Connectoren auf EINEM Tunnel = geteilter Ingress + CF-Round-Robin → jede Origin, die auf dem Host eines Connectors liegt, fällt bei ~1/N aus. Strukturell, nicht durch „Origins fixen" heilbar. Bsp: omada .20.175/pve-mcp .20.173/gitea .20.157/grafana .20.109 = NAS-macvlan → vom NAS-Connector NICHT erreichbar.

  • Invariante: innerhalb EINES Tunnels ist kein Route→Connector-Pinning möglich (alle Connectoren teilen Ingress, CF round-robined). „Ausgewählte Routen für ausgewählte Connectoren" = getrennte Tunnel.

  • Ziel-Design (HA): reziproke Per-Host-Tunnel — jeder Host-Dienst wird von den Connectoren der ANDEREN Hosts bedient (nie vom eigenen):

    Tunnel (= Dienste eines Hosts) Connectoren auf HA
    pve-Dienste NAS + macmini 1 abdrehbar
    NAS-Dienste (omada/pve-mcp/gitea/grafana/…) pve + macmini 1 abdrehbar
    macmini-Dienste (.20.167) pve + NAS 1 abdrehbar

    3 Tunnel, jeder Host fährt 2 cloudflared → Host-Ausfall ⇒ jeder Tunnel ≥1 Connector = voll HA. Voraussetzung: jede Origin L2-erreichbar.

  • Ist-Inventory (live 2026-07-10, s. cf-tunnel-mapping.md §9): Ingress v119, 24 Hostnames, 2 live Connectoren. DOWN/stale: pbs .20.135, nginx-bf .41.123 (noch VLAN1!), hermes+hermes-geli .20.167. Zu klären (Host/Netz): vw .20.127/ccu .20.232/iobroker .20.107/z2m25 .20.106 — ggf. vw/ccu auf VLAN20-macvlan heben. gateway-dw (192.168.178.1) = WG-Sonderfall.

  • CF-Access-Strang (verzahnt, s. TP11 Track-B): hass-400 in der HA-iPhone-App = CF-Access davor (Companion-App kann Access-Login nicht) → Fix ohne Passthrough: iPhone in WARP enrollen (Device-Identity erfüllt Access; „Warp Login App" existiert bereits allow:christoph). ⚠️ vw = aktuell bypass (öffentlich!) → von bypass auf geschützt heben. 🔴 Überholt (2026-08-14): vw BLEIBT bypass — harter User-Constraint, Härtung stattdessen über TP16.2. „Passthrough-Hosts sind die kritischen" (User).

  • Umsetzungs-Reihenfolge (für die eigene Session): (1) Reachability-Matrix je Host (pve/NAS/macmini → jede Origin, read-only) BEVOR gruppiert wird; (2) Interim-Sofortfix: bf-Tunnel auf pve-only (pve erreicht alles außer gateway-dw) → 0×1033, reversibel; (3) reziprokes 3-Tunnel-HA bauen (CNAMEs re-pointen, 6 Connectoren, Token je Tunnel; NAS/macmini-Alt-Token entfernen); (4) tote Origins bereinigen; (5) Access-Strang (WARP + vw); (6) Loop-Gegentest.

  • Abh.: TP14.1/14.2 ✅. Verwandt: TP11 (CF-Tunnel-Track-B/C + Access), #1033. SSoT: docs/cf-tunnel-mapping.md. Memory project_cf_tunnel_1033.

  • ✅ Incident 2026-07-16 (User unterwegs, WARP „spinnt", keine BF-IPs mehr) ▸ Public-FQDN-Teil gefixt: macmini-Connectoren (cloudflared-bfnas/-bfpve) flappten live unter QUIC/UDP (Docker-Desktop-Bridge-NAT) → bf-nas/bf-pve von 2 auf 1 Connector degradiert (HA faktisch weg, nicht down). Fix: beide auf TUNNEL_TRANSPORT_PROTOCOL=http2 umgestellt (rollend, 0 Downtime), wieder voll HA. Konvention: macmini-Connectoren künftig immer http2. ICMP-Befund (getrennt, strukturell): TCP über die WARP-Route ging überall, ICMP nicht (fm 36 %/bf 100 %/dw 100 % Loss) — cloudflared ist QUIC-Proxy, kein WireGuard. Kurz als „bf-pve-Route wegen USV" notiert (Commit 1c1a676) → REVIDIERT (Begründung invertiert: bf-pve-Connectoren laufen auf NAS+macmini, nicht pve). Details: docs/cf-tunnel-mapping.md §13. → Richtungswechsel für Private Access = TP16.1.

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

  • ✅ Kernergebnis: ICMP ist repariert. Privater Zugang unterwegs läuft über natives WireGuard auf crs326-core statt cloudflared-QUIC — 0 % Ping-Loss auf alle BF-VLANs, vorher 100 %. Genau das Symptom aus dem Incident 2026-07-16, das dieses TP ausgelöst hat. Öffentliche FQDNs unberührt auf dem 3-Tunnel-HA (TP16). Compliance 68/68.
  • ✅ §4.0 erledigt 2026-08-17: Mesh-Node angelegt. bf-core-mesh = d1da7c8a-2736-4cbb-8af1-32db5393fb9c, Register-Token in mcp/cloudflare/.env (CF_MESH_REGISTER_TOKEN, gitignored). ➡️ Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.
  • 🔑 Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe). Der alte User-Token hatte zwei fehlende Permissions (§4.0 und §4.1 — devices/settings war schon lesend 403). Ersetzt durch einen account-owned Token (9984078812bc…, ⏰ gültig bis 2027-01-09). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an /accounts/{id}/tokens/verify — am alten /user/tokens/verify melden sie 1000 "Invalid API Token", während alle echten Endpoints 200 liefern; (2) Dashboard-Fehler "Account tag in access policy must match tag in request uri" = eine Zone-Permission (DNS) steckt in der Policy-Zeile mit Account-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in docs/tp16.1-…md §4.0. Merke: Dashboard sagt „Edit", API sagt „Write".
  • ✅ §4.1–§4.4 durchgezogen 2026-08-17 (User-Freigabe „jetzt durchziehen"). Prereqs: use_zt_virtual_ip=true, Device-Profil „Cloudflare Mesh" (4d3c0f57…), Split-Tunnel auf beiden Profilen korrigiert (100.64.0.0/10 → /11 + 100.112.0.0/12). Gerät: cf-mesh auf crs326-core, Mesh-IP 100.96.0.6/32, Peer 162.159.193.2:2408, Handshake steht, Route 100.96.0.0/12 → cf-mesh aktiv; Backup pre-tp16.1-mesh-20260817.backup vorher (Auflage #4 ✅). CF meldet Node healthy. §4.4: Weg A hat funktioniert — der befürchtete vnet-Gotcha trat NICHT ein, 10.40.0.0/16 zeigt jetzt auf bf-core-mesh statt bf-pve (Rollback = 1 PATCH zurück, bf-pve unangetastet).
  • ⚠️ Zwei Werkzeug-Fallen, real getroffen (gehören in jede künftige WG-Arbeit): (1) MikroMCPs manage_wireguard_interface kann grundsätzlich keinen privaten Schlüssel annehmen — RouterOS erzeugt ihn selbst; bei CF Mesh kommt er aber von CF → Key per SSH mit Interpolation aus der gitignoreten .env setzen, nicht über das Tool. (2) manage_wireguard_peer schreibt endpoint: "IP:Port" komplett in endpoint-address und lässt endpoint-port=0 → kein Handshake, sieht aus wie ein Firewall-Problem, ist ein Parameter-Bug. Nach jedem Peer-Add print detail gegenlesen. Auch persistent-keepalive fehlt dem Tool.
  • ✅ §4.5 ABNAHME BESTANDEN 2026-08-17 (MacBook am Hotspot, WARP connected): Ping 0 % Loss auf 10.40.10.1 / 10.40.20.105 / 10.40.99.12 / 10.40.20.10 / 10.40.99.1 (47–63 ms) — vorher 100 %. TCP mit echtem Datenfluss (pve 8006 → 200, Nautobot → 302, ssh macmini → ok). FQDN-Smoke gitea/hass/pve/nautobot/vw → 200/200/302/302/200 ⇒ cloudflared-Public-Teil unberührt.
  • ✅ Auflage Ausnahme #4 erfüllt: Ist-Stand als intended config zurück nach Nautobot → zuerst 65/68 (drei extra-Drifts = die Mesh-Zeilen), Zeilen byte-genau aus dem Backup ins Jinja-Template, Push nach bf-golden-config, Repo-Sync → 68/68, keine Drifts. Privkey bleibt draußen (<<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.

TP16.2 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) ▸ 📋 KONZEPT (2026-08-14), Umsetzung offen

  • 🔴 HARTER CONSTRAINT (User 2026-08-14): vw wird NICHT WARP-/Access-geschützt. Gilt dauerhaft, nicht „vorerst". Begründung (technisch belastbar, nicht Bequemlichkeit): die Bitwarden-Clients (Browser-Extension, Mobile, Desktop) können den interaktiven Access-Login nicht durchführen — dasselbe Muster, das die HA-Companion-App auf 400 laufen lässt. Ein Passwortmanager hinter einem Client, den man im Notfall (fremdes Gerät, Neuinstallation, Handyverlust) nicht installieren kann, ist ein Aussperr-Risiko gegen den eigenen Schlüsselbund — und damit gefährlicher als das, wovor die Maßnahme schützen soll. Die frühere TP16-Schritt-5-Absicht „vw von bypass auf geschützt heben" ist damit gestrichen.
  • Ziel stattdessen: Schutz erhöhen, ohne Clients zu brechen — gilt für alle bypass-Dienste (vw, hass, gitea).
  • ✅ Schicht 0 — bereits vorhanden, nicht unterschätzen: keine offenen Ports, alles ausschließlich über CF-Tunnel (3-Tunnel-HA, TP16), TLS terminiert an der Edge, DDoS-Schutz. Der Origin ist nicht direkt erreichbar. Das ist bereits mehr als bei den meisten Selfhostern.
  • ✅ Schicht 1 — chirurgisch: Access nur auf die Pfade, die kein Client anfasst. CF Access wertet die spezifischere Application zuerst → man kann eine zweite Access-App auf vw.emsenhuber.net/admin* legen (echte Policy) und den Host im Übrigen auf bypass lassen. Das Admin-Panel wird ausschließlich per Browser bedient — dort funktioniert der Access-Login einwandfrei, während /api, /identity und /notifications (die Client-Pfade) unberührt bleiben. Kein Client bricht, das wertvollste Ziel ist weg.
    • Alternativ noch härter: Admin-Panel ganz aus (ADMIN_TOKEN leer lassen) und nur bei Bedarf temporär aktivieren.
    • Analog für gitea: /admin* + Registrierung path-scoped schützen (Git-über-HTTPS mit Token bleibt offen).
  • ✅ Schicht 2 — App-eigene Bremsen (kostenlos, sofort): Vaultwarden hat Rate-Limits per Default an (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10, dazu ADMIN_RATELIMIT_*) → Werte prüfen/verschärfen; SIGNUPS_ALLOWED=false, INVITATIONS_ALLOWED=false, SHOW_PASSWORD_HINT=false, 2FA erzwingen. HA hat ip_ban_enabled + login_attempts_threshold. Gitea: Registrierung aus.
  • ✅ Schicht 3 — Edge-Regeln (CF), wirkt für alle Dienste gemeinsam: WAF-Custom-Rules + Rate Limiting gezielt auf die Login-Endpunkte (/identity/connect/token bei vw, /auth/login bei HA, /user/login bei Gitea); optional Geo-/ASN-Filter. ⚠️ Free-Plan-Kontingente vorher im Dashboard prüfen (nicht belegt).
  • ✅ Schicht 4 — ein Mechanismus für alle: CrowdSec (oder fail2ban) mit Cloudflare-Bouncer. Bans landen dann an der Edge statt erst am Origin. Vaultwarden hat ein offizielles Fail2Ban-Setup-Wiki (Log-Muster für Login und Admin-Panel).
  • 🔴 Voraussetzung für Schicht 2–4, und der eigentliche Knackpunkt: die echte Client-IP muss am Origin ankommen. Hinter dem CF-Tunnel sieht die Anwendung sonst nur die IP des cloudflared-Containers → jede IP-Sperre bannt den Tunnel statt den Angreifer = Selbstaussperrung aller Nutzer, und Rate-Limits greifen ins Leere. Genau dieser Defekt ist bereits offen dokumentiert: HAs trusted_proxies fehlen .20.10/.20.167 (TP16, hass-400-Strang). ➡️ X-Forwarded-For/CF-Connecting-IP sauber durchreichen ist Schritt 1 dieses TPs, alles andere baut darauf auf.
  • Reihenfolge: (1) Client-IP-Weitergabe fixen (inkl. HA trusted_proxies) → (2) /admin-Pfade path-scoped schützen → (3) App-Limits + 2FA → (4) Edge-Rules → (5) CrowdSec/fail2ban mit CF-Bouncer.
  • Abh.: TP16 ✅ (Tunnel-HA). Verwandt: TP20.1 (vw wandert auf Platform-Postgres — DB-HA ≠ Access, getrennt halten), TP23 (dessen WARP-Managed-Network-Idee ist durch diesen Constraint entlastet: vw verlangt keine Device-Identity, also kollidiert „WARP zuhause aus" nicht mehr mit dem Access-Strang).

TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + shelly-mcp ▸ ✅ KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30 — Geräte-PW (TP17.2) weiterhin bewusst zurückgestellt — mcp/shelly/, Runbook docs/tp17-shelly-go-runbook.md

  • ✅ Abschluss 2026-08-13 (User-Bestätigt): auch die zuletzt offenen saisonalen Shellys (s1/s2/s3/s12) sind inzwischen umgeklemmt — alle Shellys auf dirtyvader-iot/VLAN30, kein VLAN1-Shelly-Rest mehr. Migration-Teil von TP17 damit vollständig abgeschlossen.
  • ✅ GO durchgezogen 2026-07-13 (16 Shellys VLAN1→VLAN30, 0 Fehler): Pilot s26 + Welle A (s4–s9) + Welle B (s10/11/13/15/17/19/20/21/25) → alle auf dirtyvader-iot/10.40.30.<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).

TP17.2 — einheitliches Geräte-Passwort (Shelly SetAuth) ▸ ⏸️ ZURÜCKGESTELLT (Kosmetik, User 2026-07-13)

  • Was: je Gerät Shelly.SetAuth (User admin, realm-individueller ha1 = sha256("admin::"), einheitliches Klartext-PW) via mcp/shelly/migrate.py auth --go. SHELLY_DEVICE_PW liegt in mcp/shelly/.env (gesetzt, len 13). Runner + Digest-Auth-Fallback in _rpc fertig.
  • Warum zurückgestellt: rein kosmetisch/Härtung; bricht die HA-Shelly-Integration, bis pro Config-Entry (22×) das Credential neu eingetragen ist → disruptiv, kein Mehrwert im abgeschotteten IOT-VLAN. Erst nach dem Netz-Teardown (TP19) neu bewerten.
  • Ziel: ~26 Shellys (aktuell untagged VLAN1 10.40.41.2xx, weil auf alt-WLAN cudy, s. TP3.1/project_vlan1_dhcp_incident) auf SSID dirtyvader-iot (VLAN30, w2-iot, WPA2/WPA3-mixed, PSK 74c9L283$669) umklemmen → DHCP dhcp-v30/pool-v30 (live). Kein netzseitiger Move möglich (anderes WLAN) → per-Gerät WiFi-Reconfig.
  • User-Entscheide (2026-07-11): (1) volles shelly-mcp bauen (reusable, nicht nur Skript); (2) Pilot 2–3, dann Rest; (3) DHCP-Reservierungen je MAC in VLAN30 (stabile IPs für HA/ioBroker/DNS).
  • ✅ Recon durch (mcp/shelly/discover.py, read-only /shelly-Sweep): 16 live, alle Gen3/Gen4 (uniforme RPC-API WiFi.SetConfig, auth=off — kein Gen1-Sonderfall). 10 „offline" = weihnachten_* (s1/s2/s3, saisonal ausgesteckt Juli) + s12/s14/s16/s18/s22/s23 (aus/DHCP-drift). ⚠️ Inventar-Fix: s6 (kueche_kuehlschrank, .213) MAC falsch erfasst (0c:72:74:36:90:9e) → real d0:cf:13:d8:18:fc. s24 hat gar keine MAC.
  • Mechanismus je Gerät: Gen2+ RPC WiFi.SetConfig {sta:{ssid,pass,enable}} → Reboot → landet .30.x. ⚠️ Risiken: falsches PSK → AP-Mode (physischer Reset); Gerät kurz offline; HA/ioBroker-Refs brechen bis re-pointed; WPA3-mixed ggf. zickig auf alter FW; ap1 2,4-GHz-Master wifi2 running:false → Coverage v.a. via ap2 prüfen.
  • ✅ Live-Abgleich 2026-07-11 (go-ready): (a) shelly-mcp (server.py) fertig — discover/get/set_sta/verify/reservation_plan, PSK in .env gesetzt (noch nicht als MCP registriert, optional). (b) Alle VLAN30-DHCP-Reservierungen liegen (.30.2xx, Oktett erhalten, außerhalb Pool .100–.200); s6-Reservierung .30.213 MAC gefixt (falsch 0C:72…9E → real D0:CF:13:D8:18:FC). (c) IOT-SSID live verifiziert: ap1 w2-iot running:true, 8 Clients −26…−68 dBm (ap1-wifi2-„running:false" ist kosmetisch). (d) Migration bereits ~5/26 durch: s14/16/18/22/23 seit ~2,5 d stabil auf reservierten .30.221/.223/.225/.202/.203 (+ .30.204 unbekannt, evtl. s24) → Pilot faktisch bestanden.
  • ✅ HA-Cross-Check 2026-07-11 (alle IPv4-Geräte in HA): Shellys deckungsgleich; .30.204 = Shelly „Bad/Spiegelschrank" (Plus RGBW PM, 14:33:5c:d8:2d:7c) = vmtl. s24 → 6 statt 5 schon migriert. Netzweite Aufstellung + VLAN1-Leerstand-Analyse im Runbook: nach TP17 + pve-Host→VLAN20 + Christophs Endgeräte→VLAN10 ist VLAN1 endgeräte-frei; es bleiben nur Switch-/AP-Mgmt (sw1/sw2/ap2 → VLAN99-Cutover, eigener Schritt) + 1 unidentifiziertes Gerät .41.246 (morgen klären). Nicht-Shelly-VLAN30-Mover (User-bestätigt): ✅ SwitchBot floor-lamp .41.184 + ✅ Twinkly-Girlande .30.228 (beide 2026-07-16 übersiedelt, s. TP19) + Creality Ender (fehlt noch, off). Withings schon übersiedelt; Tasmota existiert nicht mehr (kein aktives Gerät → stale HA-Eintrag). EOS-Smartwatches/eBook-Reader später + von Christoph selbst (eBook eher VLAN50).
  • Scope-Erweiterung (User 2026-07-11): je Gerät 3 Änderungen — WLAN→dirtyvader-iot + IP (.41.2xx→.30.2xx) + einheitliches Geräte-Passwort (Shelly.SetAuth, User admin, realm-individueller ha1 → Tool set_auth in server.py nachrüsten). PW entschieden: eigenes Shelly-Admin-PW (getrennt vom WLAN-PSK) → SHELLY_DEVICE_PW in .env (Christoph liefert Wert). HA-Entities unverändert (keyt per MAC). HA-Entities bleiben unverändert (HA keyt per MAC). Projektion TP17.1 (experimentell, nachrangig): Gen4 (~11 St.) auf Thread/Matter flashen (Firmware-Verfügbarkeit + Thread-Border-Router offen) — Konflikt mit „Entities unverändert" (Matter = neue Entities) → erst WiFi-Migration stabilisieren.
  • ✅ Reservierungs-/Pool-Konvention (User 2026-07-11): alle Shellys bekommen statische IP (10.40.30.<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.

TP18 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier) ▸ 🔜 KONZEPT (2026-07-12)

  • Auslöser: 9-TB-Syno-Backup-Volume zu 2/3 voll; reine Daten ~3 TB, Tendenz steigend, „schlagartiger" Bedarf nicht ausgeschlossen. „Wenn skalieren, dann jetzt Syno-Nachfolger mitdenken."
  • Kriterien (User-priorisiert): (1) keine Fremd-Abhängigkeit / kein Cloud-Lock-in (Cloud-Opex abschreckend); (2) Multi-Site BF/FM/DW; (3) HA (Standort-Verlust überlebbar); (4) billig/teuer trennen — Variante „kalt = Objektspeicher, heiß = lokal" genügt (kein Hot/Cold innerhalb des Stores nötig); (5) horizontal + vertikal skalierbar; (6) S3-API als Branchenstandard, den jedes Backup-Tool spricht.
  • Entscheid Engine = Garage (Deuxfleurs, Rust): einziger Kandidat, der geo-verteilt über offenes Internet/WAN latenztolerant ist (CRDT-Metadaten, eventually consistent) → passt aufs 3-Keller-Modell BF/FM/DW. Ceph verworfen (will Sub-ms-LAN, Single-Cluster-über-WAN = Anti-Pattern; zu schwer, ~1 GB RAM/TB). MinIO (AGPL + Console/Feature-Abbau 2025) und SeaweedFS (kann Hot/Cold + Erasure-Coding, aber komplexer, Multi-Site async) = Fallback, falls später feingranulares Tiering / EC-Effizienz bei zweistelligen TB dominant wird.
  • Entscheid Disk-Layout = JBOD, KEIN RAID. Redundanz gehört in eine Schicht: Garage RF=3 über 3 Sites ist die Redundanz. RAID 1 darunter = Doppelt-Bezahlen (3 Sites × 2 Mirror = 6× roh für 1× nutzbar, absurd). RAID 0 verworfen (1 Platte tot = ganzer Node-Rebuild über WAN). JBOD = jede Platte eigenes Garage-Data-Dir (v1 Multi-HDD): Failure-Domain = 1 Platte, Rebuild nur deren Anteil, kein Kapazitätsverlust. Merksatz: distributed store = „just a bunch of disks", Redundanz ausschließlich über die Sites.
  • Was man dafür bezahlt: Plattentod triggert Re-Replikation über WAN (durch Upload-BW gedeckelt). Bei ~3 TB/Site tolerabel; wenn Bedarf „schlagartig" auf zweistellige TB springt → mehr/kleinere Platten (Blast-Radius) oder 4. Node, ggf. Wechsel auf SeaweedFS-EC.
  • Dimensionierung (Planungsanker 10 TB nutzbar; heute 3 TB unique / 6 TB Backup-used): RF=3 → 30 TB roh gesamt = 1 volle Kopie ~10 TB/Site (provisionieren ~16 TB roh/Site: 2 Platten = halber Blast-Radius + Headroom). Linear skalierend.
  • Kostenmodell (Annahmen: HDD neu CMR ~€18/TB · recert ~€10/TB · Strom €0,25/kWh · Lean-Node Mini-PC/pve 12 W + 2 HDD ~20–28 W):
    • Capex kumulativ (3 Sites): Platten €540 (recert) – €900 (neu) + Nodes €400 (BF=pve €0; FM/DW je Mini-PC €200) = **€600–1.300**. Vergleich: ein resilienter Syno-Nachfolger für 10 TB = €1.000–1.600 capex ohne Geo-HA → Garage-3-Node gleich teuer/billiger mit HA.
    • Opex kumulativ: ~€135–185/J Strom — aber getrennt gerechnet: FM/DW laufen über fremde Zähler, auf Christophs Zähler landet nur der BF-Node ≈ €50–60/J (Gegenleistung: er hostet deren Kopie, reziprok wie TP16).
    • JBOD-vs-RAID1-Ersparnis: Strom bloß €9–22/J (Christophs Zähler) → kein Entscheidungsgrund; der Hebel ist Capex ~€900 (nicht gekaufte Spiegelplatten) + architektonische Korrektheit. (Reicht nicht für ein BW-Upgrade €120–240/J, taugt aber als Seed für HW-Rückstellungen.)
  • Konzeptioneller Bruch (Syno-Nachfolger): Kein großes resilientes NAS mehr nötig — die kollektive 3-Site-Redundanz ist die Garantie. Syno-Rolle splittet: Hot/SMB/VM-Disks bleiben lokal (Syno oder ZFS auf pve, klein/schnell); Backup/Cold wandert in Garage über BF/FM/DW → das 9-TB-Volume wird aufgelöst statt ersetzt (aus 1 SPOF werden 3 geo-redundante Kopien). Backup-Tool (restic/Kopia) sprechen S3 nativ.
  • Impacts auf Bestand (Kurz): (a) Storage-Redundanz-Philosophie kippt — weg von SHR/RAID pro NAS, hin zu 3× JBOD; (b) Backup-Kette Hyper-Backup/SMB → S3-Bucket umstellen; (c) Container-Host-System (TP14) kriegt Garage als LXC/Container je Site, aber auf rohen Platten, nicht auf bestehenden SHR-Volumes; (d) Upload-BW wird Durchsatz-/Rebuild-Limit; (e) HW-Planung verzahnt mit TP15 (Node-HW, pve-Standort) und TP24 (Stromversorgung je Site), FM/DW brauchen je 1 Node.
  • Startpfad (inkrementell/reversibel): ① BF zuerst — Garage-Container auf pve, 2 HDDs JBOD, Backups lokal auf Bucket (sofort Nutzen, noch keine Redundanz). ② FM/DW nachziehen (Mini-PC + 2 Platten), RF→3, ab jetzt geo-redundant+HA. ③ Syno entlasten (Backup-Last abgeben).
  • Offen (Modell scharfstellen): realer Strompreis; ob FM/DW eigene Nodes stellen oder Christoph HW hinstellt; Backup-Tool-Wahl (restic vs Kopia); ob heißes Zeug künftig auf ZFS/pve statt Syno.
  • Abh.: TP10 (WG-Multi-Site = Transport, Upload-BW-Constraint), TP14 (Container-Hosts), TP15 (Node-HW) / TP24 (Stromversorgung), Muster von TP16 (reziprokes Per-Host-HA). Kein Blocker für laufende TPs. SSoT dieser Session.

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

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

TP20.1 — Vaultwarden → Platform-Postgres + Cross-Site-HA ▸ 🔜 KONZEPT (2026-07-14, ausgelagert aus TP20)

  • Ziel: vw (.20.127, heute MariaDB) auf den Precious-Tier holen und das eigentliche User-Ziel erfüllen: DB-Replikation über 2 Sites (Standort-Verlust überlebbar).
  • Phase 1 — Migration (single-site): vw MariaDB→Postgres (Export → Import in Tier-1-DB vaultwarden → DATABASE_URL umstellen → verifizieren). Danach ist vw precious + im pgBackRest/PITR-Schirm von TP20. (Alternative verworfen: vw auf MariaDB lassen + MariaDB-Repl — zugunsten EINER Replikations-/Backup-Mechanik im Precious-Tier.)
  • Phase 2 — Cross-Site-Standby: db-platform-standby-LXC am 2. Standort → pg_basebackup/Streaming-Replication über WG (TP10) → manuelles Promote-Runbook. Repliziert den ganzen Tier-1-Cluster (vw = HA-Ziel, Nautobot bekommt Gratis-DR-Kopie). Patroni + 3. Witness nur, falls später Auto-Failover statt manuellem Promote gewünscht.
  • Verwandt/Abgrenzung: vw ist auch in TP16 offen (CF-Access: bypass→WARP-geschützt) — DB-HA ≠ Access, getrennt behandeln. Reziprokes 2-Site-Muster analog TP16/TP18.
  • Abh.: TP20 (Tier-1 Postgres muss stehen), TP10 (WG-Transport für Phase-2), TP18 (Backup-Schirm). Kein Blocker für TP21.

TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ 🟢 Plattform + Datenmodell + Templates live

  • 🔴 BETRIEBSBEFUND 2026-08-18: der Stack hat sich zweimal in einer Nacht selbst umgebracht — Ursache war der Image-Healthcheck, nicht Celery. nautobot-worker und -scheduler erben aus dem Image einen HEALTHCHECK, der alle 10 s nautobot-server health_check startet — einen kompletten Django-Bootstrap (~250 MB). Unter Last dauert ein Lauf >2 min, die Aufrufe stapeln sich also unbegrenzt (neun gleichzeitig gemessen, aeltester 2:16 alt). Ergebnis: Speicher alle → proc_fork.c:189 Failed to fork(). errno=[12/Cannot allocate memory] im NAS-Syslog um 23:48:47 → eine Minute spaeter raeumt DSM den Stack ab (sauberes docker stop, daher ExitCode 0 bzw. 137 nach Grace — kein Kernel-OOM, OOMKilled=false). Dasselbe Muster schon um 19:35.
    • Behoben im Stack (docs/nas-vlan20/stacks/210-nautobot.yml, Portainer-Stack 203): healthcheck: disable: true auf worker+scheduler, --concurrency=2 --max-tasks-per-child=100, mem_limit je Container, Web-Healthcheck-Timeout 5s → 15s. Load fiel von 73,5 auf 5,8.
    • ⚠️ cpus: geht auf Synology NICHT — der Kernel hat keine CFS-Quota, der Stack-Deploy bricht mit NanoCPUs can not be set ab. Nur mem_limit verwenden.
    • 📌 Nichts haengt an dieser Health: restart=unless-stopped startet bei Exit neu, nicht bei unhealthy, und kein depends_on wartet auf den Worker. Der Web-Container behaelt seinen billigen curl-Check — das ist das Signal, das service_healthy nutzt.
    • 🎯 Strukturell bleibt es eng: Nautobot teilt sich die DS923+ (4 Kerne) mit einer VMM-VM, Active Backup und ~14 weiteren Containern. Der Stack bleibt vorerst dort (User-Entscheid 2026-08-18); ein groesserer Server ist gedanklich bereits vorgemerkt.
  • 🔴 Die Kehrseite des Re-Baselines, am eigenen Leib gelernt (2026-08-18): ingest-core-firewall.py erklärt den Ist-Stand zum Soll — es prüft nicht, ob der Ist-Stand sinnvoll ist. Beim Lauf nach TP5 E3/E4 ist damit die kaputte Fasttrack-Reihenfolge des Core als Absicht in den Config-Context gewandert (s. TP6). Der Warnhinweis im Skriptkopf („nur nach einer bewusst durchgeführten, verifizierten Änderung laufen lassen") ist also notwendig, aber nicht hinreichend: die Änderung war bewusst, die Umgebung, die mitgeschrieben wurde, hat aber niemand angesehen. Merkregel: nach dem ersten Re-Baseline einer Sektion deren Diff einmal ganz lesen, nicht nur die Zeilen, die man selbst angefasst hat.
    • ✅ Nachtrag 2026-08-19: der konkrete Fall ist aufgelöst — Christoph hat entschieden, die Fasttrack-Reihenfolge so zu lassen (Begründung in TP6). Der Eintrag in der SoT ist damit nachträglich korrekt. Die Merkregel bleibt trotzdem gültig: dass ein versehentlich mitgeschriebener Zustand sich hinterher als brauchbar herausstellt, ist Glück, kein Verfahren.
  • 🔧 Zwei echte Template-Fehler, beim Re-Baseline 2026-08-18 gefunden (beide gepusht):
    • /ip service set … **address=** → RouterOS 7.23/7.24 exportiert available-from=. Das erzeugte vier Dauer-Drifts (ap1/ap2/branch/core), bei denen das Geraet recht hatte und das Soll falsch war — und ein Push haette auf 7.24 auflaufen koennen.
    • hw-offload gehoert in die SKIP-Liste von ingest-core-firewall.py: die REST-API meldet es auf der defconf-fasttrack-Regel, /export schreibt es nicht → ewige Falsch-Drift auf genau einer Regel.
    • Ergebnis: 71/76 compliant; die verbleibenden 5 sind der TP28-Rest (wifi-* auf dem Core). (App 2026-07-22, Basisdaten + Git-Backend + L3-Core-Template 2026-08-05, ap1/ap2 As-built 2026-08-08); Device-Adoption/Compliance-Push ausgelagert → TP22 — Memory project_mikrotik_commit_nautobot_gc
  • Ziel: modellierter Control-Plane über den MikroTik-RouterOS-Kern: GUI-SoT + Config-Compliance/Drift + intended-config + Push. Ergänzt MikroMCP (interaktiv/Debug), ersetzt es nicht (deklarativ/DR/Compliance). Aus User-Recherche 2026-07-14: MikroTik in Golden Config erstklassig (mikrotik_routeros + _api, Nornir/NAPALM, backup+deploy).
  • Scope-Grenze (Fassung 2026-08-16, nachgezogen): RouterOS in-model = crs326-core (L3-Router) + crs326-branch (L2) + ap1/ap2 (reine APs) — alle 4 Geräte sind seit TP22 adoptiert, Golden Config deckt damit den ganzen Fabric inkl. WLAN. Es bleibt nichts außerhalb: der frühere Vorbehalt „außerhalb bleibt Omada-WLAN" ist gegenstandslos, seit der Omada-Stack gelöscht (2026-07-16) und CAPsMAN abgeschafft (2026-08-08) ist.

    Vorherige Fassung (überholt): „RouterOS in-model = Router (ap1/ap2, dauerhaft — kein RB5009) + beide CRS326-Switches (RouterOS-Boot) → Golden Config deckt den ganzen Kabel-Fabric. Außerhalb bleibt nur Omada-WLAN (eigener Controller, spielt aber laut TP15-Korrektur ohnehin keine Rolle mehr)."

  • 🔒 Wird zum alleinigen Config-Weg für Netzgeräte (Governance-Entscheid 2026-07-16, s. oben): ab hier keine ad-hoc MikroMCP-Writes/Switch-UI-Klicks mehr auf Dauer — Nautobot managed den Fleet.
  • ✅ Nautobot-Stack live (2026-07-16), auf NAS statt Mac mini (Ressourcen-Check hatte Mac mini vorgeschlagen, dann auf NAS umentschieden — mehr Headroom + Gitea-Co-Location, s. TP20-Log): Portainer-Stack nautobot (Id 203, ep14), 4 Container — nautobot (App/API, 10.40.20.30:8080), nautobot-worker (Celery), nautobot-scheduler (Celery Beat), nautobot-redis. Bild ghcr.io/nautobot/nautobot:3.1.7 (offizielles Image direkt, nicht das nautobot-docker-compose-Repo — das ist ein invoke/Poetry-getriebenes Dev-Tooling-Repo ohne eigenständige docker-compose.yml, für einen Portainer-String-Stack ungeeignet; bewusste Abweichung, dokumentiert statt stillschweigend). DB = Platform-Postgres (10.40.20.20, TP20). Compose-Datei docs/nas-vlan20/stacks/210-nautobot.yml (gitignored, wie alle NAS-Stack-Files).
    • 🐛 Gotcha beim ersten Deploy: alle 3 Nautobot-Container liefen initial gleichzeitig migrate (Standard-Entrypoint-Verhalten) → Race-Condition, IntegrityError auf pg_type. Fix: nautobot-Service kriegt einen healthcheck (curl .../health/), nautobot-worker/nautobot-scheduler starten erst bei depends_on: nautobot: condition: service_healthy — serialisiert die Migration sauber auf einen Container. DB dafür einmal DROP+CREATE (sauberer Neustart, kein Datenverlust — war eh leer).
    • ✅ End-to-End verifiziert: /api/status/ mit Token 200, Nautobot-Version 3.1.7, alle 4 Container laufen. Superuser admin angelegt, Creds im Stack-File (gitignored) + postgres/.secrets (DB-Seite).
    • ⚠️ Bekannt offen, nicht blockierend: kein HTTPS/Reverse-Proxy vor Nautobot (Django-Security-Warnings beim Start, erwartet für internen HTTP-only-Betrieb auf VLAN20 — analog anderen internen Diensten); Celery-Worker-Registrierung beim allerersten Start nicht sofort sichtbar (celery-workers-running:0 initial), unkritisch, braucht kurz zum Einpendeln.
  • PoC-Pfad (angepasst an die Montag-Deadline): Nautobot-Stack hoch ✅ → ap1 + ap2 in einem Rutsch adoptieren (ap2 bekommt dabei gleich seinen VLAN99-Mgmt-Umzug aus TP19.1, s.o. — nicht isoliert vorher) → sobald CRS326 verkabelt sind, direkt als Nautobot-verwaltete Geräte adoptieren statt erst manuell konfigurieren → Compliance/Drift + Templates/intended für VLAN-/Firewall-Standards danach.
  • ✅ Basisdaten-Ingest durchgeführt (2026-07-22) — Ist-Zustand, nicht Zielzustand (User-Entscheid: „wir gehen vom status quo aus"). Quelle = ap1 live via MikroMCP. Skript-Muster idempotent (get-or-create + Delta-PATCH).
    • Locations: neuer LocationType Bereich unter Site; BF → Technik UG (crs326-core, crs326-branch, ap2) und BF → Wohnzimmer Sideboard (ap1).
    • Devices: ap1 = Rolle Router (Ist am 2026-07-22 — ⚠️ seit der Adoption 2026-08-13 ist ap1 in Nautobot Wireless AP und crs326-core der Router, s. TP22), ap2 = neue Rolle Wireless AP + Status Offline, beide CRS326 Platform RouterOS nachgetragen (fehlte → hätte jeden GC-Job auf ihnen verhindert) + Werks-MAC/software-id in comments. Neu als Übergangs-Devices ohne Platform (nie GC-fähig, bewusst): sw1 (ALLNET), sw2 (ES208G), cudy1.
    • IPAM: VLAN-Group BF + 9 VLANs (1 Deprecated, 10/20/30/40/50/90/99/100 aktiv) + 8 Prefixes, jeweils an ihr VLAN gebunden. VLAN100/WAN bewusst ohne Prefix (öffentliches Liwest-/24, gehört uns nicht).
    • ap1-Interfaces (26): ether1-5, bridge, lo, wg-hub, vlan10-100, wifi1/wifi2, 7 VAPs — inkl. MACs, ether2 als Trunk (untagged VLAN1 + tagged 10-100), VAPs als Access-Ports auf ihrem VLAN. 9 Gateway-IPs an ihren Interfaces. Platzhalter-Interface mgmt auf ap1 gelöscht (existiert real nicht).
    • WLAN: logischer Controller bf-wlan (kein CAPsMAN — controller_device bewusst leer, die Control-Plane ist Nautobot GC) + Gruppe BF-WLAN-APs (ap1+ap2) + 6 WirelessNetworks mit VLAN-Bindung + 2 RadioProfiles (2,4 GHz ch1 / 5 GHz ch36, Domain AT).
    • 🐞 API-Gotcha: tagged_vlans wird vom REST-Serializer in Detail-/List-Views als null zurückgegeben, obwohl korrekt gespeichert — Verifikation nur über GraphQL verlässlich. Kostet sonst eine Stunde Fehlersuche an einem Nicht-Problem. Ebenso: Relationen kommen per Default ohne display (?depth=1 nötig), und VLANGroup/Prefix/VLAN brauchen passende content_types am LocationType, sonst 400.
    • ✅ CRS326-Core L3-Device-Modell + Ports angelegt (2026-08-05): crs326-core jetzt Rolle Router, alle VLAN-SVIs (vlan10/20/30/40/50/90/99/100) + Gateway-IPs .1 (außer vlan100) in Nautobot; Portbelegung via nautobot/ingest-crs326-ports.py aktualisiert. crs326-branch bleibt Switch mit Mgmt-IP .99.13 (Zielrenumber .12 im Cutover-Fenster).
    • ⚠️ Offen/nachzutragen: exaktes Modell von sw1 + cudy1 (aktuell … (Modell TBD)); Standort von sw1/sw2/cudy1 (hängen an BF statt an einem Bereich); ap2s primary_ip4 zeigt noch auf das tote 10.40.41.2 → ✅ erledigt (TP22 Schritt 5): 10.40.99.12.
  • ✅ Git-Backend an Gitea angebunden (2026-08-05): Repository bf-golden-config (jinja_templates/mikrotik_routeros.j2) sync-t in Nautobot; Generate Intended Config für crs326-core erfolgreich, intended config in GC-Tabelle vorhanden.
    • ✅ Lokale Cutover-Config materialisiert: routeros/tp15-core-cutover-intended.rsc enthält die aus Nautobot gerenderte Config plus die 54 ap1-Leases und den WG-Hub-Privkey (lokales Fenster-Artefakt, nicht im Golden-Config-Repo).
  • ↪️ Adoption + Compliance-Push → ausgelagert nach TP22 (eigener TP, User-Entscheid 2026-08-08): ap1/ap2/crs326-Adoption, RouterOS-Nornir-Dispatcher, enable_deploy, WG-Privkey/PSK-Secret-Handling. TP21 selbst = Plattform + Datenmodell + Templates (steht). Datenmodell-Nachträge (branch/core-Ports final, sw1 außer Betrieb, cudy1) laufen in TP22 mit.
  • Abh.: TP20 (Postgres muss stehen), Gitea ✅, RouterOS-Fleet (core/branch/ap1/ap2).

TP21.1 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) ▸ ✅ NETZ WIEDER LIVE (2026-08-08); Aufräum-Todos offen

Kern-Erkenntnis (wichtig gegen Doku-Verwirrung): crs326-core IST jetzt der produktive L3-Router. Der Core-Cutover (TP15) wurde faktisch vollzogen — ap1 wurde plattgemacht/getrennt, core hat Routing/DHCP/DNS/NAT/WG-Hub übernommen. core-Mgmt = 10.40.99.1 (vlan99-SVI) bzw. same-subnet-Fallback 10.40.10.1 (RouterOS-Version 7.19.5 identifiziert das Gerät zweifelsfrei als CRS326, nicht als hAP/ap1). Die CLAUDE.md-Zeile „core grundkonfiguriert, NICHT produktiv" ist damit überholt.

  • Verlauf: Ausgangssymptom war ein L3-Ausfall-Eindruck (kein DHCP, „kein Routing nach .20.x"). Ursache = unvollständiger Cutover: core hatte L3-SVIs + Firewall, aber es fehlten DHCP-Server, WAN-Egress, WG-Hub und der DNS-Resolver-Upstream. ap1 (bisher alleiniges L3-Gerät) war bereits Werksreset + getrennt.
  • ✅ DHCP wiederhergestellt aus letztem ap1-Backup (routeros/ap1-pre-core-cutover-20260806-073616.rsc): 7 Server (dhcp-v10…v99) + 8 Networks + 54 statische Reservierungen + Option dns 1.1.1.1. Reproduzierbar in routeros/tp21-core-dhcp-from-ap1.rsc. Legacy VLAN1 (pool-v1/defconf) + tote omada-inform-Option bewusst weggelassen.
  • ✅ WAN/Internet: dhcp-client auf vlan100 → Liwest-Public-IP 90.146.33.177 gebunden (Arris hängt physisch an core.vlan100), NAT-Masquerade out-interface-list=WAN, Default-Route korrekt; die self-pointing Blackhole-Route 0.0.0.0/0 via 10.40.99.1 entfernt. disabled-Falle beachtet: die ap1-Masquerade-Regeln out-interface=bridge/=ether1 sind disabled=yes und wurden NICHT übernommen.
  • ✅ WG-Hub (TP10) neu aufgebaut: wg-hub (listen 51821, Privkey aus routeros/ap1-wg-hub.privkey, Hub-Pubkey unverändert) + Peers fm/dw + Routen. Beide Tunnel handshaken (dw + fm live). Reproduzierbar (Privkey redacted) in routeros/tp21-core-wan-wg-restore.rsc.
  • ✅ DNS gefixt: core hatte allow-remote-requests=no + leeren Upstream → Client-DNS tot UND Hub konnte myfritz (FM-Endpoint) nicht auflösen. Fix = allow-remote-requests=yes + servers=1.1.1.1 (plain, weil core certs=0 → verify-doh-cert unmöglich). Das entsperrte auch FM.
  • ✅ Identity auf crs326-core gesetzt (war fälschlich „ap1" — genau diese Altlast stiftete Verwirrung). mcp/mikromcp/config/routers.yaml auf Zielschema: core .99.1, branch .99.2, ap1 .99.11, ap2 .99.12 (wirkt nach MCP-Neustart; danach core via routerId crs326-core).
  • ✅ disabled/invalid-Falle bereinigt (User-Hinweis „keine disabled-Falle?"): (a) die 2 TP10-WG-Forward-Regeln (allow from/to WG spokes) waren I=invalid, weil aus dem ap1-Backup mit der stale Interface-ID *1D übernommen → auf in/out-interface=wg-hub (per Name) korrigiert; (b) 2 inaktive WG-Route-Leichen (gateway="", aus einem früheren Cutover-Versuch vor Existenz von wg-hub) entfernt, nur die aktiven gateway=wg-hub bleiben. Finaler Sweep sauber: filter-invalid=0, nat-invalid=0, routes-inactive=0, if-disabled=2 (nur ungenutzte sfp-sfpplus1/2, gewollt).
  • ✅ Backups: core-pre-dhcp-restore-20260808.backup (vor Eingriff) + core-post-tp21-recovery-20260808.backup (nach allen Fixes neu gezogen) auf dem Gerät.
  • ℹ️ branch ist gesund (L2, forwardet), Mgmt 10.40.99.2 (+ temp 10.40.10.2 — die Temp-IP ist am 2026-08-16 entfernt worden) — via core-MNDP bestätigt. Das in Winbox-MAC gesehene „0.0.0.0" ist der bekannte Discovery-Artefakt (Gerät hat auf dem Discovery-VLAN keine IP), kein echtes Problem.
  • 🔜 TODO (Aufräum-Liste, spätere Session — danach WLAN-Neuprovisionierung):
    1. DoH-Cloudflare-Gateway auf core wiederherstellen (aktuell nur plain 1.1.1.1, keine Filterung/Account-Bindung): CA-Bundle importieren → use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes (Referenz-Config liegt auf ap1).
    2. Intended config nach Nautobot zurückschreiben (die Direkt-Writes liefen unter der CRS326-Cutover-Governance-Ausnahme).
    3. branch 192.168.88.1-Rest verifizieren (Bootstrap Rev 4 entfernt es in Zeile 70; via MCP noch nicht gegengeprüft, weil Registry-Umstellung MCP-Neustart braucht).
    4. ✅ core ether4 GEKLÄRT (2026-08-13) — es ist das Wohnzimmer-Sideboard (Smart-Managed-Switch, nur VLAN40-Multimedia), und der Port ist der falsche. Symptom war „TV meldet, LAN funktioniert nicht".
      • Ursache = VLAN-Mismatch, kein Kabeldefekt: ether4 hat pvid=40 (untagged rein → VLAN40 ✓), ist in VLAN40 aber tagged-Member (untagged sind ether2/ether3/ether5) → alles raus ist 802.1Q-getaggt und der untagged Switch/TV verwirft es. Einseitige Kommunikation.
      • Belege: TX 3,4 GB / 16 M Pakete gegen RX 452 KB / 1.338 Pakete, 0 stabil gelernte MACs; TV-Lease 10.40.40.11 (70:AF:24:B8:C9:86) bound, last-seen 1h54m, gleichzeitig ARP-Status failed — ein Gerät, das nachweislich sendet, aber nicht antwortet. Der bound-Lease ist kein Gegenbeweis: beim Einschalten macht der TV einen INIT-REBOOT-REQUEST auf seine alte IP, RouterOS quittiert mit ACK und markiert bound — das ACK muss den Client nie erreichen.
      • Herkunft: ether4 war als ap1-Uplink-Trunk vorbereitet („ap1 wandert #15→#4"); ap1 blieb auf ether15, in den übrigen Trunk-Port wanderte das Sideboard-Kabel.
      • ✅ BEHOBEN 2026-08-13 — am Port, nicht am Kabel (User-Vorgabe): „Patchfeld 1–10 ist durchnummeriert und komplett gepatcht, die Nummerierung wird nicht wegen einer Config geändert." Richtig so — die Portnummer ist physische Infrastruktur. Also ether4 sauber auf untagged VLAN40 gezogen statt das Kabel zu verschieben.
      • Ausgeführt via Nautobot-GC-ConfigPlan (plan_type=manual, 6 Zeilen, change_control_id TP21.1-4 …), nicht per Direkt-Write — Governance gewahrt. Vorher /system backup = core-pre-ether4-vlan40-20260813.backup (Regel 3). ether4 aus den Tagged-Listen von VLAN 10/20/30/40/50/90 entfernt + als untagged-Member zu VLAN40 ergänzt. pvid=40 stand bereits — das war nie das Fehlende. Verifiziert: vlan40 untagged=ether2,ether3,ether4,ether5, tagged=ether1,ether8,ether15; vlan99 (Mgmt) und vlan100 (WAN) unangetastet. Damit ist auch der Flood-Leak aus IoT/INFRA/MAIN zum Wohnzimmer-Switch weg.
      • ✅ End-to-End-Beweis ERBRACHT (2026-08-14, live via MikroMCP auf crs326-core) — der TV war am 08-13 tatsächlich nur im Standby. Alle vier Kriterien grün: ping 10.40.40.11 = 5/5, 0 % Loss (avg 563 µs); ARP complete=true/status=delay auf vlan40 (vorher failed); /interface/bridge/host lernt 70:AF:24:B8:C9:86 auf ether4, VID 40; ether4 rx-unicast=6.909 / 3,0 MB RX (am 08-13 noch 1.338 Pakete / 452 KB → der Downstream lebt jetzt). DHCP-Lease tv weiterhin bound auf dhcp-v40. Damit ist TP21.1 Punkt 4 vollständig abgeschlossen und die Diagnose „tagged Egress auf einem untagged Port" ist end-to-end bestätigt (SSoT-Merkregel: ref_tagged_egress_oneway_port).
      • 🔴 INCIDENT + Lehre (2026-08-13): Der erste Deploy lief gegen das FALSCHE GERÄT (ap1 statt core). Ich hatte 4 Device-UUIDs mit einer Sammelabfrage geholt und die Reihenfolge angenommen; die Nautobot-REST-API sortiert aber alphabetisch → ap1, ap2, crs326-branch, crs326-core. Der ConfigPlan trug damit ap1.
        • Schaden: keiner. RouterOS wies jedes set ab (invalid value for argument interface), weil ether8/13/15 auf einem 5-Port-hAP nicht existieren; die Ablehnung ist pro Kommando atomar → kein Teilzustand. ap1 nachgeprüft: VLAN-Tabelle vollständig, 25 WLAN-Clients, Mgmt .99.11. Das war Glück, kein Können — bei einem auf beiden Geräten gültigen Kommando (z.B. tagged=ether1) hätte es ap1s VLAN-Tabelle zerlegt.
        • Regel daraus: Device-UUIDs IMMER einzeln per Name auflösen, nie aus einer Sammelabfrage positional zuordnen — und vor dem Deploy plan.device.display gegen den Zielnamen assert'en (im Skript jetzt so gemacht).
        • Bestätigt zum zweiten Mal: der Deploy-Job meldete SUCCESS und der Plan Completed, obwohl auf dem Gerät nichts ankam. Weder Job- noch Plan-Status sind ein Wirksamkeitsnachweis — nur die Gegenprüfung am Gerät zählt. Der Fehl-Plan wurde gelöscht (irreführender Datensatz).
      • ✅ Lücke geschlossen (2026-08-13, User: „compliance rules! die Änderung machen wir"): /interface bridge port + /interface bridge vlan sind jetzt für alle drei Geräteklassen im Template und als ComplianceFeatures scharf — s. TP22.
    5. ap1/ap2 Neuprovisionierung (WLAN-Rebuild, eigene Session) — ap1 ist Werksreset; danach greifen die .99.11/.99.12-Registry-Einträge.

TP22 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) ▸ ✅ DURCH (2026-08-13), Compliance-Backlog abgearbeitet 2026-08-16 → 68/68, 0 Drifts — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08

  • Ziel: die 4 Live-Geräte (crs326-core, crs326-branch, ap1, ap2) real unter Nautobot Golden Config nehmen — Backup → Compliance/Drift → intended → Push (enable_deploy). Damit greift der Governance-Entscheid „Config exklusiv über Nautobot" operativ. Macht die aktuellen routeros/.secrets/*.rsc-Handoffs mittelfristig überflüssig.
  • Stand aus TP21: Plattform + Datenmodell + Git-Template + intended-config für core stehen; ap1/ap2 als reine APs zurückgeschrieben (nautobot/ingest-ap-rebuild.py, 2026-08-08). enable_plan/enable_deploy in nautobot_config.py noch bewusst aus (pull/read-only).
  • ✅ Schritt 0 DURCH (2026-08-13) — Backup-Scrubbing + Repo-Neuanlage. Vorgezogen aus Schritt 4, weil ohne Scrubbing jedes weitere Backup Secrets geleakt hätte.
    • 🔴 Root-Cause-Fund: Es gab keine ConfigReplace/ConfigRemove-Regeln → der Backup-Job vom 2026-07-22 hatte den ap1-/export ungefiltert nach Gitea geschrieben. backups/ap1.cfg enthielt im Klartext den damals wie heute live genutzten WG-Hub-private-key (bei der Core-Recovery unverändert auf crs326-core übernommen, damit FM/DW-Spokes matchen) und 6 WLAN-PSKs.
    • ✅ 5 ConfigReplace-Regeln (Platform RouterOS) via nautobot/ingest-gc-scrubbing.py (Dry-Run-Default, Regex-Selbsttest bei jedem Lauf): 10-wifi-passphrase, 20-wireguard-private-key, 30-preshared-key, 40-password, 50-secret → Maske "<<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).

TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ STUFE 1 DURCH, STUFE 2 VORBEREITET (2026-08-18)

  • ✅ Stufe 1 — DoH auf dem Core scharf (routeros/tp23-stufe1-doh-core.rsc), schliesst zugleich TP21.1 TODO 1. use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query, verify-doh-cert=yes. servers=1.1.1.1 bleibt als Bootstrap fuer den DoH-Hostnamen stehen.
    • 🔑 Die alte Begruendung „core hat kein CA-Bundle, verify also unmoeglich" war zu pessimistisch. Es braucht kein Bundle, sondern genau einen Root: die Kette ist Leaf → GTS WE1 → GTS Root R4, importiert wurde nur R4 (~1 KB, gueltig bis 2036) — bei 2,8 MB freiem Flash ist das der Unterschied zwischen geht und geht nicht. Fingerprint lokal und am Geraet gegengeprueft (349dfa40…3c7d).
    • 📌 Der Zertifikatsimport steht bewusst nicht im GC-Template: er braucht die Datei am Geraet und ist einmaliger Bootstrap, kein Sollzustand. Im Template steht nur die /ip dns set-Zeile.
    • ⚠️ Der Deploy meldete FAILURE, obwohl alles sass. /certificate import gibt failed: 0 aus, und der Job scannt den Geraete-Output stumpf nach dem Wort. Wieder ein Beleg fuer die TP22-Regel: Job-Status ist kein Wirksamkeitsnachweis, nur die Gegenpruefung am Geraet zaehlt.
  • ⏳ Stufe 2 — AdGuard eingerichtet, aber bewusst NICHT scharf geschaltet (User-Vorgabe 2026-08-18). adguard_bf1 (10.40.20.16) macht bereits DoH zum selben CF-Gateway. Ergaenzt wurden: conditional upstreams [/bf.lan/]10.40.20.1 und [/40.10.in-addr.arpa/]10.40.20.1 (ohne die zweite Zeile wuerden interne Reverse-Lookups zu Cloudflare abfliessen), sechs CIDR-Client-Gruppen (ein VLAN = eine Gruppe) und die permanente Ausnahmegruppe dns-bypass-exempt (aktuell 10.40.10.183). Abnahme per dig: core.bf.lan → 10.40.99.1, nas.bf.lan → 10.40.20.10, extern ueber DoH. Backup AdGuardHome.yaml.bak-tp23-20260817.
    • Kein DHCP-Scope wurde angefasst — alle sieben liefern weiter 10.40.X.1 aus. Das Scharfschalten ist damit spaeter ein reiner DHCP-Change; die passende Firewall-Zeile (TP5-E2: LAN->AGH dns (Vorrat)) steht bereits, damit dabei die scharfe Firewall nicht angefasst werden muss.
    • ⚠️ AGH schreibt die YAML beim Start neu (eigene Schluesselreihenfolge, Kommentare weg, time_zone: Local → UTC). Wer die Datei patcht, muss danach am laufenden Stand pruefen, nicht am eigenen Entwurf.
  • 🔜 Offen bleiben Stufe 3–6 (zweite AGH-Instanz, per-Lease Option 6, HA-Buttons, und zuletzt die Bypass-Sperre). Ausdruecklich keine HA/zweite Instanz (User 2026-08-18).
  • Auslöser: sobald gefiltertes DNS scharf ist, gehen einzelne Seiten/Dienste kaputt. Es braucht Ausnahmen ohne Admin-Eingriff — per User/Client/Gruppe/global, temporär oder permanent, mit Oberfläche. Bedarf ist belegt, nicht vermutet: der bisherige Workaround war „Client von Hand auf 1.1.1.1 stellen", regelmäßig (User 2026-08-14).
  • 🔴 Zentrale Reihenfolge-Regel: genau dieser Workaround IST der DNS-Bypass, den TP5 schließen will → erst der Ersatz, dann die Sperre. Ohne funktionierende Ausnahme-Oberfläche verliert u.a. der Entwicklungsrechner samt Agenten-Toolchain seinen Ausweg — und DNS-Blocks äußern sich dort nicht als klare Meldung, sondern als diffus kaputte Build-Umgebung.
  • Zwei Befunde, die die Architektur bestimmen:
    1. DoH ist nicht das Problem. Clients sprechen plain mit dem lokalen Resolver, der macht DoH nach oben — die empfohlene Auflösung des DoH/Split-Horizon-Konflikts, bf.lan bleibt heil. Was Seiten blockiert, ist die Policy. ➡️ Nie einen „DoH aus"-Schalter bauen, sondern Policy-Ausnahmen.
    2. Identität existiert nur dort, wo die Client-IP sichtbar ist. Router-DoH aggregiert alle Clients hinter einer Quelle — CF sieht nur den Core. Per-User-Identität bei CF verlangt einen Agent (WARP; bei NextDNS/ControlD deren CLI) → auf Shelly/Sonos/TV unmöglich. RouterOS kann keine per-Client-Ausnahme (keine DNS-Views; /ip dns static wirkt domain-weit — verifiziert). ➡️ Ein lokaler Resolver mit Client-Policy ist unvermeidbar.
  • ✅ Architektur-Entscheid: Filter zentral bei CF, Identität + Ausnahmen lokal. Die Filterliste bleibt vollständig bei Cloudflare (Core via DoH, unterwegs via WARP); lokal wird nie eine Filterliste vorgehalten, nur die Diffs. Vorteile: kein RAM-Thema auf dem Core (eine Adlist kostet ~1,9 KB/Domain, 50k ≈ 95 MB — Konkurrenz mit Conntrack/DHCP/WG), eine Policy-Quelle statt zweier, gleiche Regelbasis zuhause wie unterwegs.
  • ✅ Resolver-Wahl: AdGuard Home (läuft bereits, 10.40.20.16). Vergleich gegen die realen Anforderungen:
    • Pi-hole disqualifiziert: spricht kein DoH → bräuchte cloudflared/unbound als Sidecar (vierte QUIC-Komponente neben den drei TP16-Connectoren). Dazu belegtes Bruchrisiko v5→v6 (FTL/API/Web neu, Docker-Env-Variablen tot).
    • AGHs „keine Gruppen"-Schwäche verschwindet hier, weil Clients als CIDR eintragbar sind → 10.40.30.0/24 = die IoT-Gruppe. Die VLAN-Segmentierung ist bereits die Policy-Grenze. Dazu natives DoH, per-Device-ID auch bei wechselnder IP, „Unblock for this client", API + HA-Integration.
    • Technitium = ernsthafte Alternative (autoritativer DNS + DHCP + Apps, networkGroupMap); seine IP/Subnet-Bindung trifft uns kaum, weil die IPs per Reservierung faktisch statisch sind. Neubewertung legitim (die TP4-Ablehnung galt einer anderen Frage) — sinnvoller Anlass wäre, DNS und DHCP zusammenzuführen. Eigener TP, nicht dieser.
    • Verworfen: eigene Self-Service-UI bauen · per-Device-DoH via NextDNS/ControlD (Agent-Zwang, bricht bf.lan) · Umbrella ($30–45/User/Jahr) · Cloudflare verlassen (wirft TP16/TP16.1 weg) · Adlist auf dem Core.
  • Zuweisung ist nativ möglich: /ip/dhcp-server/lease hat die Property dhcp-option (verifiziert) → Option 6 pro Lease, bei 54 Reservierungen ein Konfigurationsschritt. (Nebenfund: Leases haben address-lists → automatische Aufnahme in eine Firewall-Liste, direkt nützlich für die TP5-JIT-Gruppe.)
  • ✅ WARP im LAN abschalten — Mechanismus: Cloudflare „Managed Networks". Der Client sucht bei jedem Netzwechsel einen selbst gehosteten TLS-Endpunkt, prüft dessen SHA-256-Fingerprint und zieht ein Device-Profil, das WARP deaktiviert. Beacon auf NAS/pve, nicht auf den Core (certs=0). Beseitigt die ICMP-Falle vom 2026-08-13 (ref_warp_local_icmp_trap) an der Wurzel.
    • ✅ Kollision aufgelöst (2026-08-14): die frühere Sorge war, dass TP16-Schritt 5 vw auf WARP-geschützt hebt und damit „WARP zuhause aus" bricht. Der User-Constraint aus TP16.2 streicht genau das — vw bleibt bypass und verlangt nie Device-Identity. Damit steht der Managed-Networks-Weg frei. Rest-Risiko nur für Dienste, die künftig doch Device-Identity verlangen sollten → dann Access-Policy zusätzlich auf die Heim-IP (bf4-home-ip existiert bereits).
    • ✅ Kein Verlust beim Fremdnetz-Zugriff: FM/DW laufen über den WG-Hub (TP10), nicht über WARP. Managed Networks schaltet nur im Heimnetz ab.
    • ⚠️ Free-Plan-Verfügbarkeit des Features nicht belegt → im Dashboard gegenprüfen. Holzhammer-Alternative (WARP-Endpunkte per Firewall sperren) nicht empfohlen: bricht still, Client probiert Transport-Fallbacks, trifft auch Gäste.
  • Umsetzung in Stufen — Sperre zuletzt:
    1. DoH auf dem Core scharf → schließt TP21.1-TODO 1 (heute plain 1.1.1.1); CA-Bundle nötig.
    2. WARP-Managed-Network (Beacon auf NAS/pve) → WARP zuhause aus.
    3. AGH in den Pfad, zweite Instanz + Config-Sync, beide per DHCP als DNS 1 + 2.
    4. Client-Gruppen per CIDR (ein VLAN = eine Gruppe); per-Lease Option 6 nur für Abweichler.
    5. HA-Buttons für temporäre Freigaben + JIT (AGH-API, RouterOS-REST, CF-API), RouterOS-scheduler als Backstop, damit Ablauf auch ohne HA greift.
    6. 🔴 Erst jetzt (in TP5): DNS-Bypass sperren — Vorbedingung: Freigabe über HA funktioniert nachweislich, und die permanente Ausnahmegruppe dns-bypass-exempt (Entwicklungsrechner + Build-/Agent-Tooling) steht. Dann: nur Resolver dürfen 53/853 nach außen (schließt Geräte, die eigene Resolver hardcoden — Smart-TV mit 8.8.8.8 ist der Klassiker).
  • Grenzen, offen benannt: ein Resolver im Datenpfad ist unvermeidbar (zwei Instanzen federn ab, ⚠️ AGH hat kein natives HA → Config-Sync ist Betriebsaufwand, sonst gilt nach Failover eine andere Policy) · keine Blockseite bei HTTPS (gilt für jede DNS-Filterlösung, auch Umbrella) · CF-Log-Retention 24 h im Free-Plan — laut User ausreichend, weil Ausnahmen JIT entstehen und ältere Fälle reproduziert werden.
  • ⚠️ 802.1X löst die IoT-Identität NICHT: Consumer-IoT (Shelly, Sonos) hat keinen 802.1X-Supplicant; der Standardweg ist MAB = wieder MAC-basiert → kein Identitätsgewinn gegenüber der DHCP-Reservierung. TP8 bleibt richtig, aber als Sicherheitsthema (dynamische VLAN-Zuweisung, kein MAC-Spoofing bei Managed-Geräten), nicht als DNS-Identitätsquelle.
  • Abh.: TP21.1 TODO 1 (DoH auf core), TP5 (Firewall/Bypass-Sperre — Reihenfolge s.o.), TP16 Schritt 5 (Access-Kollision), TP16.1 (falls WARP zuhause bleiben soll), TP8 (nur Sicherheit). Kein Blocker für TP5 → TP6, aber TP5 darf die Bypass-Sperre nicht vorziehen.

TP24 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) ▸ 🔜 KONZEPT — aus TP15 herausgelöst (User-Entscheid 2026-08-16) — docs/hw-refresh-requirements.md §9

  • Warum ein eigener TP: Die USV hing bisher als Unterpunkt in TP15 (HW-Refresh Switching/Router) mit. TP15s Kernauftrag — CRS326-Beschaffung, Bring-up, Core-Tausch — ist seit 2026-08-08 durch; die Stromversorgung ist als einziger Rest übrig geblieben und hat mit Switching sachlich nichts zu tun. Sie ist ein eigenständiges Infrastruktur-Thema mit eigener Einkaufs-, Verkabelungs- und Entscheidungslogik (Elektrik, Batterie, Standort) und blockiert nichts auf dem kritischen Pfad. Darum eigener TP.

  • Was es ist (Begriffsklärung): eine Pufferbatterie für die Netz-/Infrastruktur-Hardware — keine klassische AC-USV mit 230-V-Steckdosen. Ein 48-V-Gleichspannungsbus versorgt die Geräte direkt an ihrem DC-Eingang; die Batterie hängt am selben Bus und übernimmt bei Netzausfall ohne Umschaltlücke. Der Umweg AC→DC→AC→DC (und damit dessen Dauerverlust) entfällt.

  • Zielbild (Aufbau, unverändert gültig aus der TP15-Recherche):

    Teil Typ Preis Wirkungsgrad
    Netzteil MeanWell LRS-150-48 (150 W, 48 V) ~€30 ~88 %
    DC-USV-Modul MeanWell DDR-120-48 (120 W) ~€80 ~95 % (pass-through)
    Batterie LiFePO4 12,8 V / 20 Ah (256 Wh) ~€50 —
    DC-DC 12 V MeanWell SD-100A-12 (100 W, für NAS/pve) ~€30 ~85 %
    Summe ~€190

    230 V AC → LRS-150-48 → 48-V-Bus → Switches direkt · → DC-DC 12 V → DS923/pve · → DDR-120-48 → LiFePO4.

  • Warum 48 V: PoE-Technik arbeitet nativ mit 44–57 V (kein Wandler nötig), höhere Spannung = kleinere Ströme = weniger Leitungsverlust, und 48 V ist der Telecom-/USV-Standard.

  • Laufzeit (256 Wh): ~2,5 h bei 100 W reiner DC-Last · ~5,5 h wenn nur Switch + PoE (45 W) · ~2 h wenn ein Inverter für den Mac mini mithängt.

  • Effizienz-Argument (der eigentliche Grund für DC statt AC): eine AC-USV verheizt bei 100 W Dauerlast ~25–40 W (≈ €66–105/Jahr), die DC-Variante 5–12 W (≈ €13–32/Jahr) — bei praktisch gleicher Anschaffung (€190 vs. €150–250). Der Mehrpreis amortisiert sich über den Dauerbetrieb.

  • ✅ DC-Eingang vorhanden (geprüft): crs326-core + crs326-branch (12–57 V, DC-Jack) · ap1/ap2 hAP ax² (12–57 V, alternativ PoE-in). ⚠️ Adapter/Sonderfall: DS923+ (externes 12 V/7 A, 4-Pin Mini-Fit JR) · pve/N100 (12 V Hohlstecker) · Mac mini hat gar keinen DC-Eingang (eingebautes Netzteil → nur über Inverter).

  • Offene Entscheidungen (aus TP15 mitgenommen, unverändert offen):

    1. Mac mini auf USV? Braucht einen Inverter (~15–20 % Verlust + Komplexität). Vorfrage: läuft er überhaupt 24/7? Wenn nein, reicht ein Graceful-Shutdown-Script auf USV-Warnung.
    2. Standort/Verkabelung: Der Bus soll laut Skizze im Arbeitszimmer/Technik OG sitzen — der L3-Core steht aber in Technik UG. Entweder zweiter Zweig nach unten oder Bus dorthin, wo der Core steht. Diese Frage ist durch den Core-Umzug neu zu stellen (die Skizze stammt aus der Zeit, als oben der Verteiler geplant war).
    3. Arris-Kabelmodem: vorentschieden auf Grid (muss ohnehin am Netz bleiben, DC-Zweig nach unten wäre Aufwand für ~1–2 W).
    4. Batteriegröße: 20 Ah als Startwert; 50 Ah wenn die gepufferte Last über Switch+AP hinaus wächst.
    5. Monitoring/Shutdown: wie meldet die USV einen Netzausfall an NAS/pve (Signalkontakt vs. nichts)? Ohne das gibt es kein sauberes Herunterfahren, nur Durchhalten bis die Batterie leer ist.
  • Nutzen konkret: bei Stromausfall bleiben Core-Switch, Branch-Switch und beide APs online → Netz, WLAN und der WG-Hub überleben (der WAN-Uplink nur, solange auch der Arris Strom hat, s. Entscheidung 3). Für NAS/pve zählt vor allem das kontrollierte Herunterfahren statt des harten Abschaltens.

  • Abh.: keine harten. TP15 ✅ (die zu versorgende Hardware steht und hat DC-Eingänge). Verwandt: TP18 (Garage-Nodes brauchen je Site eine Stromversorgung), TP11 (USV-Zustand ins Monitoring). Kein Blocker für TP5/TP6.

  • Historie: war bis 2026-08-16 Unterpunkt von TP15 („🔋 DC-USV: MeanWell LRS-150-48 + DDR-120-48 + LiFePO4 20Ah (~€190), 48V-Bus; CRS326 DC-input passt"). Vollständige Recherche inkl. Schaltbild, Effizienzvergleich und Gerätetabelle: docs/hw-refresh-requirements.md §9 (+ §10 D/E/F für die offenen Fragen).

TP25 — WLAN-Stabilisierung ▸ 🔴 JETZT KRITISCHER PFAD — Roaming gemessen (08-20), Übergabekosten gemessen (08-21): Dead Spot statt Roaming-Problem — Sammel-TP für Christophs Beobachtungen 1–6

🆕 Messung 2026-08-20/21: tp25-wlan-roaming-messung-20260820.md (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. 📋 Übergabe: handoff-tp25-session-20260821.md. ⚠️ Auf crs326-core ist eine Config-Transaktion OFFEN (TEST-ether3-trunk-ap1-sideboard, Scope bridge-vlan, Frist 2026-09-04) — die Compliance ist deshalb absichtlich nicht 92/92. Und das passiert TROTZ aktivem 802.11k/v und FT — live geprüft: st-main mit rrm=yes/wnm=yes, ft-over-ds=yes. Die Merkregel ref_ft_is_not_roaming („11k/v fehlen") ist damit historisch. Reihenfolge der Maßnahmen (Doku §6, respektiert die User-Randbedingung „kein neuer AP, solange nicht alles aus den vorhandenen rausgeholt ist"): erst die Lücke messen → 5 GHz auf 20 MHz statt 20/40 (+3 dB Link-Budget, für 1,2 Mbit Call irrelevant an Durchsatz) → Fund E (signal-range-Boden am Manager) → 2,4 GHz NICHT vorschnell drosseln (dort hängen ~20 IoT-Geräte) → dritter AP zuletzt. Restliste (Stand 2026-08-18): Fund A–D ✅ · Drift-Luecke ✅ (mit TP28, 92/92) · offen: Fund E (access-list mit signal-range-Boden — ⚠️ gehoert jetzt auf den Manager, nicht mehr auf die APs) · Fund F (5:05-Takt bei IoT-Trennungen, am Manager nachmessen — die AP-Tabellen sind unter CAPsMAN leer) · Re-Walk + Dead-Spot-Kartierung (hands-on). Alles drei ist Messarbeit gegen die korrigierte Basis, keine Konfigurationsarbeit mehr. Warum eigener TP: Christoph hat am 2026-08-16/17 sechs konkrete Beobachtungen gemeldet und ausdrücklich einen TP dafür verlangt. Sie hängen alle an derselben Ursache-Klasse (Funkparameter, die nicht nach Manual gesetzt sind), gehören also zusammen. TP25 übernimmt den offenen Rest von TP3.3 (Re-Walk, Dead-Spot-Kartierung) — dieselbe physische Arbeit, doppelt geführt wäre Unsinn. TP3.2/TP3.3 bleiben als Historie stehen. User-Randbedingung (2026-08-17, wörtlich): „ich weigere mich einen anderen access point zu kaufen solange ich nicht das gefühl habe, wir haben alles aus dem ap rausgeholt." Das ist die Abnahmebedingung dieses TPs — keine Hardware-Empfehlung, bevor die Parameter stimmen.

  • ⚠️ Ausgangslage: Drift ist bestätigt, aber NICHT die Ursache. Christoph hat am 08-16/17 selbst an den APs gespielt („drift ist garantiert. es hat vorher aber auch nicht funktioniert"). Der Audit unten ist gegen den Ist-Stand gefahren (/interface/wifi/export + /interface/wifi/monitor + Log, beide APs, 2026-08-17 14:01). Zwei der Funde sind älter als die Experimente und stammen aus der TP3-Neuprovisionierung.

🔴 Fund A — 2,4 GHz läuft auf 40 MHz, obwohl das Profil 20 MHz sagt (Beobachtung 4)

  • Christophs Frage war berechtigt, aber der Adressat ist ein Override, keine Entscheidung. Das Channel-Profil ist korrekt: add band=2ghz-ax frequency=2412 name=ch-2g width=20mhz. Überschrieben wird es inline am Radio: set [find default-name=wifi2] channel.band=2ghz-ax .skip-dfs-channels=10min-cac .width=20/40mhz → das Inline-Override gewinnt gegen das verlinkte Profil. monitor beweist es: ap1 2412/ax/Ce, ap2 2462/ax/eC — Ce/eC = 40 MHz. Niemand hat 20/40 „gewählt"; es ist genau die Override-Klasse, die CLAUDE.md für den TP3-Bring-up schon dokumentiert („Werks-Inline-Overrides auf wifi1/wifi2 überschreiben das verlinkte Profil") — damals nur für configuration.ssid/security.* bereinigt, für channel.* nie.
  • Warum 40 MHz auf 2,4 GHz falsch ist (Manual-Ebene, nicht Geschmack): es gibt nur 3 überlappungsfreie 20-MHz-Kanäle. ap1 belegt mit 2412/Ce ≈ 2402–2442, ap2 mit 2462/eC ≈ 2442–2482 → die beiden APs decken zusammen praktisch das ganze Band ab. Jeder Nachbar-AP überlappt zwangsläufig einen von beiden. Dazu halbiert 40 MHz die spektrale Leistungsdichte → ~3 dB weniger Reichweite bei gleicher Sendeleistung. Und der Nutzen ist null: IoT-Clients sind fast alle 20-MHz-only. Das ist die „miserable Reichweite" aus Beobachtung 4.
  • Fix: die Inline-channel.*-Overrides auf wifi1/wifi2 leeren (!channel), damit die Profile greifen. Nebenbefund: skip-dfs-channels=10min-cac auf wifi2 ist bedeutungslos — im 2,4-GHz-Band gibt es keine DFS-Kanäle. Copy-Paste-Spur, harmlos, gehört trotzdem raus.

🔴 Fund B — ap2 sendet auf 5 GHz mit 9 dBm statt 18 dBm (Beobachtungen 1+2)

  • Gemessen: ap1 wifi1 tx-power: 18, ap2 wifi1 tx-power: 9 — bei identischer Hardware und identischem Profil. 9 dB Unterschied = Faktor ~8 in der Leistung, ap2s 5-GHz-Zelle hat grob ein Drittel des Radius von ap1.
  • ✅ BEHOBEN 2026-08-17: ap2s 5-GHz-Radio läuft jetzt mit 18 dBm (5220/ax/Ce/I). Messreihe am Gerät:
    Zustand Kanal tx-power
    ap2 vorher 5745/ax/Ceee (80 MHz, ohne /I) 9
    ap2 nach Frequenzwechsel 5220/ax/eeCe/I (80 MHz, mit /I) 9 ⬅️ unverändert!
    ap2 nach Breitenkorrektur 5220/ax/Ce/I (40 MHz) 18 ✅
    ap1 durchgehend 5180/ax/Ceee/I → 5180/ax/Ce/I 18 → 18
  • 🔴 Ehrliche Korrektur: meine erste Begründung war falsch, und die zweite auch nicht vollständig. Ich hatte die 9 dBm auf ETSI-Band C (ch149 ohne /I-Flag) zurückgeführt. Widerlegt: der Wechsel ins Indoor-Band bei gleicher Breite ließ die Leistung auf 9 dBm. „80 MHz kostet generell Leistung" trägt auch nicht — ap1 fuhr 80 MHz im selben Band mit 18 dBm. Der Fix ist gemessen, der Mechanismus ist es nicht.
  • 🔬 Kohärente Resthypothese (nicht bewiesen, aber testbar): es sind zwei Effekte, je einer pro Kanal — (a) 5745/Ceee = ETSI-Band-C-Limit (die erste Theorie gilt für diesen Kanal weiter), und (b) 5220/eeCe = das Kontrollkanal liegt nicht am Blockanfang (eeCe statt Ceee), was das Radio derated. Das würde ap1s 5180/Ceee=18 und ap2s 5220/eeCe=9 gleichzeitig erklären. Diskriminierender Test: ap2 testweise auf 5180/Ceee — 18 dBm ⇒ Hypothese (b) trägt; 9 dBm ⇒ es ist gerätespezifisch (dann ist ap2s Radio der Verdächtige, relevant für die Hardware-Frage!). Nicht nötig für den Betrieb, aber vor einem Hardwarekauf wissenswert.
  • ⚠️ Die TP3-Notiz „Kanäle non-DFS ap1 ch36/ch1, ap2 ch149/ch11" hatte trotzdem keinen tragenden Grund — s. eigener Absatz „Herkunft der ch149-Notiz" unten.
  • 🎯 Der Fix löst Fund B und C in einem Zug — und er ist nicht offensichtlich: unter ETSI Austria existiert im Indoor-Band (5180–5240) nur ein einziger 80-MHz-Block (5180/Ceee = ch36–48). Zwei APs können dort also nicht beide 80 MHz fahren, ohne sich zu überlappen — genau deshalb ist ap2 überhaupt auf 5745 gelandet. Mit 40 MHz gehen beide sauber ins Indoor-Band:
    AP neu Kanäle Leistung Belegung
    ap1 5180/Ce 36+40 18 dBm 5170–5210
    ap2 5220/Ce 44+48 18 dBm 5210–5250
    Beide /I-freigegeben (aus der Radio-Tabelle bestätigt), überlappungsfrei, +9 dB für ap2.

🔴 Fund C — 80 MHz auf 5 GHz kostet Reichweite, die hier fehlt (Beobachtung 1)

  • ch-5g width=20/40/80mhz, laufend 5180/ax/Ceee bzw. 5745/ax/Ceee = 80 MHz. Dieselbe Sendeleistung auf die vierfache Bandbreite verteilt kostet ~6 dB effektiven SNR am Zellenrand gegenüber 20 MHz, ~3 dB gegenüber 40 MHz. In einer 2-AP-Wohnung mit Terrasse ist Spitzendurchsatz das falsche Optimierungsziel — Randabdeckung und Stabilität sind das richtige.
  • Beleg, dass genau das das Terrassen-Symptom ist: ap1-Log, Telefon steht still im Halter: -81 → roamt auf 2,4 GHz -82 → zurück -83 → disconnected, connection lost, signal strength -88. −88 dBm ist unter der Nutzbarkeitsschwelle. Das ist kein Roaming-Problem (Christoph sagt selbst „ich red da nicht mal von roaming"), sondern Link-Budget — und dagegen hilft Breite runter, nicht drehen.

🔴 Fund D — w2-iot trägt einen unzulässigen Channel-Override auf einem Slave-VAP (Beobachtung 3)

  • ap1: add channel.band=2ghz-n .width=20mhz configuration=cfg-iot … master-interface=wifi2 name=w2-iot
  • ap2: add channel.width=20mhz configuration=cfg-iot … master-interface=wifi2 name=w2-iot
  • Ein Slave-VAP teilt das Radio des Masters und kann keinen eigenen Kanal haben. Auf ap1 erzwingt das Override zusätzlich 2ghz-n, während der Master wifi2 auf 2ghz-ax läuft — ein Band-Mismatch zwischen Master und Slave ist keine unterstützte Konfiguration. Das ist der Mechanismus hinter „alle SSIDs sind da, -iot beharrlich nicht, und plötzlich ist es wieder da": der VAP fällt aus der Beacon-Rotation und kommt bei einem Re-Apply zurück.
  • 📌 Und es ist der Beweis für Beobachtung 6: die beiden APs sind hier nicht identisch (ap1 mit Band+Width, ap2 nur Width). Eine generierte Config kann das nicht produzieren — das ist Handarbeit, und zwar auf genau dem Objekt, das keine Handarbeit verträgt.

🟠 Fund E — keine access-list, also kein Signal-Boden gegen klebende Clients (Beobachtung 2)

  • /interface/wifi/access-list ist auf beiden APs leer. Damit gibt es keinen signal-range-Boden — ein Client darf hängen bleiben, bis die Verbindung zusammenbricht, statt frühzeitig abgestoßen zu werden. Das ist der Standard-RouterOS-Griff gegen genau dieses Symptom.
  • Beleg für das Kleben: ap2-Log, Client 56:71:6C:22:E6:A9 flappt in 22 Minuten sechsmal zwischen wifi1↔wifi2 bei −62…−83 dBm. Und: F2:62:4C:E1:AF:09@wifi2 disconnected, not responding, signal strength -47 — Abbruch bei ausgezeichneten −47 dBm. Beides zusammen = Band-Oszillation ohne Steering-Politik, nicht schlechte Abdeckung.
  • ⚠️ Nebenbefund: die TP3.2-Access-List, die den P750W an ap1 pinnte („überlebt ap2-Re-Provision"), ist beim Rebuild 2026-08-08 verlorengegangen — im Export beider APs steht keine einzige Access-List-Zeile. Laut CLAUDE.md ist der Pin nicht mehr nötig (L3-GW liegt auf dem Core), der Verlust war aber unbemerkt.
  • ft=yes ft-over-ds=no war korrekt für den damaligen Standalone-Betrieb (over-DS braucht ein Inter-AP-Protokoll) — kein Fund, nur zur Abgrenzung festgehalten. 📌 Seit TP28 gilt das nicht mehr: unter CAPsMAN ist ft-over-ds=yes aktiv, weil der Manager genau dieses Inter-AP-Protokoll mitbringt.

🟡 Fund F — IoT-Clients trennen exakt alle ~5:05 bei gutem Signal (offen, präzise vermessen)

12:55:29 connected -59  →  13:00:36 disconnected -59   (5:07)
13:17:27 connected -63  →  13:22:32 disconnected -63   (5:05)
13:35:32 connected -60  →  13:40:37 disconnected -59   (5:05)
13:57:31 connected -59  →  14:02:35 disconnected -58   (5:04)
  • −59 dBm ist ein gutes Signal. Eine auf 3 Sekunden reproduzierbare 5-Minuten-Periode ist ein Timer, kein Funk.
  • 🚫 Ausdrücklich NICHT die alte Power-Save-Merkregel wiederbeleben — die ist am 2026-08-13 als Fehldiagnose zurückgezogen worden (ref_wifi_powersave_multicast, Drucker war schlicht aus). Dieser Befund ist ein anderer: vier saubere Messpaare mit konstanter Periode.
  • Kandidaten + der Test, der sie trennt: (a) periodischer VAP-Re-Apply durch Fund D → prüfen, ob ap2 (ohne Band-Override) dieselbe Periode zeigt; (b) DHCP-Lease/Renew-Interaktion → lease-time der beiden MACs (38:A5:C9:C0:73:1E, 38:A5:C9:BC:33:31) gegen die Periode legen; (c) das Gerät selbst mit 5-min-Duty-Cycle → dann bleibt es nach Fund-D-Fix unverändert. Erst nach dem Fund-D-Fix erneut messen — sonst misst man zwei Effekte übereinander.

🧭 Beobachtung 5 — „alle Welt sagt CAPsMAN, nur wir sagen Nautobot GC. Hilfe oder Last?"

  • Klare Antwort: es sind keine Alternativen, sie lösen verschiedene Probleme. CAPsMAN = Runtime-Control-Plane fürs WLAN (ein Ort für Radio-Settings, sofortiger Push, dynamische VAPs). Nautobot GC = Config-as-Code-SoT
    • Drift-Erkennung über die ganze Flotte (Core, Branch, beide APs). Man kann beides haben; das Reibungspunkt ist nur, dass CAPsMANs dynamische Objekte für GC unsichtbar sind (der Grund von 2026-07-22, und der gilt).
  • 🔎 Aber eine Begründung von damals hält der Prüfung nicht stand. Der Entscheid nannte als ersten Grund „WLAN dauerhaft instabil, besonders wenn beide APs laufen". Das WLAN ist nach dem Standalone-Rebuild weiterhin instabil — die These „CAPsMAN war die Ursache" ist damit empirisch geschwächt. Die Funde A–E sind Funkparameter; CAPsMAN hätte sie weder verursacht noch geheilt. Der Rebuild hat ein Symptom umgezogen, keine Ursache beseitigt.
  • Wo GC hier tatsächlich Last war: es hat diesen Drift nicht verhindert. Die Compliance deckt wifi-steering ab (seit TP3.3), aber offensichtlich nicht die Channel-/Inline-Override-Fläche — sonst wären Fund A und D als Drift aufgeschlagen. Das ist eine Lücke im Feature-Set, kein Argument gegen GC.
  • Ehrliches Urteil (meine Empfehlung): GC als SoT behalten — es ist das Einzige, was Hand-Tuning davon abhält, sich still zu akkumulieren, und genau das ist hier passiert. Aber: GC ist kein WLAN-Tuning-Werkzeug. Der Ausweg ist nicht CAPsMAN, sondern die Parameter einmal nach Manual richtig zu setzen und dann mit einer Compliance-Feature auf /interface/wifi festzunageln. Bei genau 2 APs ist der Kanal-/Leistungsplan eine Einmalentscheidung, kein Laufzeitproblem — der Hauptvorteil von CAPsMAN greift hier also kaum. (Wenn ein dritter AP dazukäme, wäre die Abwägung neu zu führen.)

🔎 Herkunft der ch149-Notiz (Christophs Frage 2026-08-17: „wie kam es zu dieser Notiz?")

  • Nachverfolgt bis Commit 03f328f, 2026-06-18, CAPsMAN-Ära. Wortlaut der Commit-Message: „Kanal-Plan non-DFS/überlappungsfrei: ap1 ch36+ch1, ap2 ch149+ch11 (Auto-Channel hatte beide APs auf 5500-DFS/2437 gelegt = Co-Channel)".
  • 🎯 Das gelöste Problem war Co-Channel, nicht DFS. Auto-Channel hatte beide APs auf denselben Kanal gelegt; der Fix war, überhaupt zu pinnen. Dass die Wahl auf 36/149 fiel, ist das FCC-Standardpaar aus jedem Tutorial — „non-DFS" wurde als Begründung nachträglich mitgeschrieben, war aber nie eine geprüfte Anforderung. Christophs Einschätzung ist damit belegt: es gibt keinen Grund, Frequenzbänder auszunehmen.
  • 📌 Und ein Detail, das die Sache entschärft: skip-dfs-channels=10min-cac schließt DFS gar nicht aus. Es überspringt nur Kanäle, die die 10-Minuten-CAC brauchen (Wetterradar-Band 5600–5650). Der Rest des DFS-Bands bliebe nutzbar. Ausgeschlossen wurde DFS in Wahrheit durch das harte frequency=-Pinning — wo nichts ausgewählt wird, wirkt kein Skip. (Die Overrides sind mit Schritt 2 ohnehin weg.)
  • Konsequenz für den Kanalplan: das DFS-Band 5500–5700 ist eine echte Option und liegt bisher brach — 11 zusätzliche Kanäle, in ETSI ohne /I-Beschränkung. Preis: CAC-Wartezeit beim Start und im Radar-Fall ein erzwungener Kanalwechsel (30 min Sperre). Bei Christophs aktuellem Symptombild (Abbrüche) wäre ein radar-getriggerter Wechsel unangenehm — deshalb jetzt nicht, aber es ist der Reservekanalraum, falls ein dritter AP dazukommt und 40 MHz nicht mehr reicht.

📡 Beobachtung 4b — „könnte 2ghz-g nicht besser zu IoT passen?"

  • Verfügbar auf diesem Radio (/interface/wifi/radio): 2ghz-g:20mhz · 2ghz-n:20mhz,20/40mhz · 2ghz-ax:20mhz,20/40mhz.
  • ❌ Für Reichweite bringt es nichts. band= setzt die höchste angebotene Generation, nicht die einzige — ein 2ghz-ax-AP ist rückwärtskompatibel, g/n-Clients assoziieren normal. Und ein schwacher Client fällt über die Ratenadaption von selbst auf die robusten niedrigen Raten. band ändert weder Sendeleistung noch Empfängerempfindlichkeit.
  • ✅ Für Interoperabilität ist der Gedanke aber richtig. Billige ESP-/Realtek-IoT-Stacks haben dokumentierte Probleme mit 802.11ax-Beacons (HE-Capabilities, PMF). 2ghz-n nimmt die HE-Elemente aus dem Beacon und kann Assoziationswackler beheben — ein Kompatibilitäts-, kein Abdeckungsargument.
  • 🎯 Pointe: genau dieser Instinkt steckte schon im Fehler. channel.band=2ghz-n auf w2-iot (Fund D) war exakt der Versuch, IoT ein legacy-freundlicheres Band zu geben. Die Absicht war richtig, der Mechanismus falsch — band ist eine Eigenschaft des Radios, nicht eines Slave-VAPs. Alle 2,4-GHz-SSIDs teilen wifi2.
  • Empfehlung, falls Fund F (5-Minuten-Takt) nach dem Override-Fix bleibt: wifi2 global auf 2ghz-n — die saubere Version dessen, was der Override wollte. Kostet real nichts (kein 2,4-GHz-Client hier braucht ax-Raten, dirtyvader-main auf 2,4 GHz ist ohnehin Fallback). 2ghz-g NICHT als ersten Schritt: die 20-MHz-Bindung ist richtig, aber 54 Mbit/s-Deckel und kein HT für alle 2,4-GHz-Clients ist ein echter Verlust ohne Zusatznutzen gegenüber n. g ist die Eskalationsstufe, nicht der Einstieg.

🏠 Deckenmontage-APs — bringen sie einen echten Vorteil? (Christophs Überlegung 2026-08-17)

  • Der Gewinn liegt in Platzierung und Antennendiagramm, nicht im Silizium. Ein cAP ax ist dieselbe Chip-Generation und ebenfalls 2×2 wie der hAP ax² — mehr Rohleistung gibt es nicht. Was er gibt: ein nach unten optimiertes Diagramm, freie Sichtlinien von der Decke, PoE-Montage ohne Möbel davor.
  • 🔴 Aber die Entscheidung wäre heute blind gewesen. Vor dieser Session lagen ~9 dB auf ap2 brach (Fund B), 2,4 GHz verschenkte ~3 dB an 40 MHz (Fund A) und der 5-GHz-Zellenrand ~3 dB an 80 MHz (Fund C). Hardware zu kaufen, um Fehlkonfiguration zu kompensieren, ist der teuerste Weg. Genau das deckt sich mit Christophs eigener Randbedingung („nicht kaufen, bevor alles aus dem AP raus ist").
  • ✅ Jetzt ist der Boden gelegt (beide APs 18 dBm, überlappungsfrei, 20 MHz auf 2,4). Die Entscheidung gehört an den Re-Walk (Schritt 5). Bleiben danach Dead Spots — Terrasse, Kanten —, dann ist es ein Platzierungsproblem, und Deckenmontage ist die richtige Antwort. Dann stellt sich aber zuerst die billigere Frage: reicht Umhängen der vorhandenen APs? „Drehen hat nicht geholfen" passt zu einer Abschattung, die Drehen nicht behebt — Versetzen schon.
  • 📌 Falls es auf drei APs hinausläuft, wird die CAPsMAN-Abwägung neu geführt (s. Beobachtung 5): bei 3 APs ist koordinierte Kanal-/Leistungsverwaltung erstmals ein Laufzeit- und nicht mehr ein Einmalproblem. Und der diskriminierende Test aus Fund B (ist ap2s Radio gerätespezifisch schwach?) gehört vor einen Kauf.

📋 Umsetzungsreihenfolge (alles via GC, gestaffelt, ap2 zuerst)

  1. ✅ Fund D DURCH (2026-08-17) — channel.*-Overrides von w2-iot auf beiden APs entfernt. w2-iot ist danach auf ap1 und ap2 BR (bound + running) → Beobachtung 3 strukturell erledigt. Dateien: routeros/tp25-schritt2-fix-overrides-ap2.rsc, routeros/tp25-schritt1-3-ap1.rsc.
  2. ✅ Fund A DURCH (2026-08-17) — Inline-channel.* auf wifi1/wifi2 geleert. 2,4 GHz läuft jetzt auf 20 MHz (2412/ax bzw. 2462/ax, kein Ce/eC mehr), tx-power 15 dBm. ap1 ch1 / ap2 ch11 = überlappungsfrei mit Platz für ch6 in der Nachbarschaft.
  3. ✅ Fund B+C DURCH (2026-08-17) — ch-5g auf width=20/40mhz; ap1 5180/ax/Ce/I, ap2 5220/ax/Ce/I, beide 18 dBm (ap2 vorher 9). Zellen benachbart, überlappungsfrei (5170–5210 / 5210–5250).
    • 🔴 GOTCHA, der zwei Deploys gekostet hat: /interface/wifi/set <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.

TP16.3 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) ▸ 🔜 AUSFÜHRBAR (Runbook 2026-08-19) — docs/tp16.3-nautobot-mesh-only-runbook.md

📌 Der Schnitt (nautobot aus dem bf-nas-Ingress) ist ausformuliert inkl. Vorbedingungen, PUT-Ablauf und Rollback. Zwei Punkte stehen dort explizit, weil sie den TP sonst kippen: Mesh-Hostname-Routes sind Egress und veröffentlichen nichts (wer das verwechselt, baut den Public-Strang ab), und Nautobot ist nicht nur Web-UI, sondern die Deploy-Pipeline → zur Abnahme gehört ein Job-Lauf, nicht nur ein Browser-Aufruf. Christophs Frage (2026-08-17): „recherchiere, ob unser neuer wg-tunnel/cf mesh als datenpfad für die server/services genutzt werden kann. die containerpflicht als offizieller weg ist für mich unlogisch."

  • ✅ Die Container-„Pflicht" gibt es für den privaten Zugang tatsächlich nicht — und wir nutzen sie schon nicht mehr. CF-Docs (Mesh↔Tunnel-Vergleichstabelle) sind eindeutig: Mesh = bidirektional, L3/L4, TCP+UDP+ICMP, jeder Teilnehmer bekommt eine Mesh-IP, Connector = warp-cli oder ein nativer WireGuard-Peer (unser Fall). Tunnel = inbound zum Origin, Veröffentlichen von Hostnames, Connector = cloudflared, Protokolle HTTP/S·TCP·SSH·RDP·SMB über WebSocket geproxied. Für „Server hinter dem Node erreichbar machen, die keinen Client laufen lassen können" nennen die Docs ausdrücklich CIDR-Routes auf dem Mesh-Node als den Weg — wörtlich für „servers, databases, printers, IoT devices".
  • 🎯 Das ist bei uns seit TP16.1 bereits scharf: die vnet-Route 10.40.0.0/16 zeigt auf bf-core-mesh (§4.4, Weg A). Der gesamte private Datenpfad läuft schon über natives WireGuard auf dem Core, ohne jeden Container. Und er ist im TP5-Log-Lauf belegt, nicht vermutet: 137 Zeilen in:cf-mesh, Quelle 100.96.0.1 → Nautobot .20.30:8080 (62×), NAS .20.10:6690 (46×), macmini :22, NAS :5000/:5001/:443/:22, pve .20.100:8006, pve-mcp .20.173:3000, branch/ap1/ap2 :22. Christophs Instinkt war richtig.
  • 🔴 Wo die Container-Pflicht echt ist — und warum sie nicht willkürlich ist: für einen öffentlichen Hostname, den jemand ohne WARP im Browser aufruft, braucht es einen Reverse-Proxy an der Edge. Das ist cloudflared. Achtung Verwechslungsfalle: Mesh kennt seit 2026-07-02 ebenfalls Hostname-Routes, und die können auch public hostnames — aber das ist Egress: „route that hostname's traffic through the node and egress via the node's public IP". Es veröffentlicht nichts. Wer das verwechselt, baut den Public-Strang ab und ist überrascht.
  • ✅ Der reale Gewinn ist trotzdem groß, weil die meisten Dienste keinen öffentlichen Hostname brauchen. Kriterium: hat der Dienst einen Client, der keinen interaktiven Access-Login kann? (dieselbe Logik wie der TP16.2-Constraint bei vw)
    Muss öffentlich bleiben (cloudflared) Grund Kann auf Mesh-only
    vw Bitwarden-Clients, harter TP16.2-Constraint pve, nautobot, portainer, grafana
    hass Companion-App (400-Muster) pve-mcp, ha-mcp, gitea-mcp, ds-bf
    n8n eingehende Webhooks von Dritten ccu, iobroker, z2m25
    gitea Git-Clients / CI von außen — zu prüfen
    → Für die rechte Spalte ist der Mesh funktional gleichwertig und operativ einfacher (keine Ingress-Zeile,
    kein Connector-Paar, keine HA-Frage). Sie erreicht man ohnehin schon per privater IP.
  • 📌 Zwei Nebeneffekte, die den Fall stärken: (1) die TP16-HA-Degradierung (bf-nas/bf-pve je nur ein Connector) verliert an Gewicht, sobald nur noch die vier Public-Dienste an cloudflared hängen — weniger Dienste pro SPOF; (2) ICMP + beliebiges UDP gehen über den Mesh und über cloudflared grundsätzlich nicht (der QUIC-Proxy war genau die Lücke aus TP16.1).
  • ⚠️ Grenzen, ehrlich benannt: (a) der Mesh-Weg setzt WARP auf dem Client voraus — für Christophs eigene Geräte gegeben, für Gäste/Fremdgeräte nicht; (b) IPv6-CIDR-Routes brauchen MASQUE und funktionieren mit einem WireGuard-Device-Profil nicht — deckt sich mit dem v4-only-Entscheid aus TP16.1, wäre aber ein Blocker, falls TP12 später v6 über den Mesh wollte; (c) es bleibt eine Cloudflare-Abhängigkeit — der Mesh ersetzt cloudflared, nicht Cloudflare.
  • Vorgeschlagener Umsetzungsschnitt (klein anfangen, reversibel): einen Dienst der rechten Spalte als Probe aus dem CF-Ingress nehmen (Kandidat: nautobot — nur Christoph nutzt es, im Mesh-Log mit 62 Treffern der am häufigsten per Mesh angefasste Dienst, und ein Fehlschlag kostet nichts Produktives), eine Woche fahren, dann entscheiden. Rollback = eine Ingress-Zeile zurück.
  • 📋 Runbook: docs/tp16.3-nautobot-mesh-only-runbook.md (2026-08-19) — der Schnitt oben, ausführbar ausformuliert: Vorbedingungen (Mesh-Route, Zugriff über 10.40.20.30:8080 mit WARP von außerhalb), Ingress-PUT über CF_API_TOKEN (nicht über den CF-MCP, der ist read-only), Rollback aus der gesicherten Config.
    • 🔴 Der Abnahmepunkt, den man übersieht: Nautobot ist nicht nur Web-UI, sondern die Deploy-Pipeline. Nach dem Schnitt gehört ein Job-Lauf zur Abnahme, nicht nur ein Browser-Aufruf — der Netmiko-Deploy kommt von 10.40.20.31 (nautobot-worker), nicht von .20.30.
    • ⚠️ Beim Kürzen des ingress-Arrays muss die Catch-all-Regel die letzte bleiben, sonst greift der Ingress für alle übrigen Hostnames nicht mehr.
  • Abh.: TP16.1 ✅ (Mesh live), TP16 (Public-Strang + dessen HA-Degradierung), TP16.2 (der vw-Constraint liefert das Entscheidungskriterium). ⚠️ Berührt TP5: der Mesh-Verkehr braucht bei deny-by-default eine explizite forward-Zeile — s. TP5 Etappe 2.

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

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

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

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

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

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

TP28 — WLAN über CAPsMAN auf crs326-core ▸ ✅ KOMPLETT (2026-08-18) — mit Nachtrag: slaves-datapath fehlte, IOT+MEDIA lagen ~14 h

  • 🔴🔴 POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"): Seit dem Cutover hingen alle Slave-VAPs in gar keiner Bridge. /interface wifi cap slaves-datapath war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten Slave-VAPs lokal in die Bridge haengt. Der Manager schickt datapath.bridge=bridge zwar mit, der CAP uebernimmt davon nur die vlan-id. Betroffen: dirtyvader-iot, -media, -untrusted, -guest. Nicht betroffen: dirtyvader-main — dessen Master-Radios wifi1/wifi2 sind statische Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.

    • Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template): /interface wifi datapath add name=dp-caps-slaves bridge=bridge + /interface wifi cap set slaves-datapath=dp-caps-slaves. Das Objekt traegt bewusst keine vlan-id — die liefert der Manager pro VAP. Danach erscheinen die VAPs als dynamische Bridge-Ports mit pvid=30/40/50/90, und DHCP laeuft sofort wieder.
    • 🔴 Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts. Am Manager standen 25 IOT- Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle: torch auf vlan30 am Core (leer) und der TP5-Drop-Log (kein einziger DHCP-Eintrag). Beides leer heisst: es wird nichts verworfen, es kommt nichts. Damit war die Firewall — der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
    • ⚠️ Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch. Sie zaehlte Registrations (30 Clients, beide CAPs Ok, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine frische DHCP-Lease pro SSID, nicht die Registration-Table.
    • routeros/tp28-ap-wifi-reste-abraeumen.rsc hat den Ausfall nicht ausgeloest, aber zementiert: mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf slaves-datapath haette zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
    • ✅ Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet: slaves-datapath ueberlebt den Neustart (statisches Objekt, steht im /export), der CAP findet seinen Manager durch die gehaertete input-Kette wieder (current-caps-man-identity=crs326-core — MNDP/CAPWAP sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach 40 s wieder erreichbar. Abnahme nach neuer Regel = frische Lease: IOT-Client 10.40.30.8 last-seen=30s, MAIN-Client 10.40.10.180 last-seen=1m54s.
    • ⚠️ Reboot per MikroMCP geht auf 7.24 nicht: das reboot-Tool sendet delay, die REST-API antwortet unknown parameter delay; run_command ist fuer /system reboot* per Deny-Policy gesperrt. Weg bis das gefixt ist: sshpass + ssh admin@<ap> "/system reboot".
    • ✅ Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts (2026-08-18). Weg: Template in nautobot/mikrotik_routeros.j2 → routeros/push-gc-template.py (Secret-Guard) → GitRepository-Sync → Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als extra auf beiden APs — das Jinja lebt im separaten Gitea-Repo bf-golden-config, ein Commit hier allein erreicht Nautobot nicht.
    • ⚠️ Beim ersten Sync-Versuch: redis.exceptions.ConnectionError: broken pipe auf dem Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das als FAILURE — nicht mit einem inhaltlichen Fehler verwechseln.
  • ✅ Der offene GC-Rest ist erledigt (2026-08-18). Die fuenf wifi-*-ComplianceFeatures haengen jetzt am Core, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft (/interface wifi cap set discovery-interfaces=vlanXX enabled=yes).

    • 🔴 Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config aus TP3 — je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den defconf-Firewallregeln in TP26. Abgeraeumt mit routeros/tp28-ap-wifi-reste-abraeumen.rsc.
    • ✅ Ohne jeden Aussetzer: vorher/nachher 30 Clients, beide CAPs Ok, 7 dynamische VAPs je AP. Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht bestaetigt — die dynamischen VAPs tragen ihre Werte inline (datapath.vlan-id=…) und referenzieren kein lokales Objekt; nur wifi1/wifi2 hielten noch einen toten configuration=-Verweis, der beim Loeschen folgenlos blieb.
    • 🔴 [find !dynamic] ist auf diesen Sektionen ein SYNTAXFEHLER, nicht bloss wirkungslos: /interface wifi configuration & Co. haben gar keine dynamic-Eigenschaft (die gibt es nur auf /interface wifi selbst). Der erste Anlauf lief komplett in die on-error-Zweige, aenderte nichts und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
  • ✅ Drei neue ComplianceRules schliessen die letzten WLAN-Luecken: wifi-capsman (der Manager-Schalter), wifi-provisioning (die vier Regeln je radio-mac — sie entscheiden, welcher AP welche Kanaele und Slave-VAPs bekommt) und wifi-cap (die CAP-Seite; faellt discovery-interfaces weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr). Ergebnis: 92/92 compliant, 0 Drifts.

  • ⚠️ wifi-security ist gruen, aber NICHT deploybar. Die Passphrasen stehen als <<REDACTED>> im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen). Der gruene Haken beweist nur, dass Soll und Ist dieselbe Maske tragen — nicht, dass die Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck (GET http://10.40.99.1/rest/interface/wifi/security) — nicht aus wlan-psks.env oder den drei Bring-up-Dateien, die voneinander abgeschrieben sind.

  • ⚠️ Die frueher hier notierte „bisher fehlende Rule auf /interface wifi" ist gegenstandslos — wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.

Methodenwechsel auf Christophs Anstoß: „ich möchte die methode in frage stellen … eine sot ist zwar nett, aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance." Der Entscheid vom 2026-07-22, CAPsMAN abzuschaffen, ist damit revidiert.

  • ✅ Warum es diesmal trägt: 2026-07-22 war ap1 Manager UND AP — und ein Manager kann seine eigenen Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von TP3.1. Mit dem Core als Manager sind beide APs symmetrisch. Der Core braucht dafür kein wifi-qcom: das capsman-Menü steckt im Basis-routeros, die 3,35 MB freier Flash sind irrelevant. Die zweite Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen waren die Funkparameter aus TP25.
  • ✅ Ist-Stand: Manager auf vlan99, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations · 4 Provisioning-Regeln je radio-mac. Beide remote-cap Ok, je 7 dynamische VAPs. Kanäle exakt wie in TP25 gemessen (ap1 5180/ax/Ce/I+2412/ax, ap2 5220/ax/Ce/I+2462/ax). 34 Clients (main 3, iot 27, media 4), Roaming im Manager-Log belegt. ft-over-ds=yes ist jetzt aktiv (unter CAPsMAN erstmals verfügbar), -guest hat eigenes st-guest mit neighbor-group=bf-guest — nicht bf-main mitbenutzen, sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
  • 📌 Local-Forwarding ist keine Einstellung mehr: im neuen /interface/wifi gibt es kein zentrales Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die datapath-Property existiert deshalb nicht — monitor bestätigt traffic processing on CAP.
  • 🔴 DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP. /interface/wifi/registration-table und die wireless-Logs sind auf ap1/ap2 leer, obwohl das WLAN einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit vollem Ausschlag verbunden waren und ein Speedtest gut lief. Immer am Manager zählen.
  • 🔴 DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege. wlan-psks.env wich für iot/untrusted/infra von ap1-prov-base.rsc, ap2-prov-base.rsc und ap1-standalone.rsc ab. Ich habe die drei als Bestätigung gelesen und die env-Datei „korrigiert" — ~20 Shellys waren 20 Minuten aus dem Netz. Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also eine Quelle in drei Kopien. ✅ Auflösung: die Passphrasen sind am Gerät per REST lesbar (GET http://10.40.99.1/rest/interface/wifi/security) — dort ausgelesen ergab sich exakt der env-Wert. wlan-psks.env ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
  • ✅ OFFEN — das GC-Modell beschreibt weiter Standalone-APs — ERLEDIGT am 2026-08-18. Die wifi-*-Features haengen jetzt am Core, das AP-Template ist auf die cap-Zeile eingedampft, und die toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs. Die hier genannte Zahl 76/76 ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules). (Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon „KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session gestolpert ist. Merkregel fuer kuenftige Statuswechsel: nicht nur den Header umschreiben, sondern die Punkte darunter mitziehen.)
  • 📌 Nebenbefund: HAs mikrotik-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (get_log ohne topics-Filter liefert nur admin logged in/out via api).
  • Abh.: TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort). Verwandt: TP3 (dessen CAPsMAN-Ära hier revidiert wird), TP22 (GC-Scope).

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

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

Reihenfolge / Abhängigkeiten

🎯 Kritischer Pfad (Stand 2026-08-19)

TP5 E1 ✅ ─► E2 ✅ ─► E2-Nachtrag ✅ ─► E3 Drop ✅ ─► E4 input+MGMT ✅ ─► E5 MAIN→IOT ✅ ─► TP6 (CAKE) ◄ Ziel
                                                                                            ▲
                                                                                  ab hier ist alles frei

TP5 ist am 2026-08-18 abgeschlossen. Damit steht rechnerisch nur noch TP6 auf dem kritischen Pfad — 🟠 aber dessen Prämisse ist offen: eine Messung von 2026-06-16 (docs/call-quality-diagnosis.md) bescheinigt dem Netz bereits A/A+ und hält CAKE für „marginal". Der nächste Schritt ist deshalb ein Bufferbloat-Test gegen den heutigen Edge, nicht der Bau einer Queue. Fällt er gut aus, ist der kritische Pfad leer und das Projekt in seinem Kern fertig — dann entscheidet Christoph, was das neue Ziel ist. Alles andere läuft nebenher und blockiert nichts.

  1. ✅ TP5 Etappe 1–4 — komplett durch (2026-08-16 bis 2026-08-18). Details im TP5-Abschnitt oben. Die Wartezeit-Position, die hier bis 2026-08-17 als eigener Schritt stand, ist eingeloest: statt eines mehrtaegigen Fensters wurde der Nachtrag aus einem dreieinhalbstuendigen Fenster abgeleitet und der Drop mit Auto-Revert plus weiterlaufender Log-Regel abgesichert. Die Vorbedingung D2 (iPhone .30.201) ist gegenstandslos: das Geraet hing dort bewusst zum Anlernen eines IoT-Geraets (User 2026-08-17). Wer im IOT-SSID sitzt, ist im selben VLAN wie das Geraet und beruehrt die forward-Kette gar nicht; fuer Ad-hoc-Zugriffe aus MAIN gibt es jetzt die selbstverfallende Gruppe jit-admin.
  2. 🟠 TP6 — QoS/CAKE + Call-Quality-A/B-Test. Das eigentliche Projektziel; keine offenen Blocker mehr. Korrigiert 2026-08-19: kein Blocker, aber eine offene Prämisse — der Satz oben und dieser Punkt widersprachen sich seit dem 18. Richtig ist: der nächste Schritt ist eine Messung, kein Bau. Runbook liegt bereit: docs/tp6-praemisse-messrunbook.md. Fällt sie A/A+ aus, wird TP6 geschlossen statt gebaut — dann ist der kritische Pfad leer und das Projekt in seinem Kern fertig.

Eine harte Reihenfolge-Regel dazwischen: TP5 darf die DNS-Bypass-Sperre erst schließen, wenn TP23s Ausnahme-Oberfläche steht — der bisherige Workaround „Client von Hand auf 1.1.1.1" ist genau dieser Bypass. Alles andere aus TP5 ist davon unabhängig und kann vorher laufen.

⚠️ Und eine zweite, aus TP16.1/TP16.3: der Drop braucht cf-mesh explizit — steht seit Etappe 2 als TP5-E2: mesh->LAN / LAN->mesh drin. Ohne diese zwei Zeilen sperrt Etappe 3 den Fernzugang aus, also genau den Weg, über den man den Fehler zurücknehmen wollte.

Follow-ups nebenher (blockieren nichts)

  • TP25 — WLAN-Stabilisierung (NEU 2026-08-17): Audit durch, 6 Funde (2,4 GHz fährt 40 MHz statt der profilierten 20; ap2 sendet auf 5 GHz mit 9 statt 18 dBm, weil ch149 unter ETSI leistungsbegrenzt ist; 80 MHz kostet Randabdeckung; unzulässiger Channel-Override auf w2-iot; keine access-list; 5-Minuten-Takt bei IoT-Trennungen offen). Umsetzung in 6 Schritten, alles via GC. Übernimmt den offenen Rest von TP3.3.
  • TP16.3 — cloudflared-Reduktion (NEU 2026-08-17): Recherche durch. Der Mesh ist schon der private Datenpfad (im TP5-Log mit 137 Zeilen belegt); Container braucht nur noch der öffentliche Ingress. Vorschlag: nautobot als Probe aus dem CF-Ingress nehmen.
  • DoH-Cloudflare auf crs326-core ✅ erledigt 2026-08-18 (TP23 Stufe 1 = TP21.1 TODO 1).
  • TP24 — DC-USV (seit 2026-08-16 eigener TP, aus TP15 herausgelöst).
  • TP19 — VLAN1-Client-Retirement / cudy-Teardown ✅ geschlossen 2026-08-18 (VLAN1-ARP = 0). Die zwei Reste sind dorthin verschoben, wo sie hingehören: tote CF-Tunnel → TP16, HA-Portainer setup_retry → TP11.
  • TP3.3 — WLAN-Coverage-Kur → in TP25 aufgegangen (2026-08-17). 802.11k/v ist repariert (08-13); Re-Walk + Dead-Spot-Kartierung laufen als Schritt 5 von TP25 — und zwar erst nach den Funkparameter-Fixes, weil vorher gemessene Werte wertlos sind.
  • TP10 — DDNS-Auto-Updater für bf4.emsenhuber.net (auf dem Core möglich, device-mode erlaubt scheduler).
  • TP14.4 — CF-Access-Service-Token rotieren.
  • Doku-Aufräumen (docs/doku-cleanup-session.md) — Punkte A–D am 2026-08-16 abgearbeitet, E teilweise. 📌 Läuft in einer eigenen Session — Übergabe: docs/handoff-doku-session-20260818.md. ⚠️ Solange die läuft, teilprojekte.md nicht parallel bearbeiten (in der Nacht auf den 18. viermal committet, das kollidiert sonst).

Nachgelagert / research-getrieben

TP8 (Identity/802.1X) · TP11 (Monitoring) · TP12 (IPv6) · TP13 (Botschafts-WLANs) · TP16.2 (Härtung bypass-Dienste) · TP18 (Garage-Objektspeicher) · TP20.1 (Vaultwarden → Platform-PG) · TP23 (DNS-Policy, s. Regel oben). (TP16.1 stand hier bis 2026-08-17 fälschlich als offen — es ist seit demselben Tag ✅ abgeschlossen und steht unten unter „Durch". Nachfolger ist TP16.3, das oben unter Follow-ups läuft.)

✅ Durch (Fundament — nicht mehr planen, nur nachschlagen)

TP1 · TP2 · TP2.1 · TP3 (Rebuild 2026-08-08) · TP3.1 · TP4 (Rolle seit 08-08 auf dem Core) · TP7 · TP7.2 · TP10 (Kern) · TP14 (+14.1/14.2/14.2a/14.3/14.5) · TP15 · TP16 (Kern) · TP17 · TP19.1 · TP20 · TP21 · TP21.1 · TP22 · TP-R · TP3.2 (08-17) · TP5 (08-18) · TP7.1 (08-18) · TP19 (08-18) · TP23 Stufe 1 (08-18) · TP26 (08-17) · TP27 (08-17/18) · TP28 (08-17, GC-Modell 08-18). Verworfen: TP9 (Gateway-HA, User-Entscheid 2026-08-08). Obsolet: Omada-PVID-Fix (User-Entscheid 2026-07-22 — „nie wieder aufgreifen").

📜 Historische Fassung dieses Abschnitts (bis 2026-08-14 fortgeschrieben, hier bewusst erhalten)

🎯 AKTUELLER KRITISCHER PFAD (Stand 2026-07-16, überschreibt die Reihenfolge unten): TP20 (Platform-Postgres) → TP21 (Nautobot) — Ziel: fertig vor CRS326-Produktivsetzung, spätestens Montag 2026-07-20. TP18 (Garage-S3) und die Komodo-Abhängigkeit wurden am selben Tag als Blocker entfernt/entkoppelt (s. TP20), um diese Deadline zu halten — beide werden später nachgezogen, nicht mehr vorher abgewartet. ➡️ Stand-Update 2026-08-08: TP20/TP21-Plattform steht, Core-Cutover (TP21.1) + WLAN-Rebuild (TP3) + VLAN20-Migration (TP14) sind durch. Neuer Rest-Pfad: TP22 (Device-Adoption + Compliance-Push) → TP5 (Deny-by-default) → TP6 (CAKE, das Ziel). Kleine Follow-ups davor: DoH-Cloudflare auf core, USV (TP24). ➡️ Stand-Update 2026-08-13 (Abschluss): TP22 ist DURCH (alle 5 Schritte; Fleet adoptiert, Deploy-Pipeline scharf, Datenmodell as-built, Secret-Handling entschieden) → kritischer Pfad jetzt: TP5 (Deny-by-default) → TP6 (CAKE). Kleine Follow-ups davor: DoH-Cloudflare auf core, DC-USV (TP24), Doku-Aufräumen (docs/doku-cleanup-session.md, vorbereitet 2026-08-14 — dieses Dokument beschreibt an mehreren Stellen noch die Topologie vor dem Core-Cutover). ➡️ Neu 2026-08-14: TP23 (DNS-Policy & Self-Service) hängt an TP5 — und zwar in beide Richtungen: TP23 braucht TP5s Firewall, TP5 darf aber die DNS-Bypass-Sperre erst schließen, wenn TP23s Ausnahme-Oberfläche steht (der bisherige Workaround „Client auf 1.1.1.1" ist dieser Bypass). Ebenfalls in TP5 gezogen: TP7.1 (SSDP-Reflector für die macOS-Sonos-App). Kabel core ether4→ether2 (Sideboard/TV) entfällt (2026-08-14): gelöst wurde am Port, nicht am Kabel (User-Vorgabe „Patchfeld-Nummerierung wird nicht wegen einer Config geändert") — ether4 steht seit 08-13 auf untagged VLAN40, End-to-End-Beweis am 08-14 erbracht, s. TP21.1 Punkt 4. Historisch: TP22 war das nächste Arbeitspaket — TP17 (Shelly-Migration) ist parallel dazu vollständig abgeschlossen (alle Shellys auf VLAN30, User-bestätigt). Grund für den Nautobot-First-Ansatz: Governance-Entscheid „Config exklusiv über Nautobot" (s. oben) macht Nautobot zur Voraussetzung für praktisch jede weitere Router-/Switch-Änderung. Die Punkte 3-4 unten (Omada-PVID-Fix etc.) sind obsolet (User-Entscheid 2026-07-22 — Omada spielt seit TP15-Korrektur keine Rolle mehr, nie wieder aufgreifen) — ganze Liste braucht mal eine Aufräum-Session, hier nur der aktuelle Fokus vorangestellt. ✅ TP16 (CF-Tunnel Round-Robin/macvlan-Host-Isolation → reziprokes 3-Tunnel-HA) KERN UMGESETZT 2026-07-10 (#1033 strukturell weg). Offen nur Access-Strang (WARP/vw) = user-gated. As-Built docs/cf-tunnel-mapping.md §10.

  1. TP1 (GE8) → TP2 (Omada Controller). ✅ erledigt 2026-06-15.
  2. TP4 (Router-Cutover L3-Infra + WAN-Edge). ✅ erledigt 2026-06-15 (L3) + 2026-07-01 (Edge-Cutover, FB physisch weg).
  3. Jetzt: Omada-PVID-Fix — Omada Access-Profile korrigieren (PVID 10/20, untagNetworkIds bereinigen, sicher P3/P5/P6 zuerst), dann IP-Migration NAS/macmini/pve auf VLAN-Subnetze + DNS-Einträge updaten. IP-Migration NAS/pve = TP14 (NAS ✅ 2026-07-10, pve 🔜 Session B).
  4. Dann: TP5 (Deny-by-default fertigziehen — Sonos-/HA-/mDNS-Holes + WAN-Input-Seal stehen bereits; TP5.1 = DNS-Redirect + Live-Torch für Low-Trust-Geräte/Airfryer hängt dran) → TP6 (QoS/CAKE). TP7 (mDNS) ✅ 2026-07-02 (inkl. HA/vlan20), TP3 (WLAN/CAPsMAN) ✅ 2026-06-18, TP3.1 (ap2-VLAN-Trunk) ✅ 2026-07-01, TP3.2 (P750W/Discovery) ✅ 2026-07-02, Sonos-Migration ✅ 2026-07-02 (alle 10 auf VLAN 40). ⏳ TP3.3 (WLAN-Coverage-Kur) offen — Fortsetzung neue Session (docs/wifi-p750w-coverage-and-map.md).
  5. TP10 (VPN) ✅ Kern durch (2026-06-27) + Edge-Cutover (2026-07-01) → DURCH. Offen nur: DDNS-Auto-Updater.
  6. Nachgelagert/Research-getrieben: TP8 (Access), TP9 (HA), TP11 (Monitoring + CF-Tunnel-Track-B/C), TP12 (IPv6), TP13 (Spaß).
  7. Foundation (2026-07-14): TP15 (MikroTik-Switching / Omada-WLAN, 2× CRS326 bestellt) ∥ TP20 (Platform-Postgres, single-site replication-ready) → TP21 (Nautobot Golden Config) ∥ TP20.1 (Vaultwarden→PG + 2-Site-HA). TP20 hängt an TP18 ① (Garage-Bucket für pgBackRest).

TP29 — Anwesenheitserkennung fürs Energiesparen ▸ 💡 IDEE, noch nicht auf dem kritischen Pfad (angelegt 2026-08-21)

User-Formulierung (2026-08-21): „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So kommen wir dem Ziel der Energieersparnis ohne Komfortverlust groß näher."

  • Ziel: raumgenaue Anwesenheit als Eingangssignal für Heizung und Licht — sparen, ohne den typischen Komfortverlust („erst kalt, dann warm, wenn jemand da ist").
  • 🔎 Der Entwurfsgedanke sind die komplementären Schwächen der drei Quellen — keine trägt allein:
    Quelle erkennt erkennt nicht
    BLE über die Shellys grobe Zonen, billig, überall vorhanden Menschen ohne getragenes Gerät; Raumgrenzen unscharf
    WLAN (Registration-Table am Manager: Gerät, AP, Signal) wer mit Telefon da ist, inkl. AP-Zuordnung Menschen ohne Telefon; Raum nur so genau wie die AP-Abdeckung
    mmWave-Radar auch die stillsitzende Person ohne Gerät braucht einen Sensor je Raum
  • ✅ Was schon steht: Shellys vollständig auf VLAN30 (TP17 ✅), HA produktiv — und mit docs/tp6-monitor-core.sh existiert seit TP25 bereits ein Sammler, der die WLAN-Präsenzdaten am Manager mitschreibt (Gerät → AP → Signal, im Sekundentakt).
  • ❓ Offene Fragen für die erste Session: Datenpfad ins HA (MQTT oder REST?) · Firewall-Regeln für neue Sensorik (TP5 ist deny-by-default, jeder neue Dienst über eine VLAN-Grenze braucht eine Regel) · Aufbewahrung und Privatsphäre der Ortsdaten · ob Radar-Sensoren auf dirtyvader-iot laufen oder eigene Segmentierung brauchen.
  • Abgrenzung: TP25 misst den Funk, TP29 nutzt ihn als Sensor — dieselbe Datenquelle, anderes Ziel. Die Raumkarten aus TP25 sind für TP29 direkt verwertbar: sie sagen, wie genau die WLAN-Quelle einen Raum überhaupt auflösen kann.
  • Abh.: keine harten. Profitiert von TP25 (Abdeckung/AP-Zuordnung) und TP11 (Monitoring), wenn es dahin kommt. Kein Blocker für irgendetwas.