diff --git a/.gitignore b/.gitignore index a907f3b..79f7c52 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ routeros/ap1-wg-hub.privkey routeros/tp15-core-cutover-intended.rsc docs/tp6-marker/.current-run caddy/caddy.env + +# Config-Backups, die Sessions vor destruktiven Edits anlegen (2026-08-30) +*.bak-* diff --git a/CLAUDE.md b/CLAUDE.md index b76d258..c004842 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,7 @@ Wenn während einer Session ein neues Teilprojekt, eine Abhängigkeit oder ein Q verworfenen TPs bleiben frei). - Ist-Stand-Block mitziehen (Datum, betroffene Zeilen). -**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist** — und der **Ist-Stand-Block wird bei jeder Änderung mitgezogen**, nicht nur der betroffene TP-Abschnitt. +**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt.** Ein archiviertes TP ist ein *geschlossenes Ticket, kein versiegeltes*: bei neuer Arbeit an derselben Baustelle wandert der **Volltext zurück** nach `teilprojekte.md` (Zeiger bleibt im Archiv), **kein** Zwillings-TP mit neuer Nummer (Christoph-Direktive 2026-08-30, erster Fall TP28/`ap3`) — und der **Ist-Stand-Block wird bei jeder Änderung mitgezogen**, nicht nur der betroffene TP-Abschnitt. --- @@ -61,6 +61,7 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, | **crs326-core** (CRS326, Technik UG) | **10.40.99.1** (VLAN99-SVI = L3-GW; same-subnet-Fallback `10.40.10.1`) · Identity `crs326-core` | RouterOS **7.24** · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` ✅ **live** (2026-08-16 verifiziert) · **TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router** (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (**DoH zum CF-Gateway mit `verify-doh-cert=yes`**, TP23 Stufe 1 ✅ 2026-08-18; `servers=1.1.1.1` nur noch Bootstrap) · **NTP-Server für den Standort**/NAT/WG-Hub (fm+dw live); WAN `vlan100`=Liwest `90.146.33.177` · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 | | hAP ax² #1 (ap1) | **10.40.99.11** (VLAN99) · Identity `ap1` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **TP28 ✅ 2026-08-17: reiner CAP** (Manager = `crs326-core`, `cap enabled=yes`, `discovery-interfaces=vlan99`) — Uplink `ether1` (PoE-Port, `poe-out=off`) an crs326-core `ether15`; SSIDs/Kanäle kommen vom Manager, ch36/ch1 · MAC D0:EA:11:4D:40:E0 · Config-Referenz `routeros/.secrets/ap1-prov-base.rsc` | | hAP ax² #2 (ap2) | **10.40.99.12** (VLAN99) · Identity `ap2` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **TP28 ✅ 2026-08-17: reiner CAP** — Uplink `ether1` (PoE) an crs326-branch `ether24`; ch44/ch11 (TP25: ch149 war unter ETSI leistungsbegrenzt) · MAC D0:EA:11:4D:40:3A · Config `routeros/.secrets/ap2-prov-base.rsc` | +| **cAP ax (ap3)** | **10.40.99.13** (VLAN99, Ziel) · Identity `ap3` | ⏳ **Bring-up offen (TP28-Nachtrag, 2026-08-30)** · Modell `cAPGi-5HaxD2HaxD`, Werkszustand · Uplink `ether1` → **core `ether21`** (Trunk seit 2026-08-30 ✅; Gerät hängt und hat Strom — `link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01) · Kanäle **`5180/Ce` + `2437`** (TP25-Plan 08-24) · 🔴 **nur 2 Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5` wie am hAP ax² · Dateien `routeros/tp28-ap3-bringup.rsc` + `-lifeline-abbau.rsc` | | **🔑 Einheitliches Admin-PW (2026-08-08)** | alle 4 MikroTik (core/branch/ap1/ap2) | user `admin`, gleiches PW (`~/.secrets/credentials.env` → `ROUTER_*_PASS`/`CRS326_*_PASS`; „Überlebensversicherung", User-Vorgabe). Werks-Defaults ap1/ap2 damit obsolet. | | NAS (nas-bf, DS923) | 10.40.20.10 | SSH `adm.christoph@10.40.20.10` (kein PW nötig) · Docker (Portainer endpointId=14) | | platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅ 2026-07-16: PG16, DB `nautobot`, Creds `postgres/.secrets`** | @@ -103,6 +104,19 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, ## Kritische Gotchas - 📜 **~~ALLNET-Gotchas (1 Telnet-VTY, `save` nicht vergessen)~~ — gegenstandslos seit 2026-08-08** (Gerät ausgebaut). Nur noch historisch relevant, s. `switch/README.md`. +- 🔌 **`crs326-core` hat KEIN PoE-out** (`/interface ethernet poe` existiert dort gar nicht, + CRS326-24G-2S+). Ein AP an einem Core-Port braucht eigene Stromversorgung. Der **cAP ax** bringt + dafür **Netzteil UND Gigabit-PoE-Injector im Karton** mit (Produktseite, 2026-08-30) — die + TP25-Notiz „kein Netzteil im Lieferumfang üblich" gilt für dieses Modell **nicht**. Symptom, wenn + man das übersieht: der Core-Port steht auf `no-link` und man sucht den Fehler im VLAN-Tagging. +- 🧩 **Die AP-Flotte ist seit ap3 heterogen — Literal-Listen im GC-Template sind dadurch gefährlich + geworden.** Der AP-Zweig hatte `ether3/ether4/ether5` als Bridge-Ports **hartkodiert**; das war die + stille Annahme *„AP == hAP ax² mit 5 Ports"*. Der **cAP ax hat zwei**. Seit 2026-08-30 kommt die + Liste aus **`ap.extra_ports`** (ConfigContext `ap-common`, Default = hAP-ax²-Bestand; ap3 + überschreibt sie per `local_config_context_data` mit `[]`). ⚠️ **Nautobot merged Dicts tief, Listen + ERSETZEND** — für diesen Fall genau richtig, bei `zones`/`radios` wäre es eine Falle. + Merkregel: **bevor ein zweites Modell derselben Rolle dazukommt, die Literal-Blöcke im Template + daraufhin lesen, welche Zeile eigentlich „Gerätetyp" meint und nicht „Rolle".** - 🔵 **Container auf MikroTik — korrigiert 2026-08-25, die alte Pauschalregel war nie nachgemessen.** Es hiess hier „hAP ax² hat 128 MB Flash, kein USB → Container NICHT auf dem Router". Live gelesen: **ap1/ap2 sind `arm64` mit 95 MB FREIEM Flash und 690 MB freiem RAM** — das reicht für **einen** diff --git a/docs/teilprojekte-archiv.md b/docs/teilprojekte-archiv.md index d667c84..e71e48c 100644 --- a/docs/teilprojekte-archiv.md +++ b/docs/teilprojekte-archiv.md @@ -60,7 +60,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B | [TP22](#tp22) | Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) | ✅ DURCH (2026-08-13) — Compliance heute **104/104, 0 Drifts** (live 2026-08-23) | | [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) | | [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) | -| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ✅ KOMPLETT (2026-08-18) | +| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) | | [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) | Dazu am Ende: **[TP25 (Langfassung)](#tp25-lang)** — die Messchronik und die Funde A–E im Volltext. @@ -1261,126 +1261,13 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **KOMPLETT (2026-08-18)** — mit Nachtrag: **`slaves-datapath` fehlte, IOT+MEDIA lagen ~14 h** +### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ↩️ **WIEDER GEÖFFNET 2026-08-30** -- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):** - Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath` - war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten - Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit, - **der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`, - `-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind - **statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar. - - **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):** - `/interface wifi datapath add name=dp-caps-slaves bridge=bridge` + - `/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine - `vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische - Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder. - - 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT- - Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle: - `torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**). - Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall - — der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet. - - ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations - (30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner - dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine - **frische DHCP-Lease pro SSID**, nicht die Registration-Table. - - **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:** - mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette - zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript. - - ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath` - ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager - durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP - sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs - kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s** - wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8` - `last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`. - - ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API - antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy - gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@ "/system reboot"`. - - ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in - `nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync - → Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra` - auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier - allein erreicht Nautobot nicht. - - ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem - Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das - als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln. - -- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen - jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft - (`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`). - - 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 — - je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte - sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als - Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den - defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`. - - ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP. - Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht - bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und - referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten - `configuration=`-Verweis, der beim Loeschen folgenlos blieb. - - 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos: - `/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf - `/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte - **nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt. -- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman` - (der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden, - welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt - `discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr). - **Ergebnis: 92/92 compliant, 0 Drifts.** -- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<>` - im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen). - Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die - Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht - als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck - (`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den - drei Bring-up-Dateien, die voneinander abgeschrieben sind. -- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** — - wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten. -> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett, -> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom -> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**. - -- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen - Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von - TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:** - das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite - Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen - waren die Funkparameter aus TP25. -- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations · - 4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in - TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27, - media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals - verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen, - sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können. -- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales - Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert - deshalb nicht — `monitor` bestätigt `traffic processing on CAP`. -- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.** - `/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN - einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit - vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.** -- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für - iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die - drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.** - Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**. - ✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar** - (`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert. - `wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines - sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH). -- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die - `wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die - toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs. - Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules). - *(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon - „KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session - gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern - die Punkte darunter mitziehen.**)* -- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes - Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert - nur `admin logged in/out via api`). -- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort). - Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope). +> Der Volltext steht seit dem 2026-08-30 wieder in +> [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter +> Access-Point dazukommt. **Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder +> geöffnet werden** (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur +> ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 75b1703..7d76996 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -62,6 +62,12 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d (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`. + 🆕 **Dritter AP `ap3` (cAP ax) in Arbeit seit 2026-08-30** — **TP28 dafür wieder geöffnet**. + Core-seitig fertig: **`ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`) und im + GC-Template nachgezogen, Compliance weiter **104/104 / 0 Drifts**. ap3 hängt bereits am Kabel und hat Strom (`link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01), + der Bring-up steht aber noch aus. Ziel-IP **`10.40.99.13`**, Kanäle **`5180/Ce` + `2437`** nach dem + TP25-Kanalplan vom 2026-08-24. ⚠️ **ap1s Radios gehen vorher aus** (steht bis zum Umzug neben ap3); + ap1 kehrt später auf `5260/Ce` (DFS) zurück, ap2 auf `5300/Ce`. - **SSDP-Relay** (TP7.1, **nachgebessert 08-25**): 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; mDNS macht weiterhin der Core. **Läuft seit 08-25 ohne `--ssdpUnicastAddr`** — die Flagge verwarf die M-SEARCH @@ -197,7 +203,11 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > **Alles hier hat noch offene Arbeit.** Abgeschlossenes steht im > **[Archiv](teilprojekte-archiv.md)** (Volltext, mit Inhaltsverzeichnis) — die Zuordnung ist -> mechanisch: *ein TP wandert erst dann ins Archiv, wenn nichts mehr daran zu tun ist.* Wo nur ein +> mechanisch: *ein TP wandert erst dann ins Archiv, wenn nichts mehr daran zu tun ist* — **und der +> Weg zurück ist offen: ein archiviertes TP ist ein geschlossenes Ticket, kein versiegeltes.** +> Kommt an derselben Baustelle neue Arbeit dazu, **wandert der Volltext zurück hierher** (Zeiger +> bleibt im Archiv stehen, damit alte Links halten) — statt ein Zwillings-TP mit neuer Nummer +> anzulegen. *(Christoph-Direktive 2026-08-30, erster Fall: **TP28** wegen `ap3`.)* Wo nur ein > Sub-TP offen war, steht dieses hier und das erledigte Eltern-TP im Archiv (so bei > ~~**TP17.2** ↔ TP17~~ *(TP17.2 ist am 2026-08-24 in **TP30** aufgegangen und kein eigenes TP mehr)* > und **TP20.1** ↔ TP20). @@ -223,6 +233,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP23 | ✅ | DNS-Policy (Stufe 1+2 ✅) | 2026-08-26 | > | TP24 | 🔜 | DC-USV | — | > | TP25 | ✅ | WLAN-Stabilisierung (Kern erledigt) | 2026-08-24 | +> | TP28 | ⏳ | WLAN/CAPsMAN — **wieder geöffnet**: dritter AP `ap3` (cAP ax) | Kern ✅ 2026-08-18 | > | TP29 | 💡 | Anwesenheitserkennung (Idee) | — | > | TP30 | 🔜 | Credentials (Etappe 1 ✅, Rest gebündelt) | — | > | TP31 | ✅ | Reverse Proxy Caddy (Schritt 1–4 ✅) | — | @@ -1247,6 +1258,206 @@ die Redundanzfrage oben. - **Abh.:** TP22 ✅ (GC-Deploy-Pipeline). Verwandt **TP3.2** (`docs/wifi-p750w-coverage-and-map.md`), **TP29** (nutzt dieselbe Datenquelle als Sensor). **Kein Blocker für irgendetwas.** + + +### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ⏳ **WIEDER GEÖFFNET 2026-08-30: dritter AP `ap3`** + +> **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist +> unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30, +> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP +> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder +> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".** + +- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):** + Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath` + war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten + Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit, + **der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`, + `-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind + **statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar. + - **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):** + `/interface wifi datapath add name=dp-caps-slaves bridge=bridge` + + `/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine + `vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische + Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder. + - 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT- + Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle: + `torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**). + Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall + — der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet. + - ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations + (30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner + dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine + **frische DHCP-Lease pro SSID**, nicht die Registration-Table. + - **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:** + mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette + zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript. + - ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath` + ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager + durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP + sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs + kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s** + wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8` + `last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`. + - ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API + antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy + gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@ "/system reboot"`. + - ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in + `nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync + → Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra` + auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier + allein erreicht Nautobot nicht. + - ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem + Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das + als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln. + +- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen + jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft + (`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`). + - 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 — + je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte + sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als + Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den + defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`. + - ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP. + Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht + bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und + referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten + `configuration=`-Verweis, der beim Loeschen folgenlos blieb. + - 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos: + `/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf + `/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte + **nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt. +- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman` + (der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden, + welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt + `discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr). + **Ergebnis: 92/92 compliant, 0 Drifts.** +- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<>` + im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen). + Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die + Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht + als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck + (`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den + drei Bring-up-Dateien, die voneinander abgeschrieben sind. +- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** — + wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten. +> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett, +> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom +> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**. + +- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen + Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von + TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:** + das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite + Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen + waren die Funkparameter aus TP25. +- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations · + 4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in + TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27, + media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals + verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen, + sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können. +- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales + Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert + deshalb nicht — `monitor` bestätigt `traffic processing on CAP`. +- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.** + `/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN + einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit + vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.** +- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für + iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die + drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.** + Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**. + ✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar** + (`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert. + `wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines + sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH). +- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die + `wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die + toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs. + Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules). + *(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon + „KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session + gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern + die Punkte darunter mitziehen.**)* +- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes + Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert + nur `admin logged in/out via api`). +- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort). + Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope). + + +#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30 + +**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**. +**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am +Gerät wie ap1/ap2 — **keine** DHCP-Reservierung. + +**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die +gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die +bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten"). + +**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:** + +| AP | 5 GHz | Band | DFS | 2,4 GHz | wann | +|---|---|---|---|---|---| +| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** | +| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug | +| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug | +| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — | + +🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum +Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein +**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück. + +> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der +> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile +> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat. +> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die +> Bridge-Host-Tabelle stellen.** `/interface bridge host print where interface=etherN` nennt die +> MAC des Gegenübers — hier `D0:EA:11:FF:16:9D`, exakt die `E01` vom ap3-Label. + +##### Stand 2026-08-30 (diese Session) +- ✅ Backups core+ap1 (`pre-ap3-20260830.backup`/`.rsc` auf den Geräten), Compliance-Ausgangsstand + **104/104, 0 Drifts**. +- ✅ **`core ether21` ist Trunk**: tagged in 10/20/30/40/50/90/99, Muster = `ether15` (ap1). + `pvid=1`/`frame-types=admit-all` unverändert; das VLAN1-Bein entsteht dynamisch aus dem pvid. +- ✅ **GC-Template nachgezogen und gepusht** (Commit `e25d654b`): `ether21` im Core-L2-Block, und + der AP-Zweig rendert die Bridge-Ports jetzt aus **`ap.extra_ports`** statt hartkodiert + `ether3/4/5`. **Compliance danach wieder 104/104, 0 Drifts** — das beweist, dass ap1/ap2 + zeichengleich rendern. +- ✅ Bring-up-Dateien geschrieben: [`routeros/tp28-ap3-bringup.rsc`](../routeros/tp28-ap3-bringup.rsc) + + [`routeros/tp28-ap3-lifeline-abbau.rsc`](../routeros/tp28-ap3-lifeline-abbau.rsc). +- 🔜 **Offen:** physischer Bring-up (Strom + `/import`), ap1-Radios aus, CAPsMAN-Objekte für ap3, + Nautobot-Adoption, PW-Rotation, MikroMCP-Registry, Messrunde. + +##### Was am cAP ax anders ist als am hAP ax² — und was daran wehtut +- 🔴 **Nur ZWEI Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5`. Der + GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*. + Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`). + **Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.** +- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax + bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz + „kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu. +- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus, + wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port + (VLAN1 bleibt leer, TP19 ist geschlossen). +- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity + + Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`, + das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24). + +##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen) +1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit + genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und + hat keinen zweiten → 5 GHz dieses APs bleibt unten. +2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf + `5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt, + 9 dBm gesendet. +3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen, + der Port ist untagged-Member von VLAN40 bei `pvid=1`. + + ### 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." @@ -1996,7 +2207,8 @@ den Weg, über den man den Fehler zurücknehmen wollte. diese Datei ist von **2217 auf ~760 Zeilen**. Die Regel dahinter bleibt gültig: ⚠️ **während einer Doku-Session `teilprojekte.md` nicht parallel bearbeiten** (in der Nacht auf den 18. viermal committet, das kollidiert sonst) — und **ein TP wandert erst dann ins Archiv, wenn - nichts mehr daran zu tun ist.** + nichts mehr daran zu tun ist.** *(Ergänzt 2026-08-30: und **zurück**, wenn doch wieder etwas + zu tun ist — das Archiv ist eine Ablage, kein Endlager.)* ### Nachgelagert / research-getrieben diff --git a/mcp/INVENTORY.md b/mcp/INVENTORY.md index 3069263..bc1de35 100644 --- a/mcp/INVENTORY.md +++ b/mcp/INVENTORY.md @@ -39,7 +39,7 @@ | MCP | Zweck / Geräte | Host | Transport / Endpoint | Image | Config | Status | |---|---|---|---|---|---|---| -| **MikroMCP** (AliKarami) | MikroTik hAP ax² ap1/ap2 (RouterOS 7) — WLAN/CAPsMAN, Router-Cutover | **jetzt: lokal (dieser Mac)** stdio / Ziel: HTTP-Container auf Mac mini `10.40.20.167` | jetzt **stdio** (`npx mikromcp serve`) / Ziel HTTP `:3000` | `ghcr.io/alikarami/mikromcp:latest` (HTTP) bzw. npm `mikromcp` (stdio) | `mcp/mikromcp/` + opencode/Claude-Code user-config | 🟢 **live (stdio), doctor 12/12, in opencode ✓Connected** | +| **MikroMCP** (AliKarami) | MikroTik-Flotte: `crs326-core` `.99.1` · `crs326-branch` `.99.2` · ap1 `.99.11` · ap2 `.99.12` · **🆕 ap3 `.99.13` (cAP ax, 2026-08-30, Bring-up offen)** — alle RouterOS **7.24** | **jetzt: lokal (dieser Mac)** stdio / Ziel: HTTP-Container auf Mac mini `10.40.20.167` | jetzt **stdio** (`npx mikromcp serve`) / Ziel HTTP `:3000` | `ghcr.io/alikarami/mikromcp:latest` (HTTP) bzw. npm `mikromcp` (stdio) | **Registry = `bf-infra/mcp/mikromcp/config/routers.yaml`** (seit 2026-08-30 SSoT) + opencode/Claude-Code user-config | 🟢 **live (stdio), doctor 12/12, in opencode ✓Connected.** 🔴 **Zwei Drifts am 2026-08-30 gefunden und behoben:** (a) `MIKROMCP_CONFIG_PATH` in `~/.claude.json` zeigte auf **`~/dev/bf-network-r6`** (Archiv-Repo) — die identische Datei in `bf-infra` war **wirkungslos**; (b) in `opencode.jsonc` fehlte `MIKROMCP_CONFIG_PATH` **ganz** und es waren nur `ROUTER_AP1/AP2`-Creds hinterlegt → opencode kannte core/branch gar nicht. Beide Clients zeigen jetzt auf dieselbe Registry, `rosVersion` von stale 7.23/7.19.5 auf 7.24 korrigiert. ⚠️ **Neustart des MCP nötig** (Registry wird nur beim Start gelesen). ⚠️ opencode nutzt Env-Indirektion `{env:MCP_MIKROMCP_*}`, `~/.claude.json` **Klartext** — für ap3/core/branch fehlen noch `MCP_MIKROMCP_ROUTER_AP3_PASS`, `MCP_MIKROMCP_CRS326_CORE_PASS`, `MCP_MIKROMCP_CRS326_BRANCH_PASS` in `~/.secrets/exports.zsh` (TP30)** | | **gitea-mcp** (eigenbau) | Gitea REST API (Repo-Verwaltung, Issues, Wiki, …) | **Mac mini** `10.40.20.167` (Portainer-Endpoint 20) — Docker-Bridge-IP `172.17.0.3:3000` — ⚠️ **sollte auf DS923 umziehen** (Performance-Restriktion: macmini ist User-Workstation, kein zentraler MCP-Host) | **StreamableHTTP** `https://gitea-mcp.emsenhuber.net/mcp` (Caddy + CF) bzw. `http://10.40.20.167:3000/mcp` (LAN-direkt) | `docker.io/node:22-alpine` (eigener Build) | Repo `mcp/` hatte **kein** Config-Verzeichnis — Container-Definition lebt auf macmini | 🟢 **live (seit unknown, gefunden 2026-08-28), war aus dem Inventar verschwunden. serverInfo: gitea-mcp 0.0.10. Umzug auf DS923 ausstehend (Performance-Restriktion)** | | **n8n-mcp** (czlonkowski) | n8n-public-API (Workflows, Credentials, Executions) | **Mac mini** `10.40.20.167` (Portainer-Endpoint 20, **n8n-Host** — n8n `:5678`, n8n-redis `:6379`, n8n-mcp `:3001`) — Docker-Bridge-IP `172.19.0.4` → PublicPort `3001:3000` | **StreamableHTTP** `https://n8n-mcp.emsenhuber.net/mcp` (Caddy + CF) bzw. `http://10.40.20.167:3001/mcp` (LAN-direkt) | `ghcr.io/czlonkowski/n8n-mcp:latest` | keine im Repo (`mcp.json.example` in `blaueblueten-n8n/` ist alt, stdio-Modus) | 🟢 **live (gefunden 2026-08-28, war **nicht** im Inventar). FQDN → 401 ohne Bearer (Auth-Sanierung TP16.3). LAN-direkt :3001 antwortet korrekt mit 401 = Bearer fehlt, Server lebt. Bleibt auf macmini (zusammen mit n8n — n8n-Workflows lösen Tool-Calls aus, lokale Performance-Kopplung gewollt)** | | **shelly-mcp** (eigenbau, FastMCP) | ~26 Shellys Gen3/4 — VLAN1→VLAN30-Migration (TP17) + Verwaltung | **geplant:** DS923 (`10.40.20.10`), macvlan-IP **`10.40.20.180`** | **StreamableHTTP** `http://10.40.20.180:3030/mcp` (geplant) | eigenbau `bf/shelly-mcp:0.1` (DS923-lokal gebaut aus `mcp/shelly/Dockerfile`) | `mcp/shelly/` ✓ (`Dockerfile` + `BUILD.md` + `requirements.txt`) · Stack gitignored in `docs/nas-vlan20/stacks/shelly-mcp.yml` | 🟡 **staged — Image-Build + Stack-Deploy auf DS923 ausstehend. Vorher-Stand war localhost `127.0.0.1:3030` mit `enabled=false` (Handoff 2026-08-28). Live-Verifikation 23 Shellys auf `10.40.30.180-250` (VLAN30) bestätigt 2026-08-28** | diff --git a/mcp/mikromcp/config/routers.yaml b/mcp/mikromcp/config/routers.yaml index c3f4fda..74c5354 100644 --- a/mcp/mikromcp/config/routers.yaml +++ b/mcp/mikromcp/config/routers.yaml @@ -7,6 +7,10 @@ # TP21.1-Stand (2026-08-08): crs326-core IST der L3-Router (Routing/DHCP/DNS/NAT/WG-Hub live). # core mgmt = 10.40.99.1 (vlan99-SVI, aus VLAN10-Client via core geroutet erreichbar; # Same-Subnet-Fallback = 10.40.10.1). branch = 10.40.99.2 (L2, .10.2 temp). +# 2026-08-30 (TP28-Nachtrag): ap3 (cAP ax, .99.13) aufgenommen, Uplink core ether21. +# Diese Datei ist seit 2026-08-30 die EINZIGE gelesene Registry: MIKROMCP_CONFIG_PATH +# zeigte bis dahin auf ~/dev/bf-network-r6 (Archiv-Repo) -- Aenderungen hier waren +# wirkungslos. rosVersion war auf allen Eintraegen stale (7.23/7.19.5), live ist 7.24. # ap1/ap2 (.99.11/.99.12) = Zielwerte — ap1 ist derzeit Werksreset/getrennt und kommt # erst mit der WLAN-Neuprovisionierung (eigene Session) zurück. @@ -21,7 +25,7 @@ routers: source: "env" envPrefix: "ROUTER_AP1" tags: ["bf", "wifi", "access-point"] - rosVersion: "7.23" + rosVersion: "7.24" ap2: host: "10.40.99.12" @@ -33,7 +37,7 @@ routers: source: "env" envPrefix: "ROUTER_AP2" tags: ["bf", "wifi", "access-point"] - rosVersion: "7.23" + rosVersion: "7.24" crs326-core: host: "10.40.99.1" @@ -45,7 +49,7 @@ routers: source: "env" envPrefix: "CRS326_CORE" tags: ["bf", "switch", "core", "l3-router"] - rosVersion: "7.19.5" + rosVersion: "7.24" crs326-branch: host: "10.40.99.2" @@ -57,4 +61,16 @@ routers: source: "env" envPrefix: "CRS326_BRANCH" tags: ["bf", "switch", "branch"] - rosVersion: "7.19.5" + rosVersion: "7.24" + + ap3: + host: "10.40.99.13" + port: 80 + tls: + enabled: false + rejectUnauthorized: true + credentials: + source: "env" + envPrefix: "ROUTER_AP3" + tags: ["bf", "wifi", "access-point"] + rosVersion: "7.24" diff --git a/nautobot/ingest-ap-configcontext.py b/nautobot/ingest-ap-configcontext.py index 9c9732d..6655e72 100644 --- a/nautobot/ingest-ap-configcontext.py +++ b/nautobot/ingest-ap-configcontext.py @@ -51,6 +51,13 @@ AP_COMMON = { "country": "Austria", "bridge": "bridge", "uplink": "ether1", # PoE-Port, poe-out=off + # Bridge-Ports AUSSER Uplink und Radios. Bis 2026-08-30 standen ether3/4/5 + # hartkodiert im Template — das galt still fuer "AP == hAP ax2 mit 5 Ports". + # ap3 ist ein cAP ax mit ZWEI Ports (ether1 PoE-in, ether2 PoE-out); dort ist + # die Liste leer. Default hier = hAP-ax2-Bestand, ap3 ueberschreibt per + # local_config_context_data. (Nautobot merged Dicts tief, LISTEN ERSETZEND — + # genau das ist hier gewollt.) + "extra_ports": ["ether3", "ether4", "ether5"], "mgmt": {"vlan": 99, "gateway": "10.40.99.1"}, "native_vlan": 1, # Liste (nicht Dict): Reihenfolge ist garantiert und diffbar. diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2 index 5cb42d2..9862ba3 100644 --- a/nautobot/mikrotik_routeros.j2 +++ b/nautobot/mikrotik_routeros.j2 @@ -83,13 +83,16 @@ # ether13 (pve) traegt VLAN10 + VLAN40 seit 2026-08-18: der SSDP-Relay-LXC fuer die # macOS-Sonos-App braucht ein Bein in beiden (TP7.1). SSDP ist link-lokal mit TTL 1 — # ein Relay muss Mitglied beider Broadcast-Domaenen sein, Routing hilft nicht. -/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15 vlan-ids=10 -/interface bridge vlan add bridge=bridge tagged=ether8,ether13,ether15 vlan-ids=20 -/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 untagged=ether14 vlan-ids=30 -/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15 untagged=ether2,ether3,ether4,ether5 vlan-ids=40 -/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=50 -/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=90 -/interface bridge vlan add bridge=bridge tagged=ether8,ether15,bridge vlan-ids=99 +# ether21 = ap3 (cAP ax, Technik UG) seit 2026-08-30, TP28-Nachtrag: gleicher AP-Trunk +# wie ether15/ap1. Der Port traegt pvid=1 + frame-types=admit-all wie ether15 -- das +# VLAN1-untagged-Bein entsteht dynamisch aus dem pvid und steht deshalb nicht hier. +/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15,ether21 vlan-ids=10 +/interface bridge vlan add bridge=bridge tagged=ether8,ether13,ether15,ether21 vlan-ids=20 +/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 untagged=ether14 vlan-ids=30 +/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether13,ether15,ether21 untagged=ether2,ether3,ether4,ether5 vlan-ids=40 +/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 vlan-ids=50 +/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15,ether21 vlan-ids=90 +/interface bridge vlan add bridge=bridge tagged=ether8,ether15,ether21,bridge vlan-ids=99 /interface bridge vlan add bridge=bridge tagged=ether1 untagged=ether16 vlan-ids=100 # [1] INTERFACE-LISTS @@ -450,9 +453,12 @@ # [4b] BRIDGE-PORTS — ether2 fehlt bewusst: seit der Uplink-Umstellung auf ether1 (PoE) # ist er KEIN Bridge-Port mehr. ether1 traegt kein comment=defconf (beim Rebuild # neu angelegt), die uebrigen schon — deshalb hier nicht ueber einen Kamm geschoren. -/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether3 -/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether4 -/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether5 +# Die Liste kommt seit 2026-08-30 aus `ap.extra_ports` statt hartkodiert: ap1/ap2 +# sind hAP ax2 mit ether3/4/5, ap3 ist ein cAP ax mit nur zwei Ports (Liste leer). +# Die alte Hartkodierung war die stille Annahme "AP == hAP ax2". +{% for p in ap.extra_ports %} +/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface={{ p }} +{% endfor %} {% for r in ap.radios %} /interface bridge port add bridge={{ ap.bridge }} comment=defconf interface={{ r.iface }} {% endfor %} diff --git a/opencode.jsonc b/opencode.jsonc index 0acf540..bcdb3c8 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -5,7 +5,7 @@ "mcp": { "gitea-mcp": { "type": "remote", - "url": "https://gitea-mcp.emsenhuber.net/mcp", + "url": "http://10.40.20.167:3000/mcp", "enabled": true }, "mikromcp": { diff --git a/routeros/tp28-ap3-bringup.rsc b/routeros/tp28-ap3-bringup.rsc new file mode 100644 index 0000000..eecf344 --- /dev/null +++ b/routeros/tp28-ap3-bringup.rsc @@ -0,0 +1,109 @@ +# ===================================================================================== +# ap3 — Bring-up als reiner CAP unter dem CAPsMAN-Manager crs326-core. +# TP28-Nachtrag, 2026-08-30. Geraet: MikroTik cAP ax (cAPGi-5HaxD2HaxD), Werkszustand. +# +# WEG: Laptop an ap3 ether2 (cAP ax HAT einen zweiten Port), Datei per scp hoch, +# dann `/import file=tp28-ap3-bringup.rsc`. ether1 dabei NOCH NICHT am Netz. +# Danach ether1 -> crs326-core ether21 (ist seit 2026-08-30 Trunk). +# +# 🔴 ether2 bleibt in DIESER Datei als LIFELINE im Bridge (untagged VLAN1), damit die +# Sitzung das abschliessende `vlan-filtering=yes` ueberlebt. Der Abbau ist eine +# EIGENE Datei (tp28-ap3-lifeline-abbau.rsc) und laeuft ERST, wenn ap3 ueber +# 10.40.99.13 erreichbar ist. Zielzustand = wie ap2: ether2 kein Bridge-Port. +# +# ⚠️ NICHT von routeros/.secrets/ap*-prov-base.rsc abgeleitet — die sind als +# historisch/nicht-deploybar markiert und tragen veraltete PSKs. Vorlage ist der +# LIVE-Stand von ap2 (2026-08-30 gelesen). +# +# DELTAS ZU ap1/ap2 (hAP ax2): cAP ax hat nur ZWEI Ethernet-Ports (ether1 PoE-in, +# ether2 PoE-out) — es gibt kein ether3/4/5. Der GC-Template-AP-Zweig hat die +# hartkodiert; dort kommt `ap.extra_ports` dazu (fuer ap3 = leere Liste). +# ===================================================================================== + +/system identity set name=ap3 + +# --- L3/WAN-Reste weg. Ein Werks-cAP bringt DHCP-Client/Server + NAT mit; beides hat +# im BF-Netz nichts verloren (der Core ist DHCP- und NAT-Instanz). +/ip dhcp-client remove [find] +/ip dhcp-server remove [find] +/ip dhcp-server network remove [find] +/ip pool remove [find] +/ip firewall nat remove [find] +/ipv6 dhcp-client remove [find] + +# --- Interface-Lists: ether1 ist Uplink, NICHT WAN. +:if ([:len [/interface list find where name=LAN]]=0) do={ /interface list add name=LAN } +:if ([:len [/interface list find where name=WAN]]=0) do={ /interface list add name=WAN } +/interface list member remove [find where interface=ether1] + +# --- Bridge sicherstellen, VLAN-Filter waehrend des Umbaus AUS. +:if ([:len [/interface bridge find where name=bridge]]=0) do={ /interface bridge add name=bridge } +/interface bridge set [find name=bridge] vlan-filtering=no +# ether2 traegt PoE-out. Nichts haengt dran -> aus, wie ap1/ap2 es mit ihrem PoE-Port halten. +/interface ethernet set [find name=ether2] poe-out=off + +# --- Werks-WLAN abraeumen. REIHENFOLGE IST PFLICHT: `configuration` referenziert +# security/datapath/channel; wer die Referenzierten zuerst loescht, bekommt einen +# Fehler und laesst einen Teil-Zustand zurueck (Lehre aus tp28-ap-wifi-reste-abraeumen.rsc). +# `[find !dynamic]` waere hier ein SYNTAXFEHLER — diese Objekte haben gar keine +# dynamic-Eigenschaft. Richtig ist schlicht `[find]`. +/interface wifi capsman set enabled=no +/interface wifi remove [find where !(name=wifi1 || name=wifi2)] +/interface wifi configuration remove [find] +/interface wifi steering remove [find] +/interface wifi security remove [find] +/interface wifi datapath remove [find] +/interface wifi channel remove [find] +# Werks-Inline-Overrides auf den Master-Radios loeschen, sonst gewinnen sie gegen den Manager. +/interface wifi set [find name=wifi1] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds +/interface wifi set [find name=wifi2] !configuration.ssid !security.authentication-types !security.passphrase !security.ft-over-ds + +# --- Bridge-Ports idempotent. ether2 = Lifeline (faellt spaeter weg). +:if ([:len [/interface bridge port find where interface=ether1]]=0) do={ /interface bridge port add bridge=bridge interface=ether1 } +:if ([:len [/interface bridge port find where interface=ether2]]=0) do={ /interface bridge port add bridge=bridge interface=ether2 } +:if ([:len [/interface bridge port find where interface=wifi1]]=0) do={ /interface bridge port add bridge=bridge comment=defconf interface=wifi1 } +:if ([:len [/interface bridge port find where interface=wifi2]]=0) do={ /interface bridge port add bridge=bridge comment=defconf interface=wifi2 } + +# --- Bridge-VLAN-Tabelle. Muster = ap2 live, plus ether2 in VLAN1 (Lifeline). +# IOT (30) nur auf wifi2: die Zone hat bands=["2g"] (ConfigContext ap-common). +# MGMT (99) liegt auf der Bridge, NICHT auf den Radios — sonst waere Management +# over-the-air. +/interface bridge vlan remove [find] +/interface bridge vlan add bridge=bridge untagged=bridge,ether1,ether2 vlan-ids=1 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=10 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=20 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi2 vlan-ids=30 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=40 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=50 +/interface bridge vlan add bridge=bridge tagged=ether1,wifi1,wifi2 vlan-ids=90 +/interface bridge vlan add bridge=bridge tagged=ether1,bridge vlan-ids=99 + +# --- Mgmt-Layer. IP nach TP32-Konvention (.10-.29 = physische Hosts): ap1 .11, ap2 .12, ap3 .13. +:if ([:len [/interface vlan find where name=vlan99]]=0) do={ /interface vlan add name=vlan99 interface=bridge vlan-id=99 } +/ip address remove [find where interface=vlan99] +/ip address add address=10.40.99.13/24 interface=vlan99 +:if ([:len [/interface list member find where list=LAN and interface=vlan99]]=0) do={ /interface list member add list=LAN interface=vlan99 } +:if ([:len [/interface list member find where list=LAN and interface=bridge]]=0) do={ /interface list member add list=LAN comment=defconf interface=bridge } +/ip route remove [find where comment="mgmt default via core"] +/ip route add dst-address=0.0.0.0/0 gateway=10.40.99.1 comment="mgmt default via core" +/ip dns set allow-remote-requests=yes servers=10.40.99.1 +# Zeitquelle ist der Core (seit 2026-08-18). Ohne das war ap2 einmal 17 h versetzt und +# seine Logs fuer jede geraeteuebergreifende Korrelation wertlos. +/system ntp client set enabled=yes +/system ntp client servers remove [find] +/system ntp client servers add address=10.40.99.1 + +# --- CAP-Modus. 🔴🔴 `slaves-datapath` IST NICHT OPTIONAL. +# Der Manager schickt `datapath.bridge=bridge` zwar mit, der CAP uebernimmt davon +# aber NUR die vlan-id — die Bridge-Mitgliedschaft bleibt lokal. Fehlt das Objekt, +# assoziieren Clients auf iot/media/untrusted/guest sauber und bekommen NIE DHCP, +# waehrend dirtyvader-main (Master-Radios = statische Bridge-Ports) weiterlaeuft. +# Genau so lagen IOT+MEDIA am 2026-08-18 rund 14 Stunden. KEINE vlan-id hier — +# die liefert der Manager pro VAP. +/interface wifi datapath add name=dp-caps-slaves bridge=bridge comment="TP28-fix: bridge fuer CAPsMAN-Slave-VAPs" +/interface wifi cap set enabled=yes discovery-interfaces=vlan99 slaves-datapath=dp-caps-slaves + +# --- ZULETZT: Bridge VLAN-scharf schalten. Vorher waere der Rest der Datei nicht angekommen. +/interface bridge set [find name=bridge] vlan-filtering=yes + +:log info "tp28-ap3: bring-up fertig, ip=10.40.99.13 cap=yes slaves-datapath=dp-caps-slaves" diff --git a/routeros/tp28-ap3-lifeline-abbau.rsc b/routeros/tp28-ap3-lifeline-abbau.rsc new file mode 100644 index 0000000..0c236e5 --- /dev/null +++ b/routeros/tp28-ap3-lifeline-abbau.rsc @@ -0,0 +1,10 @@ +# ap3 — Lifeline-Abbau. ERST AUSFUEHREN, wenn ap3 ueber 10.40.99.13 (via ether1 am +# core ether21) erreichbar ist und `remote-cap print` am Core `ap3 state=Ok` zeigt. +# Vorher gefahren = Aussperrung. +# +# Zielzustand danach = wie ap2: ether2 ist KEIN Bridge-Port. Damit bleibt VLAN1 leer +# (TP19 ist geschlossen) und ap3s Bridge-Port-Liste ist genau ether1+wifi1+wifi2 +# -> `ap.extra_ports = []` im Nautobot-ConfigContext. +/interface bridge vlan set [find where vlan-ids=1] untagged=bridge,ether1 +/interface bridge port remove [find where interface=ether2] +:log info ("tp28-ap3: lifeline ab, bridge-ports=" . [:tostr [:len [/interface bridge port find]]])