Compare commits
17 Commits
tp45-schem
...
c0c63882e4
| Author | SHA1 | Date | |
|---|---|---|---|
| c0c63882e4 | |||
| 230f052a01 | |||
| 39e19e7f0f | |||
| be51f12ab9 | |||
| 769d0ac6b3 | |||
| 4a63c1e527 | |||
| 52a469185b | |||
| 2f3d44d4be | |||
| fe11ae2a5c | |||
| 7b365e1586 | |||
| 6d1d56e463 | |||
| 4720b5d223 | |||
| 3f774b7b17 | |||
| 180c5b6c57 | |||
| 7b03e01fab | |||
| 4e61c63ae7 | |||
| 08bbe1830f |
416
CLAUDE.md
416
CLAUDE.md
@@ -1,50 +1,16 @@
|
||||
# BF-Netzwerk — Projekt-Kontext (auto-read)
|
||||
|
||||
**Standort BF:** VLAN-segmentiertes Heimnetz auf MikroTik/RouterOS. Ziel: CAKE/QoS gegen Bufferbloat,
|
||||
DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der Standalone-Entscheid von
|
||||
2026-07-22 ist revidiert), Firewall Least-Privilege (**TP5 deny-by-default scharf seit 2026-08-18**), Multi-Site VPN.
|
||||
DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17), Firewall Least-Privilege
|
||||
(**TP5 deny-by-default scharf seit 2026-08-18**), Multi-Site VPN.
|
||||
|
||||
## Pflichtlektüre pro Sitzung (Reihenfolge)
|
||||
|
||||
0. 🔴 **TP45 = aktuell dringendster Posten (2026-09-21, Gitea #71):** interne `*.bf.emsenhuber.net`
|
||||
lösen im Browser **nicht** auf (AGH wertet Rewrites im **Filter-Pfad** aus → für unfiltrierte
|
||||
Clients übersprungen → Fallback auf den Core, der nur 10 von 26 Namen hält), und `ds-bf` ist
|
||||
**öffentlich kaputt** (einziger Ingress-Eintrag, der Caddy umgeht → TP5-Drop). Reihenfolge:
|
||||
VLAN20 ins TP32-Schema → VLAN30-Pool-Grenze → DNS/FQDN. Ausführlich: `### TP45`.
|
||||
**`### TP46`** = CF-/Tunnel-SSoT + Recovery (eigene Baustelle).
|
||||
1. **Status/Index = Gitea-Board** (seit TP36-Cutover 2026-09-07): [`admin/bf-infra` · Issues](https://gitea.emsenhuber.net/admin/bf-infra/issues) — TP = Issue, Status = Label `status:*`, Gruppierung = `thema:*`. Für den **Detail/Kontext** eines TP dann den `### TPnn`-Prosa-Abschnitt in **`docs/teilprojekte.md`** (aktiv) bzw. **`docs/teilprojekte-archiv.md`** lesen. *(Die frühere „Quick-Index-Block, 1 Read reicht"-Zeile ist überholt — Index/Status stehen nicht mehr in der Datei.)*
|
||||
1. **Status/Index = Gitea-Board** (seit TP36-Cutover 2026-09-07): [`admin/bf-infra` · Issues](https://gitea.emsenhuber.net/admin/bf-infra/issues) — TP = Issue, Status = Label `status:*`, Gruppierung = `thema:*`. Detail/Kontext eines TP → `### TPnn` in **`docs/teilprojekte.md`** (aktiv) bzw. **`docs/teilprojekte-archiv.md`**.
|
||||
2. **`docs/handoff-*.md`** (neueste) — wo die letzte Session aufgehört hat.
|
||||
3. Bei Live-Writes oder Inventur: **`docs/cf-tunnel-mapping.md`**, **`docs/teilprojekte-archiv.md`**.
|
||||
4. ✅ **TP16.3 §5 durch (2026-09-01):** [`docs/handoff-20260901-tp16.3-paragraph5-flip-durch.md`](docs/handoff-20260901-tp16.3-paragraph5-flip-durch.md)
|
||||
— alle vier oeffentlichen Namen laufen ueber `bf-public`/Caddy; Alttunnel bleiben als Rueckfallebene
|
||||
(§6 offen). Enthaelt den 10-Sekunden-DNS-Fund und drei Messfallen.
|
||||
5. ✅ **WLAN/Fund E — GESCHLOSSEN 2026-09-02: 7.24.1 war die Ursache, nicht `ft-over-ds`:**
|
||||
[`docs/handoff-20260902-tp25-fund-e-messung.md`](docs/handoff-20260902-tp25-fund-e-messung.md)
|
||||
— die Abschlussmessung ergab am 09-02 nur **4 `reauthenticating` in 8,6 h** (alle iPhone `F2:62`,
|
||||
isolierte Einzel, 0 Ausfaelle), Verlauf **218 → 40 → 4**; Vorbedingung geprueft (Core-Uptime 1d18h,
|
||||
`caps joined=0`). Hebel 1 (`ft-over-ds=no`) war widerlegt (6,4 h allein → 124× + 2 Ausfaelle) und ist
|
||||
am 09-01 zurueckgebaut; Hebel 2–4 entfallen, **kein Geraete-Write** (Soll stand: `ft=yes ft-over-ds=yes`,
|
||||
**Compliance 140/140**). **Am 2026-09-04 hat Christoph `ft=no` auf `sec-main` als Dauerzustand
|
||||
entschieden** (Hebel 2 doch gezogen: Template + bf-golden-config + Nautobot-Sync, `wifi-security` gruen).
|
||||
⚠️ **Rueckgenommen am 2026-09-14 (Terrasse-In-situ-Session): `ft=yes ft-over-ds=yes` zurueck** — mit
|
||||
`ft=no` war jeder Roam eine Voll-Reassoziation, darin starben 2 Anrufe; Rueckkehrstand = Fund-E-Known-Good
|
||||
von 09-02 (ruhig auf 7.24.1). Weg: Template + `push-gc-template.py` + `gc-configplan.py
|
||||
--refresh-compliance` + `gc-manual-plan.py --deploy` (Plan `b1135f3e`, Completed) → **140/140, 0 Drifts**;
|
||||
CAP-Abnahme: **keine** Inline-Overrides auf ap1/ap2/ap3. Commands:
|
||||
`docs/transaktionen/TP25-ft-yes-secmain.commands.rsc`, s. Handoff 2026-09-14.
|
||||
🔴 **Nachtrag 09-04: `ft=no` war NICHT uniform.** ap3 trug einen Werks-Inline `security.ft=yes` auf
|
||||
`wifi1/wifi2` (Bring-up unset `!ft-over-ds`, aber nicht `!ft`; Abnahme zählt nur benannte
|
||||
security-Objekte → Inline-Wert unsichtbar). Gemischtes FT-ESS → Strong-Signal-Abbrüche (−43/−45 dBm)
|
||||
**nur auf ap3** bei 3/3 Strichen. Fix: `!security.ft` auf ap3 (uniform `ft=no`), Quelle
|
||||
`tp28-ap3-phase2-cap.rsc` korrigiert. **Merkregel: nach jeder `sec-main`-FT-Änderung jeden CAP einzeln
|
||||
`get wifiN security.ft` prüfen — CAP-lokale Inline-Overrides sind compliance-unsichtbar.**
|
||||
Der TP25-Kanalplan ist am 09-04 committed (DFS-Frequenzlisten + ap1-Provisioning, **140/140, 0 Drifts**).
|
||||
Offen in TP25 bleibt nur **ap2 unterperformt** (2 m/−80 dBm). Vorstufen:
|
||||
[Befund](docs/handoff-20260831-tp27-upgrade-und-hebel1-befund.md) ·
|
||||
[Eingriff](docs/handoff-20260831-fund-e-hebel1-gezogen.md) ·
|
||||
[Rueckbau](docs/handoff-20260901-fund-e-hebel1-zurueckgebaut.md).
|
||||
6. ap3-Bring-up 2026-08-30: [`docs/handoff-20260830-ap3.md`](docs/handoff-20260830-ap3.md) — sechs Funde zu Werks-MikroTiks, CAPsMAN und Golden-Config-Lücken.
|
||||
7. Inventur-Befund 2026-08-27: [`docs/handoff-20260827-inventur-und-architektur.md`](docs/handoff-20260827-inventur-und-architektur.md) — Master-Struktur, Asymmetrie, Drift.
|
||||
3. Bei Live-Writes/Inventur: **`docs/cf-tunnel-mapping.md`**, **`docs/gotchas.md`** (ausführliche Diagnose-Fallen), **`docs/teilprojekte-archiv.md`**.
|
||||
|
||||
**Aktueller Fokus:** TP45 (IP-Schichten A/B/E, C4→**TP34.2**), TP34.2 (interne DNS-Namen als Daten, Gitea #73) und TP46 (CF-/Tunnel-SSoT) offen — Status im Gitea-Issue, Detail in den `### TPnn`-Abschnitten von `teilprojekte.md`.
|
||||
|
||||
## SSoT & Branch-Konvention (TP36-Cutover 2026-09-07)
|
||||
|
||||
@@ -63,15 +29,15 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der
|
||||
|
||||
## Was du sofort wissen musst (Inventur 2026-08-27)
|
||||
|
||||
- **`bf-infra`** ist aktiver Master **mit Gitea-Remote**: `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git` (live verifiziert 2026-08-30). ⚠️ **Die frühere Zeile „lokal, kein Gitea-Remote → verloren bei Festplattencrash" ist überholt** — sie stammt aus der Inventur vom 2026-08-27 und war damals richtig; das Remote existiert seit spätestens 2026-08-28 (`origin/main` = `b917691`). **Die Backup-Lücke ist damit keine Struktur-, sondern eine Disziplinfrage: `git push`.** Am 2026-08-30 lagen vier Commits nur lokal. 🔴 **Das Remote ist nur so viel wert wie der letzte Push** — der Backup-Guard (`~/dev/.bin/backup-guard.sh`, TP35 Schicht 5) bricht Commits ab, solange kein Backup markiert ist; **das ist die Erinnerung ans Pushen, kein Hindernis.** Nicht mit `--no-verify` umgehen. 🔴 **Merkregel:** diese Zeile hat eine Session dazu gebracht, „kein Remote" zu behaupten, statt `git remote -v` zu tippen — vor jeder Aussage über Remotes/Backups **live prüfen**.
|
||||
- **`admin/bf-network-r6`** (Gitea) ist Archiv, letzte aktive Commits 2026-08-25 → read-only-Header.
|
||||
- **`admin/bf-golden-config`** (Gitea) ist Bot-Master, nicht manuell. 🔴 **Korrigiert 2026-09-12:** die frühere Angabe „2×/Tag" war nie real — es gab **keinen** Scheduler, Backup/Intended/Compliance liefen nur manuell aus Sessions (zuletzt 09-04, 8 Tage Stillstand). Jetzt **täglich 03:30 UTC** per ScheduledJob **„GC Backup+Compliance – taeglich"** (Nautobot-Job *Execute All Golden Configuration Jobs - Multiple Device*, Scope `platform=RouterOS`, Reihenfolge Backup→Intended→Compliance). Stand prüfen: `backup_last_success_date` am Gerät bzw. `scheduled-jobs` in Nautobot.
|
||||
- **`bf-infra`** = aktiver Master mit Gitea-Remote: `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git`. **Das Remote ist nur so viel wert wie der letzte Push** — Backup ist eine Disziplinfrage: `git push`. Der Backup-Guard (`~/dev/.bin/backup-guard.sh`, TP35 Schicht 5) bricht Commits ab, solange kein Backup markiert ist — **das ist die Erinnerung ans Pushen, kein Hindernis.** Nicht mit `--no-verify` umgehen. 🔴 **Vor jeder Aussage über Remotes/Backups `git remote -v` live prüfen** (eine Session hat „kein Remote" behauptet, statt zu tippen).
|
||||
- **`admin/bf-network-r6`** (Gitea) ist Archiv → read-only-Header.
|
||||
- **`admin/bf-golden-config`** (Gitea) ist Bot-Master, nicht manuell. Täglich **03:30 UTC** per ScheduledJob **„GC Backup+Compliance – taeglich"** (Nautobot-Job *Execute All Golden Configuration Jobs - Multiple Device*, Scope `platform=RouterOS`, Reihenfolge Backup→Intended→Compliance). Stand prüfen: `backup_last_success_date` am Gerät bzw. `scheduled-jobs` in Nautobot.
|
||||
- **`admin/blaueblueten-n8n`** (Gitea) ist Master für terpene-Repo.
|
||||
- **`inventory/` ist tot** (Pipeline seit 2025-06 stale). Live-Daten aus MikroMCP/CF/Portainer.
|
||||
- **`inventory/` ist tot** (Pipeline stale). Live-Daten aus MikroMCP/CF/Portainer.
|
||||
- **`mcp/INVENTORY.md`** ist BF-Netz-SSoT; `~/.claude.json`/`opencode.jsonc` sind globalere Werkzeugkästen.
|
||||
- **Terpenfinder = `420.teilzeitladen.xyz`** (CF-Worker, Repo `admin/blaueblueten-n8n`, hinter CF Access), **kein Caddy, kein Mac mini im Pfad**. `terpene.*` **existiert nicht und soll es nicht** (live geprüft 2026-09-10). Einziger terpene-benannter CF-Gegenstand: der **interne** Tunnel `terpene-db` (`f2d2fc8a`, ohne öffentlichen Hostname, Hyperdrive→VPC→Postgres, s. `docs/cf-tunnel-mapping.md` §17). ⚠️ **Grenze:** blaueblüten ist ein eigenes Projekt — nicht anfassen. *(Die frühere „terpene.teilzeitladen.xyz:8890 via Tunnel"-Zeile war veraltet — bf-mm ist gelöscht.)*
|
||||
- **shelly-mcp / portainer-mcp bewusst NICHT als MCP-Client** (User-Direktive 2026-08-28): shelly-mcp-Container auf DS923 wird via **Portainer-API** (Skript-Aufrufe, `mcp/portainer/.env` als Token-Quelle) deployt, nicht via MCP-Protokoll. Inventar-Eintrag spiegelt das. `mcp-host-policy.md` ist SSoT für die Host-/IP-Konvention.
|
||||
- **Mac mini `10.40.20.167`** ist der **n8n-Host** (n8n `:5678`, n8n-redis `:6379`, n8n-mcp `:3001`) — **NICHT** der zentrale MCP-Host. Performance-Überlegung: MCP-Clients würden auf dem macmini (User-Workstation) laufen und die Box zusätzlich belasten; deshalb liegt der MCP-Host auf der **DS923** (`nas-bf`, Portainer Endpoint 14). gitea-mcp läuft aktuell noch auf dem macmini (historisch, eigene Container-Definition dort) — gehört **bei Gelegenheit** auf die DS923 umgezogen (separater Punkt, **nicht** Teil dieser Session). Container-Definitionen für n8n leben auf dem macmini (Docker Desktop); FQDN→Backend-Mapping im `caddy/Caddyfile`.
|
||||
- **Terpenfinder = `420.teilzeitladen.xyz`** (CF-Worker, Repo `admin/blaueblueten-n8n`, hinter CF Access), **kein Caddy, kein Mac mini im Pfad**. `terpene.*` **existiert nicht und soll es nicht** (live geprüft 2026-09-10). Einziger terpene-benannter CF-Gegenstand: der interne Tunnel `terpene-db` (s. `docs/cf-tunnel-mapping.md` §17). ⚠️ **Grenze:** blaueblüten ist ein eigenes Projekt — nicht anfassen.
|
||||
- **shelly-mcp / portainer-mcp bewusst NICHT als MCP-Client** (User-Direktive 2026-08-28): shelly-mcp-Container auf DS923 wird via **Portainer-API** (Skript-Aufrufe, `mcp/portainer/.env` als Token-Quelle) deployt, nicht via MCP-Protokoll. `mcp-host-policy.md` ist SSoT für die Host-/IP-Konvention.
|
||||
- **Mac mini `10.40.20.167`** ist der **n8n-Host** (n8n `:5678`, n8n-redis `:6379`, n8n-mcp `:3001`) — **NICHT** der zentrale MCP-Host. Der MCP-Host liegt auf der **DS923** (`nas-bf`, Portainer Endpoint 14). gitea-mcp läuft noch auf dem macmini (historisch) — gehört **bei Gelegenheit** auf die DS923 umgezogen. Container-Definitionen für n8n leben auf dem macmini (Docker Desktop); FQDN→Backend-Mapping im `caddy/Caddyfile`.
|
||||
|
||||
## TP-Management (2026-08-26)
|
||||
|
||||
@@ -85,7 +51,7 @@ Wenn während einer Session ein neues Teilprojekt, eine Abhängigkeit oder ein Q
|
||||
in eine Quick-Index-Tabelle oder einen Ist-Stand-Block — die gibt es seit dem TP36-Cutover (2026-09-07)
|
||||
nicht mehr in der Datei.
|
||||
|
||||
**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis) und ihr Gitea-Issue ist `closed` (Label `status:✅`/`❌`). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt** (Issue dann `reopen`). 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`) — nur noch der betroffene `### TPnn`-Prosa-Abschnitt wird angefasst.
|
||||
**Abgeschlossene TPs** stehen seit 2026-08-23 in `docs/teilprojekte-archiv.md` (Volltext, unverändert, mit Inhaltsverzeichnis) und ihr Gitea-Issue ist `closed` (Label `status:✅`/`❌`). **Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt** (Issue dann `reopen`). 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`).
|
||||
|
||||
---
|
||||
|
||||
@@ -108,264 +74,86 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
|
||||
## Hardware & Zugang
|
||||
| Gerät | IP | Zugang |
|
||||
|---|---|---|
|
||||
| ~~ALLNET Core-Switch (sw1)~~ | — | ❌ **außer Betrieb (2026-08-08)** — durch `crs326-core` (L3-Router + Core-Switch, Technik UG) ersetzt; physisch abgelöst. (Telnet-Runner `switch/` = nur noch historisch.) |
|
||||
| ~~Omada Access-Switch (sw2)~~ | — | ❌ **abgebaut 2026-07-23** (TP15 Branch-Tausch) — durch `crs326-branch` in Technik OG ersetzt, physisch entfernt |
|
||||
| **crs326-branch** (CRS326, Technik OG) | **10.40.99.2** (VLAN99) · Bootstrap Rev4 2026-08-08 | RouterOS **7.24.1** · `admin`/`~/.secrets` (`CRS326_BRANCH_*`) · REST `http://10.40.99.2/rest` · MikroMCP `crs326-branch` ✅ **live** (2026-08-16 verifiziert) · **TP15 ✅: ersetzt sw2** (L2-Switch), volle Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap3) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 |
|
||||
| **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.1** · `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.1** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **TP28 ✅ 2026-08-17: reiner CAP** (Manager = `crs326-core`) · ✅ **aktiv am Sideboard** (Wohnzimmer; Uplink `ether1` → core `ether3`, PoE-Port `poe-out=off`) — Kanäle `5260/Ce` (Band B, DFS, Fallback 5280) + `2412`/ch1, vom Manager · 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.1** · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **TP28 ✅ 2026-08-17: reiner CAP** — **Technik UG**, Uplink `ether1` (PoE) an core `ether21`; Kanäle `5300/Ce` (Band B, DFS, Fallback 5320) + `2462`/ch11 · MAC D0:EA:11:4D:40:39 (ether1; Bridge/Mgmt `:3A`) · Config `routeros/.secrets/ap2-prov-base.rsc` |
|
||||
| **cAP ax (ap3)** | **10.40.99.13** (VLAN99) · Identity `ap3` | ✅ **LIVE seit 2026-08-30** (TP28-Nachtrag) · `cAPGi-5HaxD2HaxD`, RouterOS **7.24.1** + RouterBOOT 7.24.1 (**seit 2026-08-31 ist die ganze Flotte auf 7.24.1**, TP27-Angleichung erledigt) · reiner CAP, Manager `crs326-core` · **Technik OG**, Uplink `ether1` → **branch `ether24`** (Trunk) · Kanäle **`5180/ax`** (Band A, non-DFS, 5 GHz) + **`2437`** (ch6) nach TP25-Plan · 🔴 **nur 2 Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out, **kein** Bridge-Port) → `ap.extra_ports=[]` · `device-mode=basic` (scheduler/fetch/romon **erlaubt**, anders als ap1/ap2) · Dateien `routeros/tp28-ap3-phase{1,2}-*.rsc` + `-service-accounts.rsc` + `-werksreste.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. |
|
||||
| **crs326-branch** (CRS326, Technik OG) | **10.40.99.2** (VLAN99) · Bootstrap Rev4 2026-08-08 | RouterOS **7.24.1** · `admin`/`~/.secrets` (`CRS326_BRANCH_*`) · REST `http://10.40.99.2/rest` · MikroMCP `crs326-branch` ✅ **live** · **TP15 ✅: ersetzt sw2** (L2-Switch), Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap3) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 |
|
||||
| **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.1** · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` ✅ **live** · **PRODUKTIVER L3-Router** — Routing/DHCP (7 Server + 54 Leases)/DNS (**DoH zum CF-Gateway mit `verify-doh-cert=yes`**, TP23 ✅)/**NTP-Server**/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.1** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **reiner CAP** (Manager = `crs326-core`) · **aktiv am Sideboard** (Wohnzimmer; Uplink `ether1` → core `ether3`, PoE-Port `poe-out=off`) — Kanäle `5260/Ce` (Band B, DFS, Fallback 5280) + `2412`/ch1 · MAC D0:EA:11:4D:40:E0 · Config `routeros/.secrets/ap1-prov-base.rsc` |
|
||||
| hAP ax² #2 (ap2) | **10.40.99.12** (VLAN99) · Identity `ap2` | RouterOS **7.24.1** · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **reiner CAP** — **Technik UG**, Uplink `ether1` (PoE) an core `ether21`; Kanäle `5300/Ce` (Band B, DFS, Fallback 5320) + `2462`/ch11 · MAC D0:EA:11:4D:40:39 (ether1; Bridge/Mgmt `:3A`) · Config `routeros/.secrets/ap2-prov-base.rsc` |
|
||||
| **cAP ax (ap3)** | **10.40.99.13** (VLAN99) · Identity `ap3` | ✅ **LIVE seit 2026-08-30** · `cAPGi-5HaxD2HaxD`, RouterOS **7.24.1** + RouterBOOT 7.24.1 · reiner CAP, Manager `crs326-core` · **Technik OG**, Uplink `ether1` → **branch `ether24`** (Trunk) · Kanäle **`5180/ax`** (Band A, non-DFS) + **`2437`** (ch6) · 🔴 **nur 2 Ethernet-Ports** → `ap.extra_ports=[]` · `device-mode=basic` (scheduler/fetch/romon erlaubt) · Dateien `routeros/tp28-ap3-phase{1,2}-*.rsc` |
|
||||
| **🔑 Einheitliches Admin-PW** | alle 4 MikroTik (core/branch/ap1/ap2) | user `admin`, gleiches PW (`~/.secrets/credentials.env` → `ROUTER_*_PASS`/`CRS326_*_PASS`; „Überlebensversicherung", User-Vorgabe). |
|
||||
| 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`** |
|
||||
| Nautobot (Portainer-Stack, NAS) | **10.40.20.154** (TP45 Schicht A 2026-09-21: `.30/.31/.32/.34` → `.154/.155/.156/.158`) | Portainer ep14, Stack Id 203 · **TP21 ✅ 2026-07-16: `ghcr.io/nautobot/nautobot:3.1.7`, DB=platform-pg, Creds in `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored)** · **TP22 ✅ 2026-08-13: Golden-Config-App + Device-Adoption DURCH** — alle 4 MikroTik adoptiert, Deploy-Pipeline scharf (Image `3.1.7-gc9`), **68/68 Compliance-Checks grün / 0 Drifts** (2026-08-16) |
|
||||
| ~~Omada-Controller~~ | — | ❌ **entfernt 2026-07-16** (User hat den Container gelöscht — overkill für 1 Switch auf der Abschussliste, s. TP2.1/TP15) |
|
||||
| ~~omada-mcp~~ | — | ❌ **entfernt 2026-07-16** — mit dem Controller gelöscht. ✅ **Aufräumen erledigt/gegenstandslos (geprüft 2026-08-16):** nicht mehr in `mcpServers` registriert (nur noch als „disabled" in ein paar Projekt-Scopes = harmloser Merkposten), und **kein `omada`-Hostname mehr im CF-Ingress** |
|
||||
| pve-mcp | 10.40.20.173 | **StreamableHTTP `http://10.40.20.173:3000/` (Root-Pfad, NICHT /sse)** · NAS-Stack ep14 (=nas-bf `.20.10`, NICHT LXC .126!) id 202 (vlan20-only, TP14.1 2026-07-10) · **mcp-pve v0.6.1 (Upgrade 2026-07-10: Single-Session-Bug 0.4.8 „Server already initialized" gefixt, TP14.2a)** · `~/.claude.json` ✓ |
|
||||
| Proxmox (pve) | 10.40.20.100 | `ssh root@10.40.20.100` (SSH-Key ✓) · **TP14 2026-07-13: Host-Mgmt VLAN1→VLAN20** (vmbr0.20 + Default-GW-Flip); `.41.100` bleibt interim ohne Route |
|
||||
| Docker-LXC (auf pve) | 10.40.20.126 | Portainer CE 2.40.0 (`https://10.40.20.126:9443`) · **TP14.2 2026-07-10: LXC 400 net0 tag=20/.20.126 (iface heißt jetzt `veth20`, NICHT eth0); Docker-Netz `vlan20`=ipvlan parent `veth20`; adguard_bf1 .20.16, adguard_fm2 .20.17** |
|
||||
| Mac mini | **10.40.20.167** (VLAN20) | `ssh macmini` · ⚠️ die früher hier stehende `10.40.41.167` war **stale** (VLAN1) — live korrigiert 2026-08-16 (ARP `reachable` auf `vlan20`) |
|
||||
| Arris-Kabelmodem | (Bridge-Mode, keine Mgmt-IP im BF-LAN) | Liwest-Edge · speist **`crs326-core vlan100`** (VLAN100-Transfer, WAN-IP `90.146.33.177`) — seit Core-Cutover 2026-08-08, davor via sw1 gi16 → ap1 · **FritzBox 6660 seit 2026-07-01 komplett ausgebaut** (Liwest-Tausch, kein Zurück); Telefonie nicht weitergeführt |
|
||||
| platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅: PG16, DB `nautobot`, Creds `postgres/.secrets`** |
|
||||
| Nautobot (Portainer-Stack, NAS) | **10.40.20.154** | Portainer ep14, Stack Id 203 · **TP21 ✅: `ghcr.io/nautobot/nautobot:3.1.7`, DB=platform-pg, Creds `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored)** · **TP22 ✅: Golden-Config-App + Device-Adoption DURCH** (alle 4 MikroTik, Image `3.1.7-gc9`) |
|
||||
| pve-mcp | 10.40.20.173 | **StreamableHTTP `http://10.40.20.173:3000/` (Root-Pfad, NICHT /sse)** · NAS-Stack ep14 (=nas-bf `.20.10`, NICHT LXC .126!) id 202 (vlan20-only, TP14.1) · **mcp-pve v0.6.1** (TP14.2a) · `~/.claude.json` ✓ |
|
||||
| Proxmox (pve) | 10.40.20.100 | `ssh root@10.40.20.100` (SSH-Key ✓) · **TP14: Host-Mgmt VLAN1→VLAN20** (vmbr0.20 + Default-GW-Flip) |
|
||||
| Docker-LXC (auf pve) | 10.40.20.126 | Portainer CE 2.40.0 (`https://10.40.20.126:9443`) · **TP14.2: LXC 400 net0 tag=20/.20.126 (iface heißt `veth20`, NICHT eth0); Docker-Netz `vlan20`=ipvlan parent `veth20`; adguard_bf1 .20.16, adguard_fm2 .20.17** |
|
||||
| Mac mini | **10.40.20.167** (VLAN20) | `ssh macmini` · n8n-Host (siehe oben) |
|
||||
| Arris-Kabelmodem | (Bridge-Mode, keine Mgmt-IP im BF-LAN) | Liwest-Edge · speist **`crs326-core vlan100`** (VLAN100-Transfer, WAN-IP `90.146.33.177`) · **FritzBox 6660 seit 2026-07-01 ausgebaut** (Liwest-Tausch, kein Zurück); Telefonie nicht weitergeführt |
|
||||
|
||||
*Ausgemusterte Geräte (ALLNET sw1, Omada sw2/Controller/ES208G, omada-mcp): Historie in TP15/TP2.1-Archiv + `switch/README.md`.*
|
||||
|
||||
---
|
||||
|
||||
## Tooling
|
||||
- **MikroTik (core/branch/ap1/ap2):** MikroMCP-Server live → `mcp__mikromcp__*`-Tools (routerId `crs326-core`/`crs326-branch`/`ap1`/`ap2`).
|
||||
Reads immer safe; Writes mit `dryRun:true` previewen, dann `apply_plan`. **Passwort setzen via `manage_user set-password`** (`/user set*` ist per Deny-Policy blockiert). ⚠️ **Reboot geht auf ROS 7.24 gar nicht ueber MikroMCP:** das `reboot`-Tool sendet `delay`, die REST-API kennt den Parameter nicht (`unknown parameter delay`), und `run_command` ist fuer `/system reboot*` gesperrt — Weg ist `SSHPASS="$PW" sshpass -e ssh admin@<ip> "/system reboot"` (PW aus `~/.secrets`, im Output
|
||||
maskieren). 🔴 **Niemals `sshpass -p`** — das schreibt das Passwort in die Kommandozeile, wo es in
|
||||
`ps` und jedem argv-Trace steht; **genau so ist am 2026-08-22 das Core-Passwort in ein
|
||||
Sitzungstranskript geraten** und musste rotiert werden. `-e` hält es in der Umgebung des Kindprozesses.
|
||||
✅ **Alle sechs Skripte im Repo sind am 2026-08-31 umgestellt** (`nautobot/gc-transaktion.py`,
|
||||
`routeros/import-core-rsc.py` und die vier inzwischen nach
|
||||
`routeros/archiv-tp15-cutover/` archivierten Cutover-Skripte) — gemessen
|
||||
mit `ps`-Stichproben plus Negativkontrolle, nicht nur gelesen. **Neue Skripte erben die Regel nicht
|
||||
von selbst**: wer einen sshpass-Aufruf schreibt, baut `env=dict(os.environ, SSHPASS=pw)` mit ein. `bridge/vlan/set` nur auf `[find … and !dynamic]`.
|
||||
🔴 **Zweiter Leak-Weg, gleiche Klasse: `list_wifi_interfaces` gibt `security.passphrase` ALLER
|
||||
Profile im Klartext zurück** — am 2026-08-24 hat ein Read, der nur die `cap-wifiN`-Zuordnung finden
|
||||
sollte, damit **fünf WLAN-PSKs** (`main`/`iot`/`media`/`untrusted`/`guest`) in ein Transkript
|
||||
geschrieben. Kein Werkzeugfehler — das falsche Werkzeug für die Frage. **Für Kanäle
|
||||
`/interface wifi channel print detail`, für die CAP-Zuordnung `… print where master=yes` mit
|
||||
Feldauswahl.** Der Scan selbst (`/interface wifi scan`, **nur am Manager**) ist unbedenklich.
|
||||
Merkregel dahinter: **vor jedem Read fragen, was das Tool AUSSER dem Gesuchten noch ausgibt.**
|
||||
- **~~ALLNET-Switch (sw1)~~:** ❌ **Gerät ausgebaut 2026-08-08** → der Telnet-Runner unter `switch/` ist **Archiv, nicht mehr ausführbar** (kein Endpunkt). Siehe `switch/README.md`. Nicht mehr als Werkzeug anbieten.
|
||||
- **~~Omada-Switch (sw2)~~:** ❌ abgebaut 2026-07-23 (TP15, durch `crs326-branch` ersetzt). Controller + omada-mcp komplett gelöscht — **Omada spielt keine Rolle mehr, nie wieder aufgreifen** (User-Entscheid 2026-07-22). Aufräum-Reste ✅ geprüft 2026-08-16 (s. Tabelle oben).
|
||||
Reads immer safe; Writes mit `dryRun:true` previewen, dann `apply_plan`. **Passwort setzen via `manage_user set-password`** (`/user set*` ist per Deny-Policy blockiert). ⚠️ **Reboot geht auf ROS 7.24 gar nicht über MikroMCP** — Weg ist `SSHPASS="$PW" sshpass -e ssh admin@<ip> "/system reboot"` (PW aus `~/.secrets`, im Output maskieren).
|
||||
🔴 **Niemals `sshpass -p`** — das schreibt das Passwort in die Kommandozeile (`ps`/argv-Trace; so ist am 2026-08-22 das Core-PW in ein Transkript geraten). `-e` hält es in der Umgebung des Kindprozesses. **Neue Skripte erben die Regel nicht von selbst**: `env=dict(os.environ, SSHPASS=pw)` einbauen. `bridge/vlan/set` nur auf `[find … and !dynamic]`.
|
||||
🔴 **`list_wifi_interfaces` gibt `security.passphrase` ALLER Profile im Klartext zurück** (2026-08-24: fünf WLAN-PSKs ins Transkript). **Für Kanäle `/interface wifi channel print detail`, für die CAP-Zuordnung `… print where master=yes` mit Feldauswahl.** Der Scan (`/interface wifi scan`, nur am Manager) ist unbedenklich. Merkregel: **vor jedem Read fragen, was das Tool AUSSER dem Gesuchten noch ausgibt.**
|
||||
- **Nautobot Golden Config (= der einzige legitime Config-Weg für Netzgeräte):** `nautobot/gc-configplan.py`
|
||||
(Token = `NAUTOBOT_SUPERUSER_API_TOKEN` aus `docs/nas-vlan20/stacks/210-nautobot.yml`, gitignored).
|
||||
`--job <name>` legt manuelle Pläne an (Dry-Run default), `--deploy-ids …` deployt gestaffelt,
|
||||
`--refresh-compliance` fährt **Backup → Intended → Compliance** in der einzig richtigen Reihenfolge.
|
||||
⚠️ **`POST /api/plugins/golden-config/config-plan/` ist `405` — das ViewSet ist read-only.** ConfigPlans
|
||||
entstehen nur über den Job **`Generate Config Plans`** (`plan_type=manual` + `commands`-TextVar).
|
||||
⚠️ **`POST /api/plugins/golden-config/config-plan/` ist `405` — ViewSet read-only.** ConfigPlans entstehen nur über den Job **`Generate Config Plans`** (`plan_type=manual` + `commands`-TextVar).
|
||||
- **Docker-Management:** Portainer API `https://10.40.20.126:9443`, Key in `mcp/portainer/.env`.
|
||||
- 🔴 **`pct push` meldet Exit 0, auch wenn es die Quelldatei NICHT lesen kann** (gemessen 2026-09-21).
|
||||
`pct push 130 <fehlende-datei> /etc/caddy/Caddyfile` → `failed to open … for reading`, **`rc=0`**.
|
||||
Ein `&&`/`||` hinter dem Push **beweist nichts**, und ein anschließendes `validate`/`reload` prüft
|
||||
dann die **alte** Datei → **grüne Abnahme bei unverändertem Zustand**. Genau so sah der
|
||||
Nautobot-Umzug am 2026-09-21 „gelandet" aus, während live noch die alte Backend-IP stand
|
||||
(Symptom: 502 bei gesundem Backend). **Regel: Push-Werkzeug wird geprüft, nicht geglaubt** — nach
|
||||
jedem `pct push` den Inhalt vergleichen (`md5sum` diesseits/jenseits in *einem* Remote-Aufruf,
|
||||
Abbruch bei Abweichung). Muster: `push_verified()` in `caddy/deploy.sh`. Dieselbe Klasse wie
|
||||
`nc -z` (09-01) und das `grep|sed`-Maskierungsmuster (08-31): **erst das eigene Werkzeug
|
||||
verdächtigen, dann das Netz.**
|
||||
`rc=0` trotz `failed to open … for reading`; ein `&&`/`||` dahinter beweist nichts, und `validate`/`reload` prüft dann die **alte** Datei → **grüne Abnahme bei unverändertem Zustand** (so sah der Nautobot-Umzug „gelandet" aus, live stand noch die alte Backend-IP). **Regel: Push-Werkzeug wird geprüft, nicht geglaubt** — nach jedem `pct push` den Inhalt vergleichen (`md5sum` diesseits/jenseits in *einem* Remote-Aufruf). Muster: `push_verified()` in `caddy/deploy.sh`. **Erst das eigene Werkzeug verdächtigen, dann das Netz.**
|
||||
- 🔴 **Die Core-Adressliste `nautobot` wird von Golden Config NIRGENDS gerendert** (Fund 2026-09-21).
|
||||
Der Mgmt-Zugang des Cores für die Deploy-Pipeline läuft über `src-address-list=nautobot`
|
||||
(Regel `*28`) — die Liste selbst stammt aber aus dem **handgelaufenen**
|
||||
`routeros/tp5-etappe2-whitelists.rsc` und steht in **keinem** Template (`grep address-list` im
|
||||
Template findet nur Kommentare). 🔴 **Ein GC-Recovery von null erzeugt damit einen Core, in den
|
||||
Nautobot nicht mehr per SSH kommt** — die Pipeline wäre tot und könnte sich nicht selbst
|
||||
reparieren. Gleiche Lücke wie die DHCP-Reservierungen. **Merkregel: was auf dem Gerät existiert,
|
||||
aber nicht im Template, ist eine Recovery-Lücke, auch wenn Compliance 140/140 grün meldet** —
|
||||
die Zahl prüft nur, was gerendert wird.
|
||||
Ablösung durch Komodo (native MCP) geplant.
|
||||
Der Mgmt-Zugang für die Deploy-Pipeline läuft über `src-address-list=nautobot` (Regel `*28`), die Liste
|
||||
stammt aber aus dem handgelaufenen `routeros/tp5-etappe2-whitelists.rsc` — **steht in keinem Template**.
|
||||
🔴 **Ein GC-Recovery von null erzeugt einen Core, in den Nautobot nicht mehr per SSH kommt** — die
|
||||
Pipeline wäre tot. Gleiche Lücke wie die DHCP-Reservierungen. **Merkregel: was auf dem Gerät existiert,
|
||||
aber nicht im Template, ist eine Recovery-Lücke, auch wenn Compliance 140/140 grün meldet** — die Zahl
|
||||
prüft nur, was gerendert wird. Ablösung durch Komodo (native MCP) geplant.
|
||||
|
||||
---
|
||||
|
||||
## 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`.
|
||||
- 📝 **WLAN-Logs sind seit 2026-08-30 persistent — und der Topic heißt `wireless`, nicht `wifi`.**
|
||||
Auch auf dem neuen `wifi-qcom`-Stack tragen Roaming-/Connect-Events `wireless;info` bzw.
|
||||
`wireless,debug`. Ein geratenes `topics=wifi` wäre wirkungslos geblieben. Zwei Regeln auf dem
|
||||
**Core** (nicht auf den CAPs — unter CAPsMAN führt der Manager die Events) schicken `wireless`
|
||||
und `caps` in denselben Kollektor wie TP5 (`10.40.20.18:514`, Tagesdateien unter
|
||||
`/opt/tp5-syslog/log/10.40.20.1/`). ⚠️ **RouterOS UND-verknüpft Topics innerhalb EINER Regel**
|
||||
(`topics=info,!firewall` = info UND NICHT firewall) — `topics=wireless,caps` hätte **nichts**
|
||||
gematcht, es braucht zwei Regeln. Vorher lief alles nur in einen ~75-Zeilen-Ringpuffer, der von
|
||||
einer Stunde Arbeit überschrieben war; ein seit Wochen gemeldetes Client-Problem war deshalb
|
||||
**rückwirkend nicht diagnostizierbar**. `/system logging` steht jetzt unter der 27. ComplianceRule
|
||||
`system-logging` — vorher unter keiner. **Merkregel: bevor man ein sporadisches Problem sucht,
|
||||
erst prüfen, ob es überhaupt irgendwo mitgeschrieben wird.**
|
||||
- 🧮 **Ein Log-Burst über VIELE MACs ist Infrastruktur, über EINE MAC ein Client** (Fund 2026-08-31).
|
||||
Am 31.08. sahen 13 × `caps,info … joined`, 4 × `activity timeout` und zwei Massenbursts
|
||||
`association rejected, no cached PMK` (74 Zeilen, **38 verschiedene MACs**) wie eine CAPWAP-
|
||||
Instabilität aus, die das ganze Netz trifft. Es waren die **Reboots des TP27-Upgrades**. Jedes
|
||||
Ereignis fiel auf einen Neustart. 🔴 **Vor jeder Client-Diagnose erst `grep " caps,"` und
|
||||
`/system resource get uptime` lesen** — sonst sucht man einen Gerätefehler, der ein geplanter
|
||||
Reboot war. Der Umkehrschluss gilt auch: 93 von 93 `reauthenticating` von **einer** MAC auf
|
||||
**einer** SSID sind ein echter Client-/SSID-Befund.
|
||||
- 🗺️ **Die `cap-wifiN`-Karte verschiebt sich bei JEDEM CAP-Rejoin, nicht nur beim Provisionieren.**
|
||||
Am 2026-08-31 wanderte sie zweimal an einem Tag (08:09 Re-Provisionierung, 14:36 Core-Reboot);
|
||||
eine im Handoff festgehaltene Tabelle war nach sechs Stunden falsch. ⇒ **Interface-Namen in
|
||||
Logzeilen sind ohne einen zeitnahen Karten-Snapshot NICHT zuschreibbar.** Vor jeder Auswertung
|
||||
`/interface wifi radio print terse proplist=cap,radio-mac,interface` am Manager lesen — und wenn
|
||||
zwischen Ereignis und Auswertung ein Rejoin liegt, die Zuordnung als **unbekannt** behandeln
|
||||
statt zu raten.
|
||||
- 🔑 **CAPsMAN verteilt KEINE Login-Passwörter — nur WLAN-PSKs.** Zwei verschiedene Dinge heißen
|
||||
„Passwort": die **WPA-Passphrase** (`/interface wifi security`) liegt am Manager und wird an die
|
||||
CAPs verteilt ✅; der **Geräte-Login** (`/user`) ist eine **lokale Benutzer-DB auf jedem Gerät** ❌.
|
||||
Beleg: ap1 trägt `admin`, `ha`, `nautobot` lokal, mit gerätelokalen `last-logged-in`-Zeitstempeln.
|
||||
Das einheitliche Admin-PW ist eine **von Hand angewendete Konvention**, keine Automatik — und
|
||||
logisch kann es nicht anders sein: ein Gerät *wird* erst zum CAP, nachdem sich jemand eingeloggt hat.
|
||||
- 🔴 **Ein neuer CAP braucht `configuration.manager=capsman` auf JEDEM Master-Radio.**
|
||||
`/interface wifi cap set enabled=yes` allein reicht nicht — ohne das Feld bietet der CAP seine
|
||||
Radios dem Manager gar nicht erst an. **Symptom (2026-08-30, ap3):** `remote-cap print` zeigt
|
||||
`state=Ok`, aber `/interface wifi radio print` am Manager listet für dieses Gerät **nichts**, und
|
||||
weil die Provisioning-Regel dann nicht matcht, sucht man den Fehler in der `radio-mac`. Im
|
||||
Werkszustand ist das Feld leer (= `local`).
|
||||
- 🔌 **Werks-MikroTik am Trunk: `ether1` ist WAN, und das sieht aus wie ein Netzproblem.** Beim
|
||||
cAP ax (und den hAPs) steht `ether1` per defconf in der Interface-Liste `WAN`. Folge: Discovery
|
||||
aus (**kein MNDP → in `/ip neighbor` unsichtbar**), Input-TCP verworfen (22/80/8291/8728 **zu**),
|
||||
**ICMP aber erlaubt** — es *pingt*, was sich wie „erreichbar" liest. Kein Registry-Eintrag und
|
||||
kein MikroMCP hilft, denn Port 80 ist zu. **Der Weg rein ist die LAN-Seite** (`ether2` oder das
|
||||
Werks-WLAN); dort `/interface list member remove [find where interface=ether1]` — das ist der Fix.
|
||||
- 🔀 **`pvid` ist INGRESS, `tagged`/`untagged` ist EGRESS.** Ein Port mit `pvid=99`, der in der
|
||||
*tagged*-Liste von VLAN99 steht, nimmt untagged an und antwortet **getaggt**. Symptom
|
||||
(2026-08-30): ein DHCP-Lease hing ewig auf `offered` und ARP auf `failed` — der DISCOVER kam an,
|
||||
der ACK nie. Für ein Werksgerät gehört der Port **untagged** in die VLAN-Zeile.
|
||||
- 🕳️ **Ein neues Gerät ohne `secrets_group` erzeugt eine LÜCKE, keine Fehlermeldung.** Ohne
|
||||
Secrets Group scheitert der Golden-Config-**Backup**-Job still; die *intended* config wird
|
||||
trotzdem sauber gerendert, aber ohne Backup entstehen **keine Compliance-Einträge** — und die
|
||||
Gesamtzahl bleibt unauffällig (bei uns 104/104), weil ein Gerät ohne Ergebnisse auch keine Drift
|
||||
meldet. 🔴 **Merkregel: eine gleichbleibende Compliance-Zahl nach dem Hinzufügen eines Geräts ist
|
||||
ein BEFUND, kein Erfolg.** Erst `130/130` bewies, dass ap3 wirklich geprüft wird. Zweite
|
||||
Voraussetzung: die dedizierten Service-Accounts `nautobot`/`ha` (TP-PWD) existieren auf einem
|
||||
Werksgerät nicht.
|
||||
- 🔢 **Firewall-Regelnummern aus `print where` sind Handles, keine Positionen.** Nach einem
|
||||
`place-before` zeigte `/ip firewall filter print terse where comment~"..."` die Nummer **60**,
|
||||
während `drop` auf **59** stand — die Regel lag trotzdem davor. Beweis ist der **Zähler**: eine
|
||||
Regel hinter dem `drop` kann nicht zählen. Wer nach der Zahl geht statt nach `print stats`,
|
||||
„repariert" eine funktionierende Regel kaputt.
|
||||
- 🔌 **`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**
|
||||
kleinen Container (USB ist laut MikroTik-Doku nur für schnellere Extraktion, nicht Pflicht).
|
||||
Faustregel: **~70 MB nutzbar je AP**, weil RouterOS Reserve für sein eigenes `.npk` braucht.
|
||||
🔴 **`crs326-core` dagegen NIE:** `arm` **32-bit**, **2,9 MB frei von 16 MB**. Der Core ist der
|
||||
Netz-SPOF, aber **kein Anwendungs-Host** — das ist eine Hardware-Tatsache, keine Politik.
|
||||
🔑 **Und die TP16.1-Aussage „Connector als RouterOS-Container ❌ nicht möglich" gilt nur für den
|
||||
WARP-Connector**, weil der ein **TUN-Device** braucht (RouterOS reicht keines durch).
|
||||
**`cloudflared` braucht kein TUN** — Userspace-Proxy. Die Schlussfolgerung überträgt sich nicht.
|
||||
Merkregel: **bei einer „strukturellen" Einschränkung erst prüfen, ob sie ein Default ist oder ein
|
||||
Gesetz** — und ob sie dem konkreten Dienst überhaupt gilt.
|
||||
- **Schwere Container laufen weiterhin auf NAS/pve**, nicht am Router (Caddy, AdGuard: die eine Config
|
||||
ändert sich staendig, die andere schreibt dauernd Logs auf NAND).
|
||||
- ⏱️ **Ein konstanter 10-Sekunden-Hänger ist ein DNS-Timeout, kein Lastproblem** (Fund 2026-09-01,
|
||||
TP16.3-Randbefund). `gitea` hing bei ~10 % der Anfragen exakt 10,1 s — quer über Web-UI,
|
||||
`/api/healthz` **und** den Git-Pfad, also kein Rendering. Ursache: **MariaDB (LXC 302) hatte
|
||||
`skip-name-resolve` auskommentiert** und machte bei **jeder neuen Verbindung** einen
|
||||
Reverse-Lookup, während ihr `/etc/resolv.conf` noch auf den toten `10.40.41.254` zeigte
|
||||
(glibc: 5 s × 2 Versuche = 10 s). Pool-Verbindungen waren schnell ⇒ es wirkte sporadisch.
|
||||
🔑 **Drei Merkregeln daraus:** (a) **immer gegen eine zweite Sache messen, die denselben Weg
|
||||
nimmt** — `vw`/`hass` über dieselbe Strecke hatten 0 von 30 Hängern, das hat CF, Tunnel, Firewall
|
||||
und Caddy in einem Schritt entlastet; (b) **wer an einer DB hängt, sagt die Verbindungstabelle**
|
||||
(`ss -tn | grep 3306`), nicht die Vermutung — hier hingen **Vaultwarden, Home Assistant und gitea**
|
||||
an derselben MariaDB, und die Annahme „Vaultwarden nutzt SQLite" war schlicht falsch;
|
||||
(c) **ein toter Resolver ist selten allein** — der Sweep über alle LXCs fand **fünf** Container mit
|
||||
`10.40.41.254`. Sweep: `for c in $(pct list|awk 'NR>1{print $1}'); do pct exec $c -- grep ^nameserver /etc/resolv.conf; done`
|
||||
- 🔌 **`networking.service` failed bei funktionierendem Netz = tote `auto`-Stanzas in
|
||||
`/etc/network/interfaces`** (Ursache gefunden + behoben 2026-09-01). In vier LXCs standen dort noch
|
||||
Einträge aus dem **alten VLAN1-Schema** (`eth0` → `10.40.41.x`) und aus früheren Versuchen
|
||||
(`veth16`, `veth24`, Tippfehler `eth01`) — auf Interfaces, die es im Container gar nicht gibt.
|
||||
`ifup -a` scheitert daran, das echte `veth20` kommt trotzdem hoch. **Fix:** nur die Stanzas
|
||||
entfernen, deren Interface in `ip -br link` fehlt (`lo` und die echten behalten), Backup daneben.
|
||||
🔴 **Abnahme ist ein echter Reboot, nicht `systemctl is-active`** — der Servicestatus sagt nichts
|
||||
darüber, ob beim nächsten Start etwas hochkommt.
|
||||
- 🧪 **Ein Messwerkzeug, das etwas anderes beantwortet als die Frage, kostet mehr Zeit als der Fehler**
|
||||
(zweimal in einer Nacht, 2026-09-01). (a) **`nc -z` lieferte falsch-negative** „Port zu" für neun
|
||||
Dienste gleichzeitig — inklusive des SSH-Ports, über den die Session gerade arbeitete; `curl` bekam
|
||||
von demselben Host **HTTP 200 in 24 ms**. **Bei „alles gleichzeitig kaputt" zuerst das eigene
|
||||
Werkzeug verdächtigen**, dann das Netz: ein Python-Socket oder `curl` entscheidet in Sekunden.
|
||||
(b) **Ein Firewall-Zähler taugt nicht als Wegbeweis**, wenn der Client die Verbindung offenhält
|
||||
(`cloudflared`): er zählt nur NEUE Verbindungen und steht still, obwohl alles läuft — richtig ist
|
||||
die **Verbindungstabelle** (`/ip firewall connection`).
|
||||
- 🔌 **`networking.service` kann in einem LXC dauerhaft `failed` sein, ohne dass das Netz fehlt** —
|
||||
und dann startet alles mit `After=network-online.target` nach einem Reboot **nicht**
|
||||
(Fund 2026-09-01: `101`, `102`, `106`, `107`; **influxdb2 kam nach dem Neustart nicht hoch**,
|
||||
Unit `enabled`, **kein Startversuch im Journal**). Fällt nur auf, wenn man neu startet — die
|
||||
Container liefen 13+ Tage. **Vor jedem geplanten LXC-Neustart `systemctl --failed` lesen und
|
||||
danach die Ports gegen vorher vergleichen**, sonst verliert man still einen Dienst.
|
||||
- **VLAN20-Docker-Hosts: Host-DNS nach dem Umzug prüfen (2026-07-10, TP14.2a):** NAS + pve-LXC zeigten `/etc/resolv.conf` noch auf totes `.41.254` → `docker pull` ghcr scheitert (`EAI_AGAIN`, aber Egress ok!). Fix = `.20.1`: LXC `pct set 400 --nameserver 10.40.20.1`; **Synology-Falle: DSM-„Allgemein"-DNS ≠ was `dockerd` liest** — Interface zieht DNS per DHCP → Fix an der *Netzwerkschnittstelle* „DNS manuell". Laufende Container frieren den `127.0.0.11`-Upstream zum Erstellzeitpunkt ein (Recreate nötig). Merkregel: `docker-pull-hängt` + `dns.lookup EAI_AGAIN` aber `TCP zu ghcr-IP:443 ok` = toter Resolver, nicht Egress.
|
||||
- 📜 **~~ALLNET VLAN-DB (TP1)~~ — historisch.** Die VLAN-Semantik gilt weiter (**40=MEDIA, 50=UNTRUSTED**), die Portangaben (GE1/GE2/GE8) beziehen sich auf ein ausgebautes Gerät.
|
||||
- **ap3 hängt an `crs326-branch ether24`** (Trunk, native VLAN1 + tagged 10-99); **branch-Uplink `ether1` → `crs326-core ether8`** (korrigiert 2026-08-16 — die frühere Angabe „→ sw1 GE8" stammt aus der Zeit vor dem Core-Tausch; MAC-hart belegt in TP22 Schritt 5). ap2 hängt seit dem TP25-Tausch (2026-09-15) an `core ether21` (Technik UG), ap1 an `core ether3` (Sideboard).
|
||||
- **Mesh = NEIN** (beide APs haben wired Trunk-Uplink → Loop-Risiko).
|
||||
- **Beide APs = CAPs, Manager ist `crs326-core` (TP28 ✅ 2026-08-17).** Der Entscheid „kein CAPsMAN" von 2026-07-22 ist **revidiert**: er galt der Konstellation ap1=Manager+AP, in der ein Manager seine eigenen Radios nicht provisionieren kann — mit dem Core als Manager sind beide APs symmetrisch. Der Core braucht dafür **kein** `wifi-qcom`, `capsman` steckt im Basispaket.
|
||||
- 🔴 **`/interface wifi scan` geht am CAP NICHT — nur am Manager** (Fund 2026-08-23). Das Gerät
|
||||
antwortet `failure: not allowed (/interface/wifi/scan)`; das ist **weder device-mode noch eine
|
||||
Policy-Lücke** (Gegentest: `/tool fetch` sagt auf demselben Gerät explizit „not allowed **by
|
||||
device-mode**", und die Gruppe `full` hat `test`+`sniff`). Ein CAP besitzt sein Radio nicht.
|
||||
Richtig ist `/interface wifi scan cap-wifiN duration=…` **am Core** — dort läuft es einwandfrei.
|
||||
⚠️ Die `cap-wifiN`-Zuordnung vorher über `radio-mac` bestimmen, sie ist nicht stabil.
|
||||
- 🔴 **Messfalle: unter CAPsMAN führt der MANAGER die Tabellen.** `/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN läuft. **Immer am Manager zählen.**
|
||||
- 🔴 **Die CAPs finden den Manager per Layer-2-Discovery** (`discovery-interfaces=vlan99`, `current-caps-man-address` ist eine **MAC**, keine IP). Deshalb müssen **MNDP (udp/5678)** und **CAPWAP (udp/5246-5247)** in der `input`-Kette des Core erlaubt bleiben — sonst kommt das WLAN nach dem nächsten AP-Reboot nicht zurück, während laufende Sessions unauffällig weiterlaufen (Fund 2026-08-18, TP5 Etappe 4).
|
||||
- 🔁 **Slave-VAPs aendern heisst zweimal arbeiten (2026-08-22).** `slave-configurations` in einer
|
||||
`provisioning`-Zeile zu setzen bewirkt am Funk **nichts**, solange nicht
|
||||
`/interface wifi capsman remote-cap provision [find where identity="apX"]` nachgezogen wird —
|
||||
und das gilt in **beide** Richtungen: auch beim Entfernen senden die VAPs sonst munter weiter.
|
||||
🔴 **Und die Abnahme nicht am Kommentar festmachen:** beim Rueckbau wird der Interface-Kommentar
|
||||
geleert, waehrend der VAP steht. Wer auf `comment~"ssid"` prueft, sieht "weg" und liegt falsch.
|
||||
Richtig ist `datapath.vlan-id` bzw. die Interface-Zahl **am CAP** (`/interface wifi print` dort).
|
||||
Beim Interface-*Zustand* ist ueberhaupt der CAP die Wahrheit: der Manager zeigt nach einer
|
||||
Re-Provisionierung `running=false` fuer alle `cap-*`, waehrend am CAP alles laeuft — das
|
||||
Gegenstueck zur Regel, dass die *Registration-Table* am Manager zu lesen ist.
|
||||
- **Local Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es kein zentrales Forwarding, `datapath` existiert dort nicht.
|
||||
- 🔴🔴 **`/interface wifi cap slaves-datapath` MUSS auf beiden APs gesetzt sein** (`dp-caps-slaves`, ein lokales datapath-Objekt mit `bridge=bridge` und **ohne** `vlan-id`). Es ist das Einzige, was die vom Manager erzeugten **Slave-VAPs** lokal in die Bridge haengt: der Manager schickt `datapath.bridge=bridge` mit, **der CAP uebernimmt davon nur die `vlan-id`**. Fehlt es, assoziieren Clients auf `-iot`/`-media`/`-untrusted`/`-guest` sauber und bekommen **nie DHCP**, waehrend `dirtyvader-main` weiterlaeuft — dessen Master-Radios `wifi1`/`wifi2` sind statische Bridge-Ports. Genau so lagen IOT+MEDIA am 2026-08-18 rund 14 Stunden (Ursache: CAPsMAN-Cutover 08-17, nicht die TP5-Haertung). **Abnahme fuer jeden WLAN-Umbau ist eine frische DHCP-Lease pro SSID — NICHT die Registration-Table**, die zeigte waehrend des Ausfalls 25 zufriedene IOT-Clients.
|
||||
- 🔎 **Merkregel „connect ok, kein DHCP":** zuerst `torch` auf dem betroffenen VLAN am Core UND den TP5-Drop-Log lesen. Sind **beide leer**, wird nichts verworfen — es kommt nichts an, und die Firewall ist entlastet. Dann liegt es an L2 (Bridge-Mitgliedschaft/Tagging), nicht am Regelwerk.
|
||||
- mgmt-VLAN-Wechsel am Switch = Lockout-Risiko: VLAN muss existieren + Port muss es tragen, in einem Schritt.
|
||||
- 🔥 **Die Firewall ist seit 2026-08-18 deny-by-default — in BEIDEN Ketten.** Ein neuer Dienst über eine VLAN-Grenze braucht ab jetzt eine explizite Regel, sonst funktioniert er einfach nicht. Diagnosewerkzeug ist die **Log-Regel direkt vor dem Drop** (`TP5-WOULD-DROP` / `TP5-INPUT-WOULD-DROP`), die weiterläuft und in den Kollektor `10.40.20.18:514` schreibt (Dateien auf dem Docker-LXC unter `/opt/tp5-syslog/log/`). **Erst dort nachsehen, bevor irgendwo anders gesucht wird.** **MAIN→INFRA/MEDIA/MGMT/IOT stehen dauerhaft offen** (IOT seit TP5 E5, 2026-08-19 — Drucker + Shellys); **IOT→MAIN bleibt bewusst zu**, dort sitzt der Wert der Segmentierung. Ad-hoc-Zugriff aus MAIN in ein **noch geschlossenes** VLAN (UNTRUSTED, GUEST) läuft über die selbstverfallende Liste: `/ip firewall address-list add list=jit-admin address=<deine-IP> timeout=1h`. *(Die früher hier genannten Beispiele „Scannen, IoT anlernen" brauchen jit-admin seit E5 nicht mehr.)*
|
||||
- 🖨️ **Merkregel aus dem E5-Drop-Log:** der Drucker fällt mit **udp/161 (SNMP)** auf, nicht mit 631 — macOS fragt zuerst den SNMP-Status ab und bricht ab, bevor es zu IPP kommt. **Eine Whitelist auf 631 hätte nichts repariert.** Bei „Gerät antwortet nicht" also das Log lesen, statt den erwarteten Port zu raten.
|
||||
- ⏰ **Zeitquelle ist der Core** (`/system ntp server`, seit 2026-08-18), ausgeliefert per **DHCP-Option 42** (`ntp-server=` in jedem der 7 Netze) und drift-überwacht (ComplianceRule `system-ntp`). Vorher lief **nirgends** eine Synchronisation: die anderen drei Geräte zeigten auf `10.40.99.1`, wo kein Server lief, und der Core selbst hatte gar keinen NTP-Client — ap2 war dadurch **17 Stunden** versetzt. 🔴 **Zwei Fallen dabei:** (a) ein NTP-Server mit `use-local-clock=no` und unsynchronisiertem Client liefert **Stratum 16**, das jeder Client verwirft — Server einschalten allein reicht nicht; (b) wird der Server eingeschaltet, *bevor* sein Client synchron ist, hält er diesen Zustand fest → **`enabled=no`/`=yes` nach der Synchronisation**. Merkregel: bei widersprüchlichen Zeitstempeln zwischen Geräten zuerst `/system ntp client get status` lesen, und zum Eingrenzen einen Client testweise auf eine öffentliche Quelle zeigen lassen.
|
||||
- 📡 **SSDP-Relay für die macOS-Sonos-App:** LXC **108 `mcast-relay`** auf pve, zweibeinig `10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40), `multicast-relay` mit **`--noMDNS`** (mDNS macht der Core). Unit im Repo: **`mcast-relay/multicast-relay.service`**. **Zwei Beine sind zwingend** — SSDP ist link-lokal mit TTL 1, ein Relay muss Mitglied beider Broadcast-Domänen sein; Routing hilft nicht.
|
||||
- 🔴 **`--ssdpUnicastAddr` gehört in ein zweibeiniges Relay NICHT — die Flagge hat den Hinweg 7 Wochen lang blockiert** (drin 2026-08-18, ausgebaut **2026-08-25**). Sie schreibt in `multicast-relay.py` **Z. 630** die Quelle *jedes* SSDP-Pakets an `239.255.255.250:1900` auf `<addr>:1901` um (**auch NOTIFY**); beim Senden greift dann **Z. 691** `if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue` — `10.40.10.21` liegt nicht im VLAN40-Netz, also **verwarf der Relay die M-SEARCH selbst, bevor sie die Player erreichte**. Der Rückweg gehört stattdessen **geroutet**: `forward TP5: MEDIA->MAIN` ist offen, die Player antworten unicast an den echten Sucher. (Upstream nennt den Rückweg selbst `# FIXME: record more than one?` — es gibt nur **einen globalen** Merkslot.)
|
||||
- 🔎 **Symptom-Merkregel:** *App sucht hunderte Male, sieht NOTIFYs, baut aber nie eine Verbindung auf* = **Relay, nicht Netz**. Diagnose in einer Zeile — **Richtungsbilanz** im Journal: `grep -c "via eth1"` (VLAN10→VLAN40) gegen `grep -c "via eth0"`. **0 zu n heißt: der Hinweg fehlt.**
|
||||
- ⚠️ **Aus dem Relay heraus lässt sich der Relay nicht testen** (Loop-Schutz ignoriert eigene Quelladressen) — am echten Client testen oder `journalctl -u multicast-relay` mitlesen. **Und am Client nicht auf Port 1900 messen:** Sonos-App *und* Spotify halten dort je ein `SO_REUSEPORT`-Socket, die Unicast-Antwort bekommt nur **eines** davon. Richtig ist eine Probe mit **ephemerem Quellport** (`bind((ifaddr, 0))`), genau wie ein echter Client sucht.
|
||||
- 🔴 **Zweite, unabhängige Ursache desselben Falls (08-25): die Sonos-App suchte gar nicht.** Nach der Relay-Reparatur lag weiter nichts an — der Mitschnitt auf der VLAN40-Leitung (`pct exec 108 -- tcpdump -i eth1 -n -s0 -A "udp and host <client>"`) zeigte: **jedes** SSDP-Paket des MacBooks kam von **Chrome** (`ST: urn:dial-multiscreen-org:…dial:1`, `USER-AGENT: Chromium`), **kein einziges** von Sonos. Die App hatte Sockets offen (`*:1900`, `*:6969`, `TCP *:3400`) und redete mit der Cloud, legte aber nichts auf die Leitung. Ursache war das **Local-Network-Gate von macOS**: es stand auf **„an" und griff trotzdem nicht**. Fix = Schalter unter *Datenschutz & Sicherheit → Lokales Netzwerk* **aus, App beenden, wieder an, App starten** (hart: `sudo tccutil reset LocalNetwork com.sonos.macController2`).
|
||||
⇒ **Merkregel: „die App sucht hunderte Male" ist erst dann ein Befund, wenn der Mitschnitt sagt, WER sucht.** Im Handoff vom Vormittag waren die gezählten 291 Suchen Chrome + eigene Proben; die Zahl hat drei Stunden in die falsche Richtung gezeigt.
|
||||
- 🖥️ **Das MacBook ist dual-homed in VLAN10:** `en5` = **10.40.10.183** (USB-Dock, Default-Route) und `en0` = **10.40.10.82** (WLAN). Die früher als „zweiter Mac" gelesene `.183` ist **dasselbe Gerät** — beim Messen immer beide Beine prüfen.
|
||||
- 🏠 **Home Assistant: der `http:`-Block in `configuration.yaml` ist WIRKUNGSLOS** (Fund 2026-08-26,
|
||||
TP31). HA hat die http-Config nach **`.storage/http`** migriert (`"yaml_migration_done": true`);
|
||||
seither wird die YAML kommentarlos ignoriert — selbst ein ungueltiger Schluessel darin erzeugt
|
||||
**keinen** Config-Fehler. 🔴 **Die Logzeile fuehrt aktiv in die Irre:** `Received X-Forwarded-For
|
||||
header from an untrusted proxy <ip>` faellt laut Quelltext nur bei **aktivem**
|
||||
`use_x_forwarded_for` — man haelt die Option fuer wirksam und die Liste fuer falsch und editiert
|
||||
die falsche Datei. Richtig ist der WS-Zweischritt **`http/config/configure`** (setzt `pending`,
|
||||
startet HA neu) → binnen **5 Minuten** **`http/config/promote`**, sonst Auto-Revert auf `stable`.
|
||||
Merkregel zur Eingrenzung: ein Testskript in `<config>/python_scripts/` + `python_script.reload` —
|
||||
taucht der Service auf, liest HA das Verzeichnis, und die Frage ist nicht mehr *welche Datei*,
|
||||
sondern *warum ein gelesener Block nicht wirkt*.
|
||||
- 🐳 **Nautobot auf der DS923 braucht einen abgeschalteten Image-Healthcheck.** Der eingebackene Check ruft alle 10 s `nautobot-server health_check` = einen vollen Django-Start; unter Last dauert der >2 min, die Aufrufe **stapeln sich unbegrenzt** → Speicher alle → `fork()` mit ENOMEM → DSM räumt den ganzen Stack ab (am 2026-08-17 zweimal passiert, Load bis 73 bei 4 Kernen). Im Stack stehen deshalb `healthcheck: disable: true` auf worker+scheduler, `--concurrency=2` und `mem_limit`. **`cpus:` geht auf Synology nicht** (Kernel ohne CFS-Quota, der Stack-Deploy schlägt sonst fehl).
|
||||
- 📜 **~~Omada ES208G = Easy Managed~~ — gegenstandslos** (Gerät abgebaut 2026-07-23, Controller gelöscht).
|
||||
- **L3-Router = `crs326-core` (TP21.1 ✅ 2026-08-08, ersetzt ap1).** Bridge vlan-filtering=yes; VLAN-SVIs `.1` je VLAN mit DHCP (7 Server / 54 Leases); DNS über **DoH** zum CF-Gateway, `verify-doh-cert=yes` (TP23 Stufe 1 ✅ 2026-08-18). Die alte Notiz „core hat kein CA" war zu pessimistisch: es braucht kein Bundle, sondern **genau einen Root** (GTS Root R4, ~1 KB) — die Kette ist Leaf → GTS WE1 → R4. **Beide Firewall-Ketten enden seit 2026-08-18 mit `drop`** (TP5 E3/E4); `TP10-seal-lan` ist `disabled`, Mgmt-Input kommt nur noch aus VLAN10, von HA (`8728`), von Nautobot (`22`) und über `cf-mesh`. **ap1/ap2 sind NUR noch APs** — kein DHCP/DNS/NAT/Routing/WG dort. (Die frühere „TP4: ap1 ist L3-Router"-Zeile ist überholt.)
|
||||
- **✅✅ Edge-Cutover (2026-07-01; Router-Umzug 2026-08-08): FritzBox ausgebaut, Arris im Bridge-Mode.** Liwest hat die FB6660 dauerhaft gegen ein Arris getauscht (physisch weg). WAN terminiert auf **`crs326-core vlan100`** (VLAN100-Transfer), core zieht die öffentliche Liwest-IP `90.146.33.177` → kein Double-NAT. **core = Public-Edge + WG-Hub** (fm/dw live), NICHT mehr ap1. Telefonie nicht weitergeführt. Runbook `routeros/wan-cutover-runbook.md` (ap1-Teile historisch).
|
||||
- **hAP `device-mode=home` (ap1/ap2): scheduler + fetch blockiert** (`not allowed by device-mode`) → AP-Provisionierung idempotent **ohne** Scheduler. **`[/ping]` gibt nil in 7.23**. REST je AP `http://10.40.99.11|12/rest` (Creds = `ROUTER_AP{1,2}` in `~/.claude.json`). **Der L3-Router crs326-core ist KEIN device-mode=home** → dort gehen scheduler/fetch (z.B. DDNS-Updater).
|
||||
- **Erstzugriff Werks-hAP am CRS-Fabric:** Uplink `ether1` = Werks-WAN → `192.168.88.1` (LAN) darüber NICHT erreichbar. Weg: Gerät auf **LAN-Port (ether2)** patchen ODER **Core-Temp-Pfad** `192.168.88.2/24` auf core-`bridge` + srcnat masq `192.168.88.0/24`, dann scp+`/import` (danach Temp-Pfad + stale ARP `.88.1` entfernen). Winbox-Neighbors zeigt die APs NICHT (MNDP pro-VLAN, Mgmt auf vlan99) — kein L2-Fehler.
|
||||
- 🖨️ **ZWEI Drucker, nicht verwechseln (geklärt 2026-08-23):** der **Brother MFC** ist der
|
||||
*eigentliche* Drucker und hängt auf **VLAN10** (`10.40.10.6`, MAC `30:05:5C:78:86:20`, **statisch**,
|
||||
keine DHCP-Reservierung) — er steht damit im *selben* VLAN wie die Macs, die `forward`-Kette greift
|
||||
dort gar nicht. Der **P750W ist der Etikettendrucker** auf **VLAN30** (`10.40.30.5`) — nur für ihn
|
||||
gilt der VLAN-übergreifende SNMP-Fall unten.
|
||||
- **P750W (Brother, VLAN30) läuft auf `dirtyvader-iot`** (User-bestätigt 2026-08-08) — **kein** Access-List-Pin nötig; die alte „nur am co-located L3-GW-AP erreichbar"-Logik ist mit L3-GW auf dem Core hinfällig. Coverage-Kanten/Terrasse grenzwertig (−76..−89 dBm); TX am AT-Max (18/15 dBm); echter Fix = Platzierung. SSoT `docs/wifi-p750w-coverage-and-map.md`. ⚠️ **Power-Save ist KEIN Thema dieses Netzes** — die früher hier stehende Merkregel „DHCP-geht-aber-Ping-nicht = ARP/Power-Save" war eine Fehldiagnose (2026-08-13 korrigiert: der Drucker war schlicht ausgeschaltet, das Ping-Symptom war WARP, s.u.). Nicht wieder einführen.
|
||||
- **Ping auf JEDE lokale IP tot, aber Web/App/Print (TCP) geht überall = WARP-Client lokal versehentlich noch aktiv** (2026-08-13 live beobachtet, BF vor Ort): `cloudflared`/WARP ist QUIC-Proxy, kann **kein ICMP**, reicht TCP aber durch — auch wenn man physisch im BF-LAN steht, nimmt der Client bei aktivem WARP trotzdem den Tunnel-Umweg. **Fix: WARP trennen**, kein Router-Eingriff nötig. **Vor jeder Firewall-/Mangle-/VLAN-Suche zuerst prüfen**, wenn das Symptom „Ping auf ALLES tot, TCP geht" lautet — das ist ein Client-, kein Netzproblem. Strukturell schließt **TP16.1** (natives WireGuard-CF-Mesh auf `crs326-core` statt cloudflared-QUIC) genau diese ICMP-Lücke. Memory `ref_warp_local_icmp_trap`.
|
||||
|
||||
- 🔥 **Die Firewall ist seit 2026-08-18 deny-by-default — in BEIDEN Ketten.** Ein neuer Dienst über eine VLAN-Grenze braucht eine explizite Regel, sonst funktioniert er nicht. Diagnosewerkzeug = **Log-Regel direkt vor dem Drop** (`TP5-WOULD-DROP` / `TP5-INPUT-WOULD-DROP`, Kollektor `10.40.20.18:514`, Dateien Docker-LXC `/opt/tp5-syslog/log/`). **Erst dort nachsehen, bevor irgendwo anders gesucht wird.** **MAIN→INFRA/MEDIA/MGMT/IOT offen** (IOT seit TP5 E5, 2026-08-19); **IOT→MAIN bewusst zu.** Ad-hoc-Zugriff aus MAIN in geschlossenes VLAN (UNTRUSTED, GUEST): `/ip firewall address-list add list=jit-admin address=<deine-IP> timeout=1h`.
|
||||
- 🖨️ **Drucker fällt mit udp/161 (SNMP) auf, nicht 631** — macOS fragt zuerst SNMP, bricht dann ab. Bei „Gerät antwortet nicht" das Log lesen, statt den erwarteten Port zu raten.
|
||||
- **Beide APs = CAPs, Manager ist `crs326-core` (TP28 ✅).** Mesh = **NEIN** (beide APs haben wired Trunk-Uplink → Loop-Risiko). Der Core braucht dafür **kein** `wifi-qcom` (capsman steckt im Basispaket).
|
||||
- 🔴🔴 **`/interface wifi cap slaves-datapath` MUSS auf beiden APs gesetzt sein** (`dp-caps-slaves`, `bridge=bridge`, **ohne** `vlan-id`). Fehlt es, assoziieren Clients auf `-iot`/`-media`/`-untrusted`/`-guest` sauber und bekommen **nie DHCP**, während `dirtyvader-main` weiterläuft (so lagen IOT+MEDIA am 2026-08-18 ~14 h). **Abnahme für jeden WLAN-Umbau = frische DHCP-Lease pro SSID — NICHT die Registration-Table.**
|
||||
- 🔴 **`/interface wifi scan` geht am CAP NICHT — nur am Manager.** `cap-wifiN`-Zuordnung vorher über `radio-mac` bestimmen, sie ist nicht stabil.
|
||||
- 🔴 **CAPs finden den Manager per L2-Discovery** (`discovery-interfaces=vlan99`, `current-caps-man-address` = MAC). Deshalb **MNDP (udp/5678)** + **CAPWAP (udp/5246-5247)** in der input-Kette des Core offen lassen.
|
||||
- 🔁 **Slave-VAPs ändern = zweimal arbeiten:** nach `slave-configurations`-Änderung `/interface wifi capsman remote-cap provision [find where identity="apX"]` nachziehen (beide Richtungen). Abnahme nicht am Kommentar (`comment~"ssid"`), sondern `datapath.vlan-id` bzw. Interface-Zahl **am CAP**.
|
||||
- 🔎 **„connect ok, kein DHCP":** zuerst `torch` auf dem VLAN am Core UND den TP5-Drop-Log lesen. Beide leer ⇒ nichts wird verworfen, es kommt nichts an → L2 (Bridge-Mitgliedschaft/Tagging), nicht Regelwerk.
|
||||
- 📝 **WLAN-Log-Topic heißt `wireless`, nicht `wifi`.** Zwei Regeln auf dem **Core** (nicht den CAPs) schicken `wireless` + `caps` zum Kollektor `10.40.20.18:514`. ⚠️ **RouterOS UND-verknüpft Topics in EINER Regel** — `topics=wireless,caps` matcht **nichts**, es braucht zwei Regeln. `/system logging` steht unter der ComplianceRule `system-logging`. **Merkregel: bevor man ein sporadisches Problem sucht, erst prüfen, ob es überhaupt mitgeschrieben wird.**
|
||||
- 🧮 **Log-Burst über VIELE MACs = Infrastruktur, über EINE MAC = Client.** Vor jeder Client-Diagnose erst `grep " caps,"` + `/system resource get uptime` lesen (ein Massenburst war oft ein geplanter Reboot). Umkehrschluss: 93/93 `reauthenticating` von einer MAC auf einer SSID = echter Client-/SSID-Befund.
|
||||
- 🗺️ **Die `cap-wifiN`-Karte verschiebt sich bei JEDEM CAP-Rejoin.** Vor jeder Auswertung `/interface wifi radio print terse proplist=cap,radio-mac,interface` am Manager lesen; liegt zwischen Ereignis und Auswertung ein Rejoin, Zuordnung als **unbekannt** behandeln statt raten.
|
||||
- 🔑 **CAPsMAN verteilt KEINE Login-Passwörter — nur WLAN-PSKs.** WPA-Passphrase (`/interface wifi security`) liegt am Manager ✅; Geräte-Login (`/user`) ist eine **lokale DB je Gerät**. Das einheitliche Admin-PW ist eine **Hand-Konvention**, keine Automatik.
|
||||
- 🔴 **Ein neuer CAP braucht `configuration.manager=capsman` auf JEDEM Master-Radio.** `/interface wifi cap set enabled=yes` allein reicht nicht — im Werkszustand ist das Feld leer (= `local`).
|
||||
- 🔌 **Werks-MikroTik am Trunk: `ether1` ist WAN.** Interface-Liste `WAN` → Discovery aus (kein MNDP), Input-TCP (22/80/8291/8728) **zu**, ICMP **erlaubt** → es pingt, wirkt erreichbar. **Weg rein ist die LAN-Seite** (`ether2`), dort `/interface list member remove [find where interface=ether1]`.
|
||||
- 🔀 **`pvid` ist INGRESS, `tagged`/`untagged` ist EGRESS.** Werksgerät-Port gehört **untagged** in die VLAN-Zeile.
|
||||
- 🕳️ **Ein neues Gerät ohne `secrets_group` erzeugt eine LÜCKE, keine Fehlermeldung.** Backup-Job scheitert still, keine Compliance-Einträge. 🔴 **Eine gleichbleibende Compliance-Zahl nach dem Hinzufügen eines Geräts ist ein BEFUND, kein Erfolg.** Zweite Voraussetzung: dedizierte Service-Accounts `nautobot`/`ha` existieren auf einem Werksgerät nicht.
|
||||
- 🔢 **Firewall-Regelnummern aus `print where` sind Handles, keine Positionen.** Beweis ist der **Zähler** (`print stats`), nicht die Zahl.
|
||||
- 🔌 **`crs326-core` hat KEIN PoE-out** (CRS326-24G-2S+). AP am Core-Port braucht eigenes Netzteil; der **cAP ax bringt Netzteil + PoE-Injector im Karton** mit.
|
||||
- 🧩 **Die AP-Flotte ist heterogen (cAP ax = 2 Ports vs hAP = 5).** Bridge-Ports kommen aus **`ap.extra_ports`** (ConfigContext `ap-common`; ap3 überschreibt per `local_config_context_data` mit `[]`). ⚠️ **Nautobot merged Dicts tief, Listen ERSETZEND.** **Bevor ein zweites Modell derselben Rolle dazukommt, Literal-Blöcke lesen** („Gerätetyp" vs „Rolle").
|
||||
- 🔵 **Container auf MikroTik:** ap1/ap2 = `arm64`, 95 MB Flash frei → **ein** kleiner Container ok (~70 MB nutzbar). **`crs326-core` dagegen NIE** (`arm` 32-bit, 2,9 MB frei) — SPOF, kein Anwendungs-Host (Hardware-Tatsache). **`cloudflared` braucht kein TUN** (Userspace-Proxy) → RouterOS-Container möglich; der WARP-Connector nicht (braucht TUN). **Schwere Container laufen weiterhin auf NAS/pve**, nicht am Router.
|
||||
- **hAP `device-mode=home` (ap1/ap2): scheduler + fetch blockiert** → AP-Provisionierung idempotent **ohne** Scheduler. **`crs326-core` ist KEIN `device-mode=home`** → dort gehen scheduler/fetch (DDNS-Updater).
|
||||
- **Erstzugriff Werks-hAP am CRS-Fabric:** Uplink `ether1` = Werks-WAN → `192.168.88.1` (LAN) darüber NICHT erreichbar. Weg: **LAN-Port (ether2)** patchen ODER **Core-Temp-Pfad** `192.168.88.2/24` + srcnat masq `192.168.88.0/24`, dann scp+`/import` (danach Temp-Pfad + stale ARP `.88.1` entfernen).
|
||||
- 🖨️ **ZWEI Drucker, nicht verwechseln:** **Brother MFC** = eigentlicher Drucker, **VLAN10** (`10.40.10.6`, MAC `30:05:5C:78:86:20`, statisch). **P750W** = Etikettendrucker, **VLAN30** (`10.40.30.5`), läuft auf `dirtyvader-iot`. P750W-Coverage SSoT `docs/wifi-p750w-coverage-and-map.md`. ⚠️ **Power-Save ist KEIN Thema** (frühere „DHCP-geht-aber-Ping-nicht=ARP/Power-Save"-Merkregel war Fehldiagnose).
|
||||
- **Ping auf JEDE lokale IP tot, aber TCP geht überall = WARP-Client lokal noch aktiv.** `cloudflared`/WARP ist QUIC-Proxy, kann **kein ICMP**. **Fix: WARP trennen**, kein Router-Eingriff. Memory `ref_warp_local_icmp_trap`.
|
||||
- **L3-Router = `crs326-core` (TP21.1 ✅, ersetzt ap1).** Bridge vlan-filtering=yes; VLAN-SVIs `.1` je VLAN mit DHCP (7 Server/54 Leases); DNS über **DoH** zum CF-Gateway `verify-doh-cert=yes` (TP23 ✅). Beide Firewall-Ketten enden mit `drop`; Mgmt-Input nur aus VLAN10, von HA (`8728`), von Nautobot (`22`) und über `cf-mesh`. **ap1/ap2 sind NUR noch APs.**
|
||||
- **✅✅ Edge-Cutover:** FritzBox aus, Arris Bridge-Mode. WAN terminiert auf **`crs326-core vlan100`**, öffentliche Liwest-IP `90.146.33.177`, kein Double-NAT. **core = Public-Edge + WG-Hub.** Runbook `routeros/wan-cutover-runbook.md`.
|
||||
- **mgmt-VLAN-Wechsel am Switch = Lockout-Risiko:** VLAN muss existieren + Port muss es tragen, in einem Schritt.
|
||||
|
||||
**Diagnose-Fallen (Detail in [`docs/gotchas.md`](docs/gotchas.md), hier nur die Merkregel):**
|
||||
- ⏰ Zeitstempel zwischen Geräten widersprüchlich → `/system ntp client get status` (Zeitquelle = Core, DHCP-Option 42, ComplianceRule `system-ntp`).
|
||||
- 🔌 Vor jedem geplanten LXC-Neustart `systemctl --failed` lesen + danach Ports gegen vorher vergleichen.
|
||||
- 🧪 „alles gleichzeitig kaputt" → **zuerst das eigene Werkzeug verdächtigen** (`nc -z` lügt), dann das Netz.
|
||||
- 📡 Sonos sucht hunderte Male ohne Verbindung → **Relay, nicht Netz** (Richtungsbilanz `grep -c "via eth1"` vs `"via eth0"`).
|
||||
- ⏱️ Konstanter 10-s-Hänger = **DNS-Timeout**, nicht Last (DB-Reverse-Lookup auf toten Resolver).
|
||||
- 🏠 HA-`http:`-Block in `configuration.yaml` **wirkungslos** → `.storage/http` + WS `http/config/configure`→`promote`.
|
||||
- 🐳 Nautobot auf DS923: **Image-Healthcheck aus**; `cpus:` geht auf Synology nicht.
|
||||
|
||||
---
|
||||
|
||||
@@ -377,43 +165,17 @@ Verhaltensregeln für nachfolgende AI-Sessions — kleiner, klarer, weniger Drif
|
||||
- **Eine große Fragenrunde statt vieler kleiner.** Wenn der Plan von Annahmen abhängt, sammle die Annahmen in **einer** Runde, nicht in 3–4 Mikrorunden.
|
||||
- **Master-Frage früh stellen.** Bei jedem Repo, jeder Datei, jedem Tooling: „Was ist die SSoT, wo lebt der Master, was ist Mirror?" — **bevor** du eine Lösung vorschlägst.
|
||||
- **Drift zuerst, Lösungen später.** Drift-Tabelle posten + fragen, was richtig ist. Keine Lösungen vor dem Befund.
|
||||
- **MCP-Erstellung/-Stilllegung = aktiv melden** (2026-08-27, Christoph-Direktive). Wer einen neuen MCP anlegt, ändert oder abschaltet — **meldet das proaktiv** an den User, nicht stillschweigend im Commit. Begründung: jede AI-Session ist gleichzeitig **Oberhand über Container** (MCP-Hosts) und **Garbage-Collector** auf Infra-Ebene — wenn Drift unbemerkt entsteht, sammelt sich Müll.
|
||||
- **MCPs brauchen Host-Verifikation** vor Edit. Wenn der MCP verändert/editiert wird: vorher per Live-Read (nc/ps/curl) prüfen, ob der Host noch antwortet. Beispiel: 2026-08-27 — `shelly-mcp` zeigte auf `10.40.20.30:3030`, aber kein Host antwortet. Konfig auf `enabled=false`, dokumentieren statt hart fehlschlagen zu lassen. **(Stand 2026-08-28: shelly-mcp wird auf DS923 `10.40.20.180` deployt — `BUILD.md` liegt bereit.)**
|
||||
- **Mac mini ist n8n-Host, nicht zentraler MCP-Host** (User-Direktive 2026-08-28): MCP-Dienste gehören auf die **DS923** (`nas-bf`, Portainer Endpoint 14) — nicht auf die User-Workstation. Aktuell laufen gitea-mcp + n8n-mcp **noch** auf dem macmini (historisch), gehören **bei Gelegenheit** auf die DS923 umgezogen.
|
||||
- **Tote MCP-Skripte → ins Projekt-Repo, nicht global.** Wenn ein MCP nur für ein Projekt relevant ist (`cannaleo` für terpene, `terpene-pg` für terpene): gehört in `~/dev/<projekt>/mcp.json` + Skript im Projekt-Repo. Globale `~/.claude.json`/`~/.config/opencode/opencode.jsonc` = nur projekt-übergreifende Tools.
|
||||
- **opencode-Skills: Default-Pfad kopieren, NICHT symlinken** (TP35 Schicht 6, 2026-08-28). Claude-Skill in `~/.claude/skills/<name>/SKILL.md` ist die Quelle; für opencode gehört er nach `~/.config/opencode/skills/<name>/SKILL.md` (`cp`, nicht `ln -s`). Begründung: opencode scannt diesen Pfad als Default — **kein** `skills.paths`-Eintrag in `~/.config/opencode/opencode.jsonc` nötig, das wäre redundant. Symlink wurde in der Inventur-Session testweise angelegt, hat Christophs "opencode = eigene Pfade"-Direktive verletzt und wurde wieder entfernt. Schema-Referenz: <https://opencode.ai/config.json> → `Config.skills.paths`. **Lehre aus dem Fall:** opencode-CLI-Verhalten nicht raten, Doku lesen — und ein "load-bearing"-Detail (Default-Scan-Pfad) zuerst verifizieren, bevor man ihn umgeht.
|
||||
- **Skills sind IMMER global, niemals pro Projekt** (Christoph-Direktive 2026-08-28). `projektstatus` und alle übrigen Skills liegen **ausschließlich** unter `~/.claude/skills/` und `~/.config/opencode/skills/`. **Kein** Repo-lokaler Skill unter `.opencode/skill/` oder `.claude/skill/`. Begründung: keine Redundanz, keine Drift zwischen Skill-Versionen, eine Quelle pro Skill. **Merkregel:** wer einen Skill „überschreiben" will, editiert **die globale Datei** oder schreibt einen zweiten Skill mit anderem Namen — niemals ein Repo-Override.
|
||||
- **Kommentar in Code ≠ Verhalten.** `mcp/shelly/.env` trug den Kommentar "gitignored, NICHT committen" — es gab aber **kein** `mcp/shelly/.gitignore`. Der Kommentar war Wunsch, nicht Wirklichkeit. `routeros/cloudflared/credentials.json` (Cloudflare-Tunnel-PrivateKey base64) lag ungegittoret im Working Tree. **Merkregel:** Behauptungen in Kommentaren/Konfig-Kommentaren durch `git check-ignore -v <datei>` (oder `git status`) verifizieren, bevor man darauf vertraut. **Lessons-Block erweitert 2026-08-28 nach `commit`-Session.**
|
||||
- **opencode-TUI: führendes Leerzeichen vor `/command` manchmal nötig** (Beobachtung 2026-08-28, Skill „projektstatus" getestet; **identifiziert als bekannter Bug**). Symptom: Tippen von `/projektstatus` führt nicht zum Skill, ` /projektstatus` (führendes Leerzeichen) schon. Doku <https://opencode.ai/docs/commands> zeigt den Aufruf ohne Space. **Verifiziert:** Issue **anomalyco/opencode#27528** „ACP: prompt starting with '/' is silently dropped if command name is not recognized" — Status **Not planned (skipped)**, closed 2026-07-14. Maintainer haben es also gesehen und nicht gefixt. **Konsequenz:** Workaround (führendes Leerzeichen) ist der offizielle Pfad; Skill selbst ist nicht kaputt. Vor jedem neuen opencode-Build prüfen, ob das Verhalten weg ist.
|
||||
- **Manuell gepflegte Doku mit Live-Daten verliert gegen die Realität, wenn sie lang wird.** Befund 2026-08-28: der „Nächster Schritt (2026-08-26): TP31 Schritt 5"-Satz stand zwei Tage nach seiner Formulierung noch im Ist-Stand-Block, obwohl ich in derselben Zeit drei Folge-Sessions eröffnet hatte. **Merkregel pro Edit:** drei Stellen berücksichtigen — Quick-Index (Tabelle), TP-Abschnitt, Ist-Stand-Block. Keine schlägt die andere; wer eine ändert, **muss** die anderen mitlesen. Strukturelle Antwort = TP36 (SSoT via Nautobot, Datei wird read-only Snapshot) — eigene Session, nicht nebenbei. **Lessons-Block erweitert 2026-08-28 nach Skill-Verifikation in opencode.**
|
||||
- **`sed 's/=.*/=<set>/'` maskiert NICHT, wenn das `.*` greedy ist und der Wert Slashes/Backslashes enthält** (Befund 2026-08-28, shelly-mcp-Deploy). Ich habe `sed 's/SHELLY_DEVICE_PW=.*/<set>/'` auf die Stack-YML angewendet — der **Wert selbst** wurde in den Output geschrieben, weil mein Replacement-String nicht greedy genug war und ich die Pipe nicht mit `cat -v` oder `head -15 | sed 's|=.*|=<set>|'` abgesichert hatte. Folge: **`SHELLY_DEVICE_PW` ist jetzt als verbrannt zu betrachten** — wandert in **TP30 Posten #3** mit Hochstufung. **Merkregel:** bei jeder Ausgabe von Secret-haltigen Dateien entweder `head -c <n>` (Abbruch nach Bytes), oder die Datei vor dem Read mit `tr -d '\n' | sed 's|=.*|=<set>|'` über einen Stream-Filter schicken — **niemals** einfach `cat` und auf einen Replacement-Pattern vertrauen. Vergleichbar zum PSK-Leak 2026-08-24.
|
||||
|
||||
- **Vor jeder Drift-Bewertung `python3 nautobot/gc-transaktion.py status` lesen** (Befund 2026-08-31,
|
||||
zweimal am selben Tag gestolpert). **Drift aus einem laufenden Versuch sieht identisch aus wie ein
|
||||
Versehen** — die Compliance-Zahl unterscheidet nicht. Konkret: `crs326-core`/`wifi-security`
|
||||
(`ft-over-ds=no`) wurde dem 7.24.1-Upgrade zugeschrieben und als „braucht eine Entscheidung"
|
||||
weitergereicht; tatsächlich ist es **TP25 Fund E Hebel 1**, um 08:09 bewusst gesetzt, während der
|
||||
Core erst um 14:36 gehoben wurde, geführt als offene Transaktion `TP25-fund-e-ft-over-ds`.
|
||||
🔴 **Eine solche Drift grün zu machen macht den Versuch unsichtbar.** Umgekehrt gilt: was **dauerhaft**
|
||||
gelten soll (z.B. das `veth`/`masquerade` aus TP16.3 §3.4), gehört **nicht** in eine Transaktion,
|
||||
sondern ins **Soll** — Transaktion = befristeter Versuch, Template = Dauerzustand.
|
||||
⚠️ Das Skript löst den Token-Pfad gegen die Repo-Wurzel auf und findet ihn **aus einem Worktree
|
||||
heraus nicht** (`210-nautobot.yml` liegt nur im Haupt-Checkout) — die Transaktionsliste kommt
|
||||
trotzdem, nur die Compliance-Spalte fehlt dann.
|
||||
- **Maskierungs-Muster auf den SCHLÜSSELNAMEN ankern, nie auf ein Trennzeichen** (Befund 2026-08-31,
|
||||
zweiter Fall dieser Klasse nach `SHELLY_DEVICE_PW`). `grep -n "TOKEN" datei | sed 's/:.*TOKEN/…/'`
|
||||
maskiert **nicht**: `grep -n` stellt `<zeile>:` voran, und das `:.*TOKEN` matcht diesen Präfix statt
|
||||
den Wert — ein nachgeschaltetes `cut -c1-60` schneidet dann genau nichts weg. So sind zwei
|
||||
Nautobot-Tokens ins Transkript geraten. **Sicher ist nur**
|
||||
`sed -E 's/(TOKEN|PASSWORD|SECRET|KEY|PASS)=.*/\1=<redacted>/'`. Und: **Werte nie suchen, um sie zu
|
||||
sehen** — den Schlüssel per `sed -n 's/^…KEY=//p'` direkt in eine Variable holen, nie über `grep`
|
||||
mit Ausgabe. Vergleichbar zum PSK-Leak 2026-08-24 und zum `sed`-Fall 2026-08-28.
|
||||
- **Vor jedem Flash-Budget auf MikroTik `docker export | wc -c` messen, nicht `docker image inspect
|
||||
.Size`** (Befund 2026-08-31, TP16.3). `.Size` meldete für `cloudflared` 27,5 MB, entpackt waren es
|
||||
**69,5 MiB** — das offizielle Image passt damit auf keinen hAP/cAP. RouterOS braucht beim Import
|
||||
**Tar UND entpacktes Abbild gleichzeitig**. 🔴 **Und `docker save` schreibt seit Docker 25
|
||||
(containerd-Store) OCI statt docker-v1** → RouterOS meldet `no config found in manifest`, was nach
|
||||
kaputtem Image klingt und ein Formatproblem ist. Beides in `docs/tp16.3-bf-public-ap-connector-runbook.md` §2.2.
|
||||
- **MCP-Erstellung/-Stilllegung = aktiv melden** (2026-08-27, Christoph-Direktive). Wer einen neuen MCP anlegt, ändert oder abschaltet — **meldet das proaktiv** an den User, nicht stillschweigend im Commit. Jede AI-Session ist zugleich **Oberhand über Container** und **Garbage-Collector** auf Infra-Ebene.
|
||||
- **MCPs brauchen Host-Verifikation** vor Edit: per Live-Read (nc/ps/curl) prüfen, ob der Host noch antwortet. Beispiel: `shelly-mcp` zeigte auf `10.40.20.30:3030`, kein Host antwortet → `enabled=false`, dokumentieren. (Stand 2026-08-28: shelly-mcp auf DS923 `10.40.20.180` deployt.)
|
||||
- **Tote MCP-Skripte → ins Projekt-Repo, nicht global.** Projekt-spezifische MCPs (`cannaleo`, `terpene-pg`) → `~/dev/<projekt>/mcp.json` + Skript im Projekt-Repo. Globale `~/.claude.json`/`opencode.jsonc` = nur projekt-übergreifende Tools.
|
||||
- **opencode-Skills: Default-Pfad kopieren, NICHT symlinken** (TP35 Schicht 6). Claude-Skill `~/.claude/skills/<name>/SKILL.md` ist die Quelle; für opencode `cp` nach `~/.config/opencode/skills/<name>/SKILL.md` (kein `skills.paths`-Eintrag nötig). Symlink verletzt Christophs „opencode = eigene Pfade"-Direktive. Lehre: opencode-Verhalten nicht raten, Doku lesen.
|
||||
- **Skills sind IMMER global, niemals pro Projekt** (Christoph-Direktive). Nur `~/.claude/skills/` und `~/.config/opencode/skills/`. Kein Repo-Override. Wer überschreiben will, editiert die globale Datei oder schreibt einen zweiten Skill.
|
||||
- **Kommentar in Code ≠ Verhalten.** `mcp/shelly/.env` trug „gitignored", es gab aber kein `.gitignore`. Behauptungen in Kommentaren per `git check-ignore -v <datei>` verifizieren, bevor man darauf vertraut.
|
||||
- **opencode-TUI: führendes Leerzeichen vor `/command` manchmal nötig** (bekannter Bug, anomalyco/opencode#27528, „Not planned"). Workaround (führendes Leerzeichen) ist der offizielle Pfad.
|
||||
- **Manuell gepflegte Doku verliert gegen die Realität, wenn sie lang wird.** Wer eine Stelle ändert, muss die anderen mitlesen (Quick-Index, TP-Abschnitt, Ist-Stand). Strukturelle Antwort = TP36 (SSoT via Nautobot).
|
||||
- **Maskierungs-Muster: auf den SCHLÜSSELNAMEN ankern, nie auf ein Trennzeichen.** Sicher ist nur `sed -E 's/(TOKEN|PASSWORD|SECRET|KEY|PASS)=.*/\1=<redacted>/'` — greedy `sed 's/=.*/…/'` schneidet Slashes/Backslashes nicht. **Werte nie suchen, um sie zu sehen** — den Schlüssel per `sed -n 's/^…KEY=//p'` direkt in eine Variable holen. Drei Leaks dieser Klasse: PSK 2026-08-24, `SHELLY_DEVICE_PW` 2026-08-28, Nautobot-Tokens 2026-08-31.
|
||||
- **Vor jeder Drift-Bewertung `python3 nautobot/gc-transaktion.py status` lesen** (Befund 2026-08-31, zweimal gestolpert). **Drift aus einem laufenden Versuch sieht identisch aus wie ein Versehen** — die Compliance-Zahl unterscheidet nicht. 🔴 **Eine solche Drift grün zu machen macht den Versuch unsichtbar.** Was dauerhaft gelten soll, gehört ins **Soll/Template**, nicht in eine Transaktion. ⚠️ Das Skript findet den Token aus einem Worktree heraus nicht (`210-nautobot.yml` nur im Haupt-Checkout).
|
||||
- **Vor jedem Flash-Budget auf MikroTik `docker export | wc -c` messen, nicht `docker image inspect .Size`** (Befund 2026-08-31, TP16.3). `.Size` meldete für `cloudflared` 27,5 MB, entpackt **69,5 MiB**. 🔴 **`docker save` schreibt seit Docker 25 OCI statt docker-v1** → RouterOS meldet `no config found in manifest`. Beides in `docs/tp16.3-bf-public-ap-connector-runbook.md` §2.2.
|
||||
|
||||
**Checkliste vor jedem Edit-Vorschlag:**
|
||||
|
||||
@@ -422,4 +184,4 @@ Verhaltensregeln für nachfolgende AI-Sessions — kleiner, klarer, weniger Drif
|
||||
3. Habe ich **eine** Fragenrunde mit allen offenen Annahmen gestellt?
|
||||
4. Habe ich für jeden Edit **deine** Bestätigung eingeholt (nicht nur für den Plan-Output)?
|
||||
5. **MCP-Änderung?** Vorher Live-Check (Port/Prozess), aktiv melden, Garbage-Collector-Pflicht.
|
||||
6. **Output-Länge.** Christoph hat sich heute (2026-08-28) wiederholt in langen Outputs verirrt. **Merkregel:** eine Antwort pro Edit-Vorschlag, eine Zeile pro Bestätigung, keine Wiederholung dessen, was im Diff steht. Keine Frage-Runden, wenn keine echte Annahme kollidiert. Erklärungstexte unter 6 Zeilen, sofern nicht ausdrücklich verlangt.
|
||||
6. **Output-Länge.** Christoph hat sich wiederholt in langen Outputs verirrt. **Merkregel:** eine Antwort pro Edit-Vorschlag, eine Zeile pro Bestätigung, keine Wiederholung dessen, was im Diff steht. Keine Frage-Runden, wenn keine echte Annahme kollidiert. Erklärungstexte unter 6 Zeilen, sofern nicht ausdrücklich verlangt.
|
||||
|
||||
@@ -31,10 +31,11 @@ dns:
|
||||
ratelimit_subnet_len_ipv6: 56
|
||||
ratelimit_whitelist: []
|
||||
refuse_any: true
|
||||
# TP45 C2 (2026-09-22): der Core ist der EINZIGE Upstream — AGH macht nur noch
|
||||
# Filtering. Die interne Zone und die bare Namen kommen damit aus derselben Quelle
|
||||
# wie fuer jeden anderen Client. DoH nach draussen macht allein der Core.
|
||||
upstream_dns:
|
||||
- https://px35ynlol3.cloudflare-gateway.com/dns-query
|
||||
- '[/bf.emsenhuber.net/]10.40.20.1'
|
||||
- '[/40.10.in-addr.arpa/]10.40.20.1'
|
||||
- 10.40.20.1
|
||||
upstream_dns_file: ""
|
||||
bootstrap_dns:
|
||||
- 1.1.1.1
|
||||
@@ -71,7 +72,7 @@ dns:
|
||||
ipset_file: ""
|
||||
bootstrap_prefer_ipv6: false
|
||||
upstream_timeout: 10s
|
||||
private_<redacted>: []
|
||||
private_networks: []
|
||||
use_private_ptr_resolvers: true
|
||||
local_ptr_upstreams: []
|
||||
use_dns64: false
|
||||
@@ -158,85 +159,9 @@ filtering:
|
||||
blocking_mode: default
|
||||
parental_block_host: family-block.dns.adguard.com
|
||||
safebrowsing_block_host: standard-block.dns.adguard.com
|
||||
rewrites:
|
||||
- domain: pbs.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: portainer.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: pve.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: gitea-mcp.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: n8n-mcp.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ha-mcp.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: pve-mcp.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: z2m25.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: iobroker.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ccu.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: nautobot.bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: vw.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: hass.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: n8n.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: gitea.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: ds-bf.emsenhuber.net
|
||||
answer: 10.40.20.40
|
||||
enabled: true
|
||||
- domain: nas.bf.emsenhuber.net
|
||||
answer: 10.40.20.10
|
||||
enabled: true
|
||||
- domain: macmini.bf.emsenhuber.net
|
||||
answer: 10.40.20.167
|
||||
enabled: true
|
||||
- domain: mfc.bf.emsenhuber.net
|
||||
answer: 10.40.10.6
|
||||
enabled: true
|
||||
- domain: tv.bf.emsenhuber.net
|
||||
answer: 10.40.40.11
|
||||
enabled: true
|
||||
- domain: docker.bf.emsenhuber.net
|
||||
answer: 10.40.20.126
|
||||
enabled: true
|
||||
- domain: platform-pg.bf.emsenhuber.net
|
||||
answer: 10.40.20.20
|
||||
enabled: true
|
||||
- domain: core.bf.emsenhuber.net
|
||||
answer: 10.40.99.1
|
||||
enabled: true
|
||||
- domain: ap1.bf.emsenhuber.net
|
||||
answer: 10.40.99.11
|
||||
enabled: true
|
||||
- domain: ap2.bf.emsenhuber.net
|
||||
answer: 10.40.99.12
|
||||
enabled: true
|
||||
- domain: branch.bf.emsenhuber.net
|
||||
answer: 10.40.99.2
|
||||
enabled: true
|
||||
# TP45 C2 (2026-09-22): alle 26 Rewrites entfernt — sie waren die zweite, stille
|
||||
# Definitionsquelle. Der Core haelt jeden dieser Namen als /ip dns static.
|
||||
rewrites: []
|
||||
safe_fs_patterns:
|
||||
- /opt/AdGuardHome/userfilters/*
|
||||
max_http_size: 256MB
|
||||
|
||||
162
docs/gotchas.md
Normal file
162
docs/gotchas.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Gotchas — ausführliche Diagnose-Anleitungen (Detail-Doku)
|
||||
|
||||
Diese Datei trägt die ausführlichen Diagnose-Blöcke, die in `CLAUDE.md` nur noch als 1-Zeilen-Merkregel
|
||||
mit Zeiger stehen. Volltext hier, damit der Kern-Index schlank bleibt. Alle Blöcke sind **aktiv**
|
||||
(querschnittliche Fallen), keine Historie.
|
||||
|
||||
---
|
||||
|
||||
## SSDP-Relay für die macOS-Sonos-App
|
||||
|
||||
LXC **108 `mcast-relay`** auf pve, zweibeinig `10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40),
|
||||
`multicast-relay` mit **`--noMDNS`** (mDNS macht der Core). Unit im Repo:
|
||||
**`mcast-relay/multicast-relay.service`**. **Zwei Beine sind zwingend** — SSDP ist link-lokal mit TTL 1,
|
||||
ein Relay muss Mitglied beider Broadcast-Domänen sein; Routing hilft nicht.
|
||||
|
||||
- 🔴 **`--ssdpUnicastAddr` gehört in ein zweibeiniges Relay NICHT — die Flagge hat den Hinweg 7 Wochen
|
||||
lang blockiert** (drin 2026-08-18, ausgebaut **2026-08-25**). Sie schreibt in `multicast-relay.py`
|
||||
**Z. 630** die Quelle *jedes* SSDP-Pakets an `239.255.255.250:1900` auf `<addr>:1901` um (**auch
|
||||
NOTIFY**); beim Senden greift dann **Z. 691**
|
||||
`if srcAddr == ssdpUnicastAddr and not onNetwork(srcAddr, tx['addr'], tx['netmask']): continue` —
|
||||
`10.40.10.21` liegt nicht im VLAN40-Netz, also **verwarf der Relay die M-SEARCH selbst, bevor sie die
|
||||
Player erreichte**. Der Rückweg gehört stattdessen **geroutet**: `forward TP5: MEDIA->MAIN` ist offen,
|
||||
die Player antworten unicast an den echten Sucher. (Upstream nennt den Rückweg selbst
|
||||
`# FIXME: record more than one?` — es gibt nur **einen globalen** Merkslot.)
|
||||
- 🔎 **Symptom-Merkregel:** *App sucht hunderte Male, sieht NOTIFYs, baut aber nie eine Verbindung auf*
|
||||
= **Relay, nicht Netz**. Diagnose in einer Zeile — **Richtungsbilanz** im Journal:
|
||||
`grep -c "via eth1"` (VLAN10→VLAN40) gegen `grep -c "via eth0"`. **0 zu n heißt: der Hinweg fehlt.**
|
||||
- ⚠️ **Aus dem Relay heraus lässt sich der Relay nicht testen** (Loop-Schutz ignoriert eigene
|
||||
Quelladressen) — am echten Client testen oder `journalctl -u multicast-relay` mitlesen. **Und am Client
|
||||
nicht auf Port 1900 messen:** Sonos-App *und* Spotify halten dort je ein `SO_REUSEPORT`-Socket, die
|
||||
Unicast-Antwort bekommt nur **eines** davon. Richtig ist eine Probe mit **ephemerem Quellport**
|
||||
(`bind((ifaddr, 0))`), genau wie ein echter Client sucht.
|
||||
- 🔴 **Zweite, unabhängige Ursache desselben Falls (08-25): die Sonos-App suchte gar nicht.** Nach der
|
||||
Relay-Reparatur lag weiter nichts an — der Mitschnitt auf der VLAN40-Leitung
|
||||
(`pct exec 108 -- tcpdump -i eth1 -n -s0 -A "udp and host <client>"`) zeigte: **jedes** SSDP-Paket des
|
||||
MacBooks kam von **Chrome** (`ST: urn:dial-multiscreen-org:…dial:1`, `USER-AGENT: Chromium`), **kein
|
||||
einziges** von Sonos. Die App hatte Sockets offen (`*:1900`, `*:6969`, `TCP *:3400`) und redete mit der
|
||||
Cloud, legte aber nichts auf die Leitung. Ursache war das **Local-Network-Gate von macOS**: es stand auf
|
||||
**„an" und griff trotzdem nicht**. Fix = Schalter unter *Datenschutz & Sicherheit → Lokales Netzwerk*
|
||||
**aus, App beenden, wieder an, App starten** (hart: `sudo tccutil reset LocalNetwork com.sonos.macController2`).
|
||||
⇒ **Merkregel: „die App sucht hunderte Male" ist erst dann ein Befund, wenn der Mitschnitt sagt, WER
|
||||
sucht.** Im Handoff vom Vormittag waren die gezählten 291 Suchen Chrome + eigene Proben; die Zahl hat
|
||||
drei Stunden in die falsche Richtung gezeigt.
|
||||
- 🖥️ **Das MacBook ist dual-homed in VLAN10:** `en5` = **10.40.10.183** (USB-Dock, Default-Route) und
|
||||
`en0` = **10.40.10.82** (WLAN). Die früher als „zweiter Mac" gelesene `.183` ist **dasselbe Gerät** —
|
||||
beim Messen immer beide Beine prüfen.
|
||||
|
||||
---
|
||||
|
||||
## Konstanter 10-Sekunden-Hänger = DNS-Timeout, kein Lastproblem
|
||||
|
||||
(Fund 2026-09-01, TP16.3-Randbefund.) `gitea` hing bei ~10 % der Anfragen exakt 10,1 s — quer über
|
||||
Web-UI, `/api/healthz` **und** den Git-Pfad, also kein Rendering. Ursache: **MariaDB (LXC 302) hatte
|
||||
`skip-name-resolve` auskommentiert** und machte bei **jeder neuen Verbindung** einen Reverse-Lookup,
|
||||
während ihr `/etc/resolv.conf` noch auf den toten `10.40.41.254` zeigte (glibc: 5 s × 2 Versuche = 10 s).
|
||||
Pool-Verbindungen waren schnell ⇒ es wirkte sporadisch.
|
||||
|
||||
🔑 **Drei Merkregeln daraus:**
|
||||
- (a) **immer gegen eine zweite Sache messen, die denselben Weg nimmt** — `vw`/`hass` über dieselbe
|
||||
Strecke hatten 0 von 30 Hängern, das hat CF, Tunnel, Firewall und Caddy in einem Schritt entlastet.
|
||||
- (b) **wer an einer DB hängt, sagt die Verbindungstabelle** (`ss -tn | grep 3306`), nicht die
|
||||
Vermutung — hier hingen **Vaultwarden, Home Assistant und gitea** an derselben MariaDB, und die Annahme
|
||||
„Vaultwarden nutzt SQLite" war schlicht falsch.
|
||||
- (c) **ein toter Resolver ist selten allein** — der Sweep über alle LXCs fand **fünf** Container mit
|
||||
`10.40.41.254`. Sweep:
|
||||
`for c in $(pct list|awk 'NR>1{print $1}'); do pct exec $c -- grep ^nameserver /etc/resolv.conf; done`
|
||||
|
||||
---
|
||||
|
||||
## networking.service failed bei funktionierendem Netz = tote `auto`-Stanzas
|
||||
|
||||
(Ursache gefunden + behoben 2026-09-01.) In vier LXCs standen in `/etc/network/interfaces` noch
|
||||
Einträge aus dem **alten VLAN1-Schema** (`eth0` → `10.40.41.x`) und aus früheren Versuchen (`veth16`,
|
||||
`veth24`, Tippfehler `eth01`) — auf Interfaces, die es im Container gar nicht gibt. `ifup -a` scheitert
|
||||
daran, das echte `veth20` kommt trotzdem hoch. **Fix:** nur die Stanzas entfernen, deren Interface in
|
||||
`ip -br link` fehlt (`lo` und die echten behalten), Backup daneben.
|
||||
|
||||
🔴 **Abnahme ist ein echter Reboot, nicht `systemctl is-active`** — der Servicestatus sagt nichts
|
||||
darüber, ob beim nächsten Start etwas hochkommt.
|
||||
|
||||
---
|
||||
|
||||
## networking.service failed im LXC, ohne dass das Netz fehlt
|
||||
|
||||
`networking.service` kann in einem LXC dauerhaft `failed` sein, ohne dass das Netz fehlt — und dann
|
||||
startet alles mit `After=network-online.target` nach einem Reboot **nicht** (Fund 2026-09-01: `101`,
|
||||
`102`, `106`, `107`; **influxdb2 kam nach dem Neustart nicht hoch**, Unit `enabled`, **kein Startversuch
|
||||
im Journal**). Fällt nur auf, wenn man neu startet — die Container liefen 13+ Tage.
|
||||
|
||||
**Vor jedem geplanten LXC-Neustart `systemctl --failed` lesen und danach die Ports gegen vorher
|
||||
vergleichen**, sonst verliert man still einen Dienst.
|
||||
|
||||
---
|
||||
|
||||
## Messwerkzeug, das die falsche Frage beantwortet
|
||||
|
||||
(zweimal in einer Nacht, 2026-09-01.)
|
||||
|
||||
- (a) **`nc -z` lieferte falsch-negative** „Port zu" für neun Dienste gleichzeitig — inklusive des
|
||||
SSH-Ports, über den die Session gerade arbeitete; `curl` bekam von demselben Host **HTTP 200 in 24 ms**.
|
||||
**Bei „alles gleichzeitig kaputt" zuerst das eigene Werkzeug verdächtigen**, dann das Netz: ein
|
||||
Python-Socket oder `curl` entscheidet in Sekunden.
|
||||
- (b) **Ein Firewall-Zähler taugt nicht als Wegbeweis**, wenn der Client die Verbindung offenhält
|
||||
(`cloudflared`): er zählt nur NEUE Verbindungen und steht still, obwohl alles läuft — richtig ist die
|
||||
**Verbindungstabelle** (`/ip firewall connection`).
|
||||
|
||||
---
|
||||
|
||||
## VLAN20-Docker-Hosts: Host-DNS nach dem Umzug prüfen (2026-07-10, TP14.2a)
|
||||
|
||||
NAS + pve-LXC zeigten `/etc/resolv.conf` noch auf totes `.41.254` → `docker pull` ghcr scheitert
|
||||
(`EAI_AGAIN`, aber Egress ok!). Fix = `.20.1`: LXC `pct set 400 --nameserver 10.40.20.1`; **Synology-Falle:
|
||||
DSM-„Allgemein"-DNS ≠ was `dockerd` liest** — Interface zieht DNS per DHCP → Fix an der
|
||||
*Netzwerkschnittstelle* „DNS manuell". Laufende Container frieren den `127.0.0.11`-Upstream zum
|
||||
Erstellzeitpunkt ein (Recreate nötig). Merkregel: `docker-pull-hängt` + `dns.lookup EAI_AGAIN` aber
|
||||
`TCP zu ghcr-IP:443 ok` = toter Resolver, nicht Egress.
|
||||
|
||||
---
|
||||
|
||||
## Zeitquelle ist der Core (NTP)
|
||||
|
||||
`/system ntp server`, seit 2026-08-18, ausgeliefert per **DHCP-Option 42** (`ntp-server=` in jedem der
|
||||
7 Netze) und drift-überwacht (ComplianceRule `system-ntp`). Vorher lief **nirgends** eine
|
||||
Synchronisation: die anderen drei Geräte zeigten auf `10.40.99.1`, wo kein Server lief, und der Core
|
||||
selbst hatte gar keinen NTP-Client — ap2 war dadurch **17 Stunden** versetzt.
|
||||
|
||||
🔴 **Zwei Fallen dabei:**
|
||||
- (a) ein NTP-Server mit `use-local-clock=no` und unsynchronisiertem Client liefert **Stratum 16**, das
|
||||
jeder Client verwirft — Server einschalten allein reicht nicht.
|
||||
- (b) wird der Server eingeschaltet, *bevor* sein Client synchron ist, hält er diesen Zustand fest →
|
||||
**`enabled=no`/`=yes` nach der Synchronisation**.
|
||||
|
||||
Merkregel: bei widersprüchlichen Zeitstempeln zwischen Geräten zuerst `/system ntp client get status`
|
||||
lesen, und zum Eingrenzen einen Client testweise auf eine öffentliche Quelle zeigen lassen.
|
||||
|
||||
---
|
||||
|
||||
## Home Assistant: der `http:`-Block in `configuration.yaml` ist WIRKUNGSLOS
|
||||
|
||||
(Fund 2026-08-26, TP31.) HA hat die http-Config nach **`.storage/http`** migriert
|
||||
(`"yaml_migration_done": true`); seither wird die YAML kommentarlos ignoriert — selbst ein ungueltiger
|
||||
Schluessel darin erzeugt **keinen** Config-Fehler.
|
||||
|
||||
🔴 **Die Logzeile fuehrt aktiv in die Irre:** `Received X-Forwarded-For header from an untrusted proxy
|
||||
<ip>` faellt laut Quelltext nur bei **aktivem** `use_x_forwarded_for` — man haelt die Option fuer wirksam
|
||||
und die Liste fuer falsch und editiert die falsche Datei. Richtig ist der WS-Zweischritt
|
||||
**`http/config/configure`** (setzt `pending`, startet HA neu) → binnen **5 Minuten**
|
||||
**`http/config/promote`**, sonst Auto-Revert auf `stable`.
|
||||
|
||||
Merkregel zur Eingrenzung: ein Testskript in `<config>/python_scripts/` + `python_script.reload` —
|
||||
taucht der Service auf, liest HA das Verzeichnis, und die Frage ist nicht mehr *welche Datei*, sondern
|
||||
*warum ein gelesener Block nicht wirkt*.
|
||||
|
||||
---
|
||||
|
||||
## Nautobot auf der DS923 braucht einen abgeschalteten Image-Healthcheck
|
||||
|
||||
Der eingebackene Check ruft alle 10 s `nautobot-server health_check` = einen vollen Django-Start; unter
|
||||
Last dauert der >2 min, die Aufrufe **stapeln sich unbegrenzt** → Speicher alle → `fork()` mit ENOMEM →
|
||||
DSM räumt den ganzen Stack ab (am 2026-08-17 zweimal passiert, Load bis 73 bei 4 Kernen). Im Stack stehen
|
||||
deshalb `healthcheck: disable: true` auf worker+scheduler, `--concurrency=2` und `mem_limit`.
|
||||
**`cpus:` geht auf Synology nicht** (Kernel ohne CFS-Quota, der Stack-Deploy schlägt sonst fehl).
|
||||
75
docs/handoff-20260922-claude-md-ausduennen.md
Normal file
75
docs/handoff-20260922-claude-md-ausduennen.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Handoff 2026-09-22 — CLAUDE.md ausdünnen (strukturelle Hygiene, kein Bug)
|
||||
|
||||
**Anlass:** Christoph stellt fest, dass CLAUDE.md „eh schon so groß" ist. Das ist kein kosmetischer
|
||||
Punkt — es ist eine strukturelle Drift, die jede künftige Session belastet. Dieser Handoff beschreibt
|
||||
das Problem aus der Innenperspektive einer Session (die CLAUDE.md bei jedem Start komplett lädt) und
|
||||
gibt einen Scope für eine eigene Cleanup-Session.
|
||||
|
||||
## Das Problem, aus Sicht der Session
|
||||
|
||||
Jede Session lädt CLAUDE.md **vollständig** in den Kontext. Was dabei konkret wehtut:
|
||||
|
||||
1. **Reine Kontext-Kosten.** Die Datei ist >58 KB. Ein großer Teil davon ist nicht „Kontext für die
|
||||
anstehende Arbeit", sondern Historie und abgeschlossene TPs. Der Token-Preis wird bei jeder Session
|
||||
fällig, egal worum es geht.
|
||||
|
||||
2. **Signal-to-noise im Gotcha-Block.** Der „Kritische Gotchas"-Abschnitt mischt **aktive Fallen** mit
|
||||
**erledigten/gegenstandslosen** Einträgen. Beispiele direkt aus dem Bestand: `~~ALLNET~~` und
|
||||
`~~Omada~~` sind mehrfach als „gegenstandslos/historisch" markiert, stehen aber weiterhin voll im
|
||||
Text. Mehrere Einträge tragen „korrigiert am X" — die **korrigierte** Fassung steht neben der
|
||||
**falschen**, die damit weiter mitgelesen wird. Beim Suchen nach *einer* relevanten Falle scannt
|
||||
die Session dutzende Zeilen, von denen ein guter Teil totes Gewicht ist.
|
||||
|
||||
3. **Redundanz über drei Orte.** Dieselbe Information (z. B. die AP-Positionen, der FT-Verlauf) steht
|
||||
in CLAUDE.md **und** in `teilprojekte.md` **und** teils in Handoffs. Eine Korrektur an einer Stelle
|
||||
lässt die anderen stumm veralten — genau die Drift, vor der die eigene CLAUDE.md warnt
|
||||
(„Manuell gepflegte Doku verliert gegen die Realität, wenn sie lang wird").
|
||||
|
||||
4. **Der gefährlichste Effekt: die Bloat untergräbt „live prüfen".** Die Projektregel lautet „nicht
|
||||
raten, live prüfen oder fragen". Eine Riesen-CLAUDE.md mit eingestreuten Stale-Angaben verleitet
|
||||
genau dazu, sich auf die Datei zu verlassen statt nachzusehen — die Datei ist Teil des Problems,
|
||||
das sie zu lösen vorgibt.
|
||||
|
||||
## Was wohin gehört (SSoT-Dreiteilung, TP36)
|
||||
|
||||
| Inhalt | Soll-Ort |
|
||||
|---|---|
|
||||
| VLAN-Modell, Hardware-Tabelle (aktuell), Tooling-Pointer | CLAUDE.md (bleibt) |
|
||||
| **Aktive, querschnittliche** Fallen (Firewall deny-by-default, sshpass-Regel, cap-wifiN-Karte …) | CLAUDE.md (bleibt, komprimiert) |
|
||||
| TP-spezifische Gotchas & Detail-Verläufe | `teilprojekte.md` `### TPnn` |
|
||||
| Erledigte/historische Geräte & Entscheidungen | `teilprojekte-archiv.md` |
|
||||
| „korrigiert am X"-Notizen | Original-Eintrag bereinigen (Stale-Wert raus), Korrektur **ersetzt** den Irrtum, statt neben ihm zu stehen |
|
||||
|
||||
## Konkrete Kandidaten zum Ausdünnen (Startpunkte, nicht abschließend)
|
||||
|
||||
- **„Kritische Gotchas":** alle `~~durchgestrichenen~~`/„gegenstandslos"-Einträge (ALLNET, Omada,
|
||||
sw1, ES208G, „ALLNET VLAN-DB") → Archiv oder löschen.
|
||||
- **„korrigiert"-Notizen:** die falsche Angabe entfernen, nur die korrigierte behalten (z. B.
|
||||
Nautobot-IP, „2×/Tag war nie real", „`ft=no` war NICHT uniform").
|
||||
- **„Pflichtlektüre":** ist von „1–7" auf effektiv „0–7" angewachsen; die ✅-GESCHLOSSEN-Punkte
|
||||
(TP16.3 §5, Fund E) sind abgeschlossene Historie → aus dem Pflichtteil in einen Verweis.
|
||||
- **„Lessons for Sessions":** mehrere Meta-Lektionen überlappen sich (Maskierungs-Muster kommt 3×,
|
||||
„erst das Werkzeug verdächtigen" 2×) → auf je eine Fassung verdichten.
|
||||
- **Hardware-Tabelle:** ausgemusterte Geräte (Omada, ALLNET) sind noch als Zeilen da, teils nur mit
|
||||
❌ — prüfen, ob sie ganz raus können (Archiv) statt als Toten in der aktuellen Tabelle zu stehen.
|
||||
|
||||
## Vorgehen für die Cleanup-Session
|
||||
|
||||
1. **Bestandsaufnahme zuerst:** CLAUDE.md abschnittsweise sichten und je Eintrag klassifizieren
|
||||
(aktiv-querschnittlich / TP-spezifisch / historisch / redundant). Drift-Tabelle vorlegen, **fragen**
|
||||
was wohin soll — nicht stillschweigend umsortieren.
|
||||
2. **Nichts löschen ohne Zielort:** TP-spezifisches wandert nach `teilprojekte.md`, Historisches nach
|
||||
`teilprojekte-archiv.md`. Erst wenn der Zielort steht, aus CLAUDE.md entfernen.
|
||||
3. **Zielbild:** CLAUDE.md zurück auf einen **schlanken Index** (VLAN-Modell, Hardware, Tooling, die
|
||||
~10 wirklich querschnittlichen aktiven Fallen). Detail gehört nach `teilprojekte.md`.
|
||||
4. **Abnahme:** CLAUDE.md-Wortzahl/Zeilen vorher/nachher notieren; sicherstellen, dass kein
|
||||
noch-aktiver Gotcha (Firewall, sshpass, cap-wifiN, slaves-datapath, WLAN-Logs-Topic) verloren geht.
|
||||
|
||||
## Übergabeprompt (nächste Session)
|
||||
|
||||
> CLAUDE.md ausdünnen: die Datei ist >58 KB und mischt aktive Fallen mit erledigter Historie
|
||||
> (durchgestrichene „gegenstandslos"-Einträge, „korrigiert"-Notizen neben der falschen Angabe,
|
||||
> Redundanz mit teilprojekte.md). Aufgabe: Bestandsaufnahme + Drift-Tabelle, dann TP-spezifisches
|
||||
> nach teilprojekte.md, Historisches ins Archiv, CLAUDE.md zurück auf einen schlanken Index
|
||||
> (~10 querschnittliche aktive Gotchas behalten). Nicht stillschweigend umsortieren — erst klassifizieren
|
||||
> und fragen. Siehe [[handoff-20260922-claude-md-ausduennen]].
|
||||
45
docs/handoff-20260922-doku-umbau-vorschlag.md
Normal file
45
docs/handoff-20260922-doku-umbau-vorschlag.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Handoff 2026-09-22 — Doku-/TP-Verwaltung: Umbau-Vorschlag (Basis für eigene Session)
|
||||
|
||||
**Anlass:** Christoph will eine separate Session, die die Doku-/TP-Verwaltung strafft und die
|
||||
Vorschläge selbstständig umsetzt. Diese Notiz ist die Analyse-Basis aus der Ausdünnen-Session
|
||||
(2026-09-22), in der die Drift-Kosten konkret sichtbar wurden. **Vorschläge, kein Mandat.**
|
||||
|
||||
## Diagnose (woran es hakt)
|
||||
|
||||
Drei parallele Wahrheitsquellen driften gegeneinander:
|
||||
|
||||
1. **Gitea-Issue** (Index + Label `status:*`/`thema:*`)
|
||||
2. **`docs/teilprojekte.md`** (Prosa, kuratiert, `### TPnn`-Abschnitte)
|
||||
3. **CLAUDE.md** (Quick-Index/Fokus — bis zur Ausdünnen-Session ein Ist-Stand-Block)
|
||||
|
||||
Jede Status-/Fakten-Änderung muss an 2–3 Stellen nachgezogen werden. Die komplette
|
||||
Ausdünnen-Session plus der TP25-Roll-up (507 Zeilen verschieben, Index-Zeile, zwei
|
||||
„nicht abgeschlossen"-Notizen, Pointer in `teilprojekte.md`) war reine Drift-Bekämpfung —
|
||||
Arbeit, die ein Gitea-Issue-Kommentar gratis ersetzt.
|
||||
|
||||
## Standard vs. Eigenbau (kurz)
|
||||
|
||||
Der Claude-Code-Standard ist schmal: **CLAUDE.md** (kurz, statisch, nur das Nicht-Offensichtliche)
|
||||
+ **git** + **Auto-Memory** + ein Issue-Tracker (Gitea/GitHub/Linear). Ein „TP-System" ist **kein**
|
||||
Standard — die TP-Verwaltung hier ist Eigenbau auf Gitea. Der Teil „Gitea-Issue als Index + Label
|
||||
als Status" ist der richtige, standardnahe Kern.
|
||||
|
||||
## Drei Vorschläge (Reihenfolge = Wert)
|
||||
|
||||
1. **CLAUDE.md = nur statische Konventionen.** Nach der Ausdünnen-Session 28 KB und gut so.
|
||||
Nie wieder „aktueller Stand"/Ist-Stand reinschreiben — der gehört nach Gitea.
|
||||
2. **Status → nur Gitea, Prosa → nur eine Stelle.** Der Kuratier-Aufwand (Index-Tabellen,
|
||||
„Dazu am Ende"-Notizen, Archiv-Roll-ups) kostet pro geschlossenem TP mehr als er wert ist.
|
||||
Kandidat: Prosa/Detail in den Gitea-Issue-Body/Kommentar verlagern, `teilprojekte.md`
|
||||
auf ein append-only Log reduzieren oder ganz durch Issues ersetzen.
|
||||
3. **Mehr git, weniger kuratieren.** `handoff-*.md` pro Session ist gut. Archiv/Roll-up liefert
|
||||
wenig über `git log` + geschlossenes Issue hinaus.
|
||||
|
||||
## Abgrenzung / Tradeoffs
|
||||
|
||||
- Das System hat echten Wert: auf Christoph zugeschnitten, die Drift-Warnungen haben real etwas
|
||||
gefangen. **Nicht ersatzlos streichen.**
|
||||
- Teuerster Teil ist die **zweite, kuratierte Prosa-Ebene** (`teilprojekte.md` + Archiv) — nicht die
|
||||
Gitea-Ebene und nicht CLAUDE.md.
|
||||
- Die separate Session sucht selbstständig Vorschläge UND setzt um; diese Notiz ist die Ausgangslage,
|
||||
kein Plan.
|
||||
@@ -58,20 +58,21 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP21](#tp21) | Nautobot Golden Config (config-as-code Control-Plane) | 🟢 Plattform + Datenmodell + Templates live |
|
||||
| [TP21.1](#tp21-1) | Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) | ✅ NETZ WIEDER LIVE (2026-08-08) — Aufräum-Todos **alle erledigt** (Stand 2026-08-23) |
|
||||
| [TP22](#tp22) | Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) | ✅ DURCH (2026-08-13) — Compliance heute **104/104, 0 Drifts** (live 2026-08-23) |
|
||||
| [TP25](#tp25) | WLAN-Stabilisierung | ✅ FERTIG (2026-09-15) — Platzierung final (ap1 Sideboard · ap2 UG · ap3 OG), Fund E geschlossen |
|
||||
| [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) |
|
||||
| [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) |
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) |
|
||||
| [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) |
|
||||
| [TP39](#tp39) | LXC-Hygiene pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup | ✅ ABGESCHLOSSEN (2026-09-01) — pve+NAS-Sweep grün |
|
||||
| [TP34](#tp34) | Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) | ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell |
|
||||
| [TP34](#tp34) | Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) | ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell · **Fortsetzung: TP34.2 (2026-09-22)** |
|
||||
| [TP44](#tp44) | Cloudflare-Estate als Dokumentations-SSoT in Nautobot | ✅ DURCH (2026-09-10) |
|
||||
| [TP41](#tp41) | CF-Access-Reparatur + Zero-Trust-Konsolidierung | ✅ ABGESCHLOSSEN (2026-09-10) — Gelis WARP via Android eingerichtet, letzter Blocker weg |
|
||||
| [TP43](#tp43) | dw-Standort-DNS-Uplink (site-spezifische Overrides) | ♻️ REOPENED (2026-09-11) — Volltext zurück in teilprojekte.md; interne BF-Namen über WG am dw offen |
|
||||
| [TP42](#tp42) | Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net | ✅ DURCH (2026-09-02) — `.bf.lan` raus, `.16` abgebaut, Compliance 140/140 |
|
||||
|
||||
Dazu am Ende: **[TP25 (Langfassung)](#tp25-lang)** — die Messchronik und die Funde A–E im Volltext.
|
||||
TP25 selbst ist **nicht** abgeschlossen und steht weiter in `teilprojekte.md`; nur seine 394 Zeilen
|
||||
Herleitung liegen hier.
|
||||
Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)** — TP25 ist
|
||||
✅ abgeschlossen (2026-09-15); der aktive Stand (Kondensat) und die 394-Zeilen-Herleitung
|
||||
(Messchronik A–G) liegen beide hier.
|
||||
|
||||
---
|
||||
<a id="tp1"></a>
|
||||
@@ -1295,13 +1296,529 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
---
|
||||
|
||||
<a id="tp25"></a>
|
||||
|
||||
## TP25 — Kondensat (aktiver Stand bis 2026-09-15, abgeschlossen)
|
||||
|
||||
> ✅ **TP25 ist ABGESCHLOSSEN (2026-09-15).** Platzierung final (ap1 Sideboard · ap2 Technik UG ·
|
||||
> ap3 Technik OG), Fund E geschlossen (`7.24.1` war die Ursache, nicht `ft-over-ds`), alle vier
|
||||
> Handoff-Punkte aufgelöst, Gitea #17 closed. Die 394-Zeilen-Herleitung (Messchronik A–G) steht
|
||||
> weiter unten unter [TP25 — Langfassung](#tp25-lang).
|
||||
|
||||
### TP25 — WLAN-Stabilisierung ▸ ✅ **FERTIG: Platzierung final (ap1 Sideboard · ap2 UG · ap3 OG), alle Handoff-Punkte aufgelöst (2026-09-15, [Steering-Handoff](handoff-20260915-tp25-steering-threshold-und-terrasse.md))** — ap2-UNTERPUNKT ✅ WIDERLEGT 2026-09-14 (In-situ, [Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)): ap2 sendet auf 2 m mit −49…−51 dBm, stabil, 0 Roams — Hardware sauber, kein Tausch begründet. Terrasse = 5-GHz-Zellkante (−76…−81 vs. 2,4 GHz −65…−73), Thrash nur in der Band-Überlappungszone −70…−77. Hebel „Access-List-Band-Boden" in der Machbarkeitsprüfung GESCHEITERT (kein `band`-Property, cap-wifiN-Rejoin-Falle) — s. Block unten** — **FUND E ✅ GESCHLOSSEN 2026-09-02: die Abschlussmessung ist ruhig (4 reauth in 8,6 h, 0 Ausfälle, Verlauf 218→40→4) ⇒ `7.24.1` (TP27) war die Ursache, nicht `ft-over-ds`. Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen, kein Geräte-Write** ([Handoff](handoff-20260902-tp25-fund-e-messung.md)) · Kern erledigt: 🟢 **(2026-08-22) Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). **Fund F ✅ aufgelöst (08-24, geräteseitig — kein Netzfehler)**, **5-GHz-Umfeldscan ✅ (08-24)**, Kanalplan für AP 3 ✅ gerechnet. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
> 📚 **Kondensiert am 2026-08-23 (Doku-Session, Punkt F).** Dieser Abschnitt war mit 394 Zeilen
|
||||
> 18 % der Datei. Die **Langfassung steht vollständig und unverändert** im Archiv:
|
||||
> [`teilprojekte-archiv.md` → TP25 (Langfassung)](teilprojekte-archiv.md#tp25-lang) — Messchronik (A)–(G),
|
||||
> Funde A–E im Volltext, die beantworteten Fragen (Beobachtung 5, ch149-Herkunft, `2ghz-g`,
|
||||
> Deckenmontage), Umsetzungsreihenfolge mit allen Deploy-Gotchas. Hier steht: **Stand, Offenes,
|
||||
> Merkregeln.** Wer an TP25 weiterarbeitet, kommt mit diesem Abschnitt aus; wer wissen will
|
||||
> *warum* etwas so entschieden wurde, liest die Langfassung.
|
||||
|
||||
**Stand 2026-09-15 — AP-Zuordnung DEFINITIV geklärt, Tausch-Befund ap2↔ap3** ([Handoff](handoff-20260915-tp25-zuordnung-und-redundanz.md)). Positionen per IP+MAC+Switch-Port (MNDP/Bridge-Host/Port-Link) live belegt: **ap1 = Sideboard/Wohnzimmer (`core ether3`), ap2 = Technik UG (`core ether21`), ap3 = Technik OG (`branch ether24`)**. Der geplante Tausch (ap1↔ap3, cAP ax → Sideboard) hat faktisch **ap2↔ap3** gedreht — ap1 blieb stehen, der cAP ax steht jetzt in OG. MAC-Korrektur: ap2 `ether1` ist `:39`, nicht `:3A` (`:3A` = Bridge/Mgmt-MAC). Rundgang (natürlicher Zustand, 1-s-Takt): **0 Disconnects**, −45…−77 dBm, Roaming über alle 3 APs; **Terrasse −58 stabil** (jetzt cAP ax/OG, 2,4G), ap1 nur kurz gestreift. ap1-Aus-Test: Main-Client voll abgedeckt (tiefster Wert −80), **alle 27 IOT kehren zurück**, aber ~4 Wohnzimmer-IOT marginal −77…−80 ⇒ ap1 technisch redundant, nicht ausfallkritisch. ap1 wieder an (Revert). **Aufgelöst 2026-09-15 ([Steering-Handoff](handoff-20260915-tp25-steering-threshold-und-terrasse.md) §6):** (1) ap1 **bleibt** am Sideboard, (2) Tausch-Korrektur **verworfen** — die Prämisse „Sideboard-AP deckt die Terrasse" war widerlegt (ap1 = Innenraum-König, cAP ax gehört nach OG), (3) Drei-Räume-Schatten **verworfen** (Signal beim Rundgang gut), (4) Doku-Drift ✅ geschlossen. **Platzierung final: ap1 Sideboard · ap2 UG · ap3 OG.**
|
||||
|
||||
**Merke:** `no cached PMK` im Log ≠ FT-Problem — es tritt auch auf `ft=no`-SSIDs (IOT) auf und ist ein harmloser PMK-Cache-Miss (voller Handshake statt Fast-Reassoc). Der 09-15-Abriss war die Terrassen-Bandkante, nicht PMK/FT.
|
||||
|
||||
**Stand 2026-09-14, 20:05 — ap2-In-situ: Hardware entlastet, Terrasse = 5-GHz-Zellkante,
|
||||
Band-Boden nicht sauber ausdrückbar** ([Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)).
|
||||
Live-Session auf der Terrasse (Christoph kooperierte physisch — der Test, der am 08-31 „nachts
|
||||
nicht möglich" war): das Fund-E-iPhone flappte 18:53–19:18 ~30×, fast ausschließlich
|
||||
**ap2-5G ↔ ap2-2,4G** (−68…−81), mit 3 Abwürfen (2 Call-Aussetzer). Vor ap2 (1–2 m): **−49…−51 dBm,
|
||||
>2 min stabil, 0 Roams** ⇒ der offene Verdacht „ap2 RF/Antenne/HW" (23:02-Befund, 2 m/−80) ist
|
||||
**in situ widerlegt** — der 23:02-Wert reproduziert sich seit dem 09-04-Kanalplan (5300/5320,
|
||||
tx-power 18 live) nicht mehr. Am Terrassenplatz ist **2,4 GHz klar besser** (−65…−73 vs. 5 GHz
|
||||
−76…−81) und das Telefon dort **ruhig** (36 s im 1-s-Takt, 0 Roams) — es thrasht nur in der
|
||||
Überlappungszone −70…−77 beider Bänder; zusätzlich client-getrieben (floh 19:44 selbst von −52
|
||||
auf 2,4/−77; Steering hat keine Schwellen, der AP drängt nichts). Mit `ft=no` ist jeder Roam ein
|
||||
spürbarer Voll-Reconnect ⇒ so sterben Anrufe. **Hebel „Access-List-Boden < −76 auf 5G":
|
||||
Machbarkeitsprüfung negativ** — `/interface/wifi/access-list` hat kein `band`-Property,
|
||||
Interface-Scoping bricht am cap-wifiN-Rejoin, SSID-/MAC-Scope träfen 2,4 GHz mit (Schlafzimmer
|
||||
−84 ⇒ Lockout). **Nicht umgesetzt.** Verbleibende Hebel: (1) ✅ **DURCH 2026-09-14, 20:30 Uhr** — `ft=yes ft-over-ds=yes`
|
||||
uniform auf `sec-main` (Ruecknahme der 09-04-Entscheidung; Go per Fragenrunde nach Dry-Run). Weg:
|
||||
Template-Edit + `push-gc-template.py` (GC-Commit `744a17e3`) → `--refresh-compliance` (Drift
|
||||
`wifi-security` wie erwartet) → `gc-manual-plan.py --deploy` (Plan `b1135f3e`, Completed, Asserts gruen;
|
||||
ein Polling-Timeout am Skript war folgenlos — Geraet stand bereits auf ft=yes) → Refresh: **140/140, 0
|
||||
Drifts**. CAP-Abnahme: **keine** Inline-Overrides auf ap1/ap2/ap3. Commands-File:
|
||||
[`docs/transaktionen/TP25-ft-yes-secmain.commands.rsc`](transaktionen/TP25-ft-yes-secmain.commands.rsc).
|
||||
Abnahme ✅ **DURCH (2026-09-14, 20:33–20:34 Uhr, live)**: Anruf auf der Terrasse ueberstand
|
||||
**fuenf Band-Wechsel binnen 60 s** (nur `roamed to`, keine `disconnected` mehr im Fenster —
|
||||
Christoph: „call steht"). (2) Deckungsfix
|
||||
Variante 3 (ap1-5G erreichte die Terrasse mit −64) — beendet das Springen selbst.
|
||||
Nachtrag 20:37: ein **Totalverlust** anderer Klasse (Roam auf ap1-2,4 bei −89, danach iOS-Aufgeben
|
||||
ohne jeden Reconnect-Versuch; identisch schon 19:01 mit ft=no gesehen) — **keine ft=yes-Regression**,
|
||||
sondern der strukturelle Rest. **Wechselrichter/DTU (Hoymiles HMS-800W-2T, geht mit der Sonne an/ab,
|
||||
09-14: 07:09–19:35): nachts unbeteiligt; tagsüber ~5:05-Zyklus (connect/`connection lost`/reconnect +
|
||||
DHCP-Churn) auf ap2-2,4/ch11 bei −53.** Messplan „DTU vs. Terrasse" angelegt ([Issue #17, Kommentar
|
||||
#225](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-225)): Referenz-Client auf
|
||||
ch11 pinnen (tp25-Methodik), 30–60 min Ping, Korrelation gegen die 5:05-Taktmarken. ⚠️ **Korrektur
|
||||
09-14 abends ([Kommentar #227](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-227)):
|
||||
Der urspruenglich genannte Hebel „OpenDTU/AhoyDTU-Dongle" existiert fuer DASSES Geraet NICHT** — HMS-
|
||||
**XXXXW**-2T (W = eingebaute DTU „DTUBI", protobuf-Protokoll) wird von AhoyDTU/OpenDTU nicht
|
||||
unterstuetzt (lumapu/ahoy#1189 seit 09-2023 offen; Christophs eigene AhoyDTU nachweislich
|
||||
inkompatibel; nur die Nicht-W-Variante HMS-800-**2T** ginge per CMT2300-Funk). Die lokale Ebene
|
||||
(hms-mqtt-publisher = „hmsmqtt", dtuGateway, ha-hoymiles-wifi) redet **mit der internen DTU uebers
|
||||
Netzwerk** — keines davon nimmt die DTU von der Luft, hmsmqtt pollt sogar zusaetzlich. **Einziger
|
||||
Airtime-Hebel bleibt DNS-Hoheit ueber die Cloud**, von Christoph bewusst zurueckgestellt (Cloud-
|
||||
Aufzeichnungen noch benoetigt, bis hmsmqtt + WLAN stabil); ob eine DTU ohne Cloud ruhiger oder
|
||||
gespraechiger wird, ist selbst eine offene Messfrage. Pin auf anderen AP kontraindiziert
|
||||
(−53 direkt an ap2); 5-Min-Takt ist S-Miles-Telemetrie per Design, kein Ethernet an der DTU-WLite.
|
||||
✅ **Netzwerk-Mechanismus geklaert (09-14, Nacht; [Issue #17, Kommentar
|
||||
#228](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-228)):** Der **Fund-F-5:05-Zyklus
|
||||
ist das Cloud-Recovery-Verhalten der DTU.** Wireshark-Analyse (hms-mqtt-publisher PR #110, HMS-800W-2T):
|
||||
lokaler Poll <30 s resettet den Countdown und setzt Cloud-Updates aus; **nach 4–5 min erfolgloser
|
||||
Cloud-Updates** startet die DTU eine neue TCP-Session — und dabei (unser Log-Beweis 09-14) eine
|
||||
**vollstaendige WiFi-Disassoziation inkl. DHCP-Churn**. Die heute gemessenen Zyklus-Abstaende decken
|
||||
sich mit dem Befund aufs Minutenkomma. Ursache war hmsmqtt mit **30 500 ms** (Grenzzone);
|
||||
DTU-Firmware aelter, hmsmqtt funktioniert (⇒ #134-Key-Rotation-Falle nicht primaer).
|
||||
**Hebel = Poll-Rate, nicht DNS-Hoheit:** Christoph hat **30500 → 45000 ms** gesetzt (09-14 abends;
|
||||
Community-Wert, 10 Monate stabil). ✅ **Verifikation DURCH (09-15, 12:11; [Issue #17, Kommentar
|
||||
#230](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-230)): DTU online seit 06:41:49,
|
||||
uptime 5 h 29 min, 0 Disconnects** (Vergleich 09-14 mit 30,5 s: fuenf Zyklen in den ersten 18 min).
|
||||
Die DTU sitzt heute auf **ap3-2,4** (cap-wifi4-virtual1, −49; cap-Karte unveraendert, Attribution
|
||||
belastbar) — also aktuell auch vom Terrassen-Band ch11 weg. **Fund F ist damit abgeschlossen** —
|
||||
Ursache war die DTU-Cloud-Recovery, getriggert durch hmsmqtt-Polling-Kollision. Messplan #225 ist
|
||||
gegenstandslos (der zu messende Effekt existiert nicht mehr); eine ch11-Tagzeitmessung bleibt nur
|
||||
sinnvoll, falls die Terrasse tagsueber weiterhin Call-Probleme zeigt. 📅 **terminiert ([Issue #17,
|
||||
Kommentar #232](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-232)): Deckungsfix
|
||||
Variante 3 — Umsetzung 2026-09-15/16** (Christoph: „heute oder morgen"): Deckenstrahler an den guten
|
||||
Platz, verdrängter hAP in den Drei-Raeume-Schatten (Schlafzimmer −84,6/38 %, Bad −82,3/24 %,
|
||||
Kueche −82,0/23 % laut 08-22-Messung). Vorher-Messung Terrasse (Referenz 09-14: 5 GHz −76…−81,
|
||||
2,4 GHz −65…−73) → Umsetzung → Nachher-Messung + Anruf-Abnahme; Kanalplan (09-04) bleibt unberuehrt,
|
||||
nur Platzierung aendert sich; Config-Aenderungen nur ueber den GC-Weg. Offen vor Ort: Zielraum fuer
|
||||
den verdraengten hAP (PoE/Steckdose) und ap1-Uplink-Thema (Sideboard → `core ether3`) mitdenken.
|
||||
Randnotizen: ap1 „FUNKSTILL" im CLAUDE.md ist überholt (dient live, Radar-Event 16:51 auf 5280
|
||||
sauber abgehandelt); Template-Abschnitt [6c] („Fund E WIEDER OFFEN", Stand 08-30) ist inhaltlich
|
||||
überholt — beim nächsten Template-Edit mit aktualisieren, kein separater Edit nur für den Kommentar.
|
||||
|
||||
**Stand 2026-08-22, 23:30 — die Terrasse ist repariert.** Erster Lauf mit beiden Ping-Strecken
|
||||
([`tp25-runde-20260822-2330-auswertung.md`](tp25-runde-20260822-2330-auswertung.md)): **43 % Verlust /
|
||||
20,4 s längste Lücke (08-21) → 9 % / 1,0 s** bei gleichem Gerät und gleicher Rolle. Wirksam war das
|
||||
**Anheben von ap2 auf 2,5 m**; 1,0 s liegt unter der Hörbarkeitsschwelle im Call. Der Dead Spot, der
|
||||
TP25 seit dem 08-20 getragen hat, ist damit weg.
|
||||
|
||||
🔎 **Kernbefund, der bleibt: das Netz ist gut, die Redundanz ist es nicht.** Die frei roamende Referenz
|
||||
hat in **17 von 21** Raumabschnitten 0 % Verlust; der Gegen-AP liefert Schlafzimmer −84,6/38 %,
|
||||
Bad −82,3/24 %, Küche −82,0/23 %, Terrasse −78,7/19 %. **Fällt ein AP aus, ist die halbe Wohnung
|
||||
„verbunden, aber tot".** ⇒ Ein **dritter AP ist nach Christophs eigener Randbedingung derzeit NICHT
|
||||
begründet** — er kaufte Redundanz, keine Abdeckung. Das ist eine andere Frage und Christophs.
|
||||
|
||||
**Beide vormals offenen Punkte sind am 2026-08-24 abgearbeitet** — Fund F ist aufgelöst, der
|
||||
5-GHz-Umfeldscan gefahren und der Kanalplan für einen dritten AP gerechnet. **Was von TP25 bleibt,
|
||||
ist Umsetzung bei Bedarf, keine offene Frage:**
|
||||
1. ✅ **Fund F — GEMESSEN UND AUFGELÖST (2026-08-24): kein netzseitiges Phänomen.**
|
||||
Kollektor am **Manager** (`/interface wifi registration-table`, 15-s-Takt, 29 Samples über 7,7 min,
|
||||
28 Clients), Rohdaten `docs/messdaten/tp25-fundf-20260824.log`. Von den drei Kandidaten sind **zwei
|
||||
erledigt, einer bleibt** — und die zwei fielen ohne Wartezeit:
|
||||
|
||||
| Kandidat | Ergebnis | woran es hängt |
|
||||
|---|---|---|
|
||||
| **(b)** DHCP-`lease-time` gegen die Periode | ❌ **tot** | `dhcp-v30` fährt **`lease-time=1d`** → Renew bei 12 h. Kann 5:05 nicht erzeugen. **Eine Abfrage, keine Messung.** |
|
||||
| **(a)** periodischer VAP-Re-Apply | ❌ **tot** | Ein Re-Apply wirft **alle** Clients des VAP gleichzeitig. Auf `cap-wifi4-virtual1` standen zeitgleich **drei Clients bei 16 h 51 min** Uptime, 25 von 28 über 6 h. Mit einem 5-Minuten-Takt ist das unvereinbar. |
|
||||
| **(c)** das Gerät selbst | 🟡 **bleibt** | Der einzige Client, der im Fenster überhaupt neu verband. |
|
||||
|
||||
🔑 **Der entscheidende Punkt an der Beweisführung: dafür braucht es die vier Zyklen nicht.** Ein
|
||||
einziges Sample mit **16 h 51 min Uptime auf genau dem VAP**, der angeblich alle 5:05 abwirft,
|
||||
widerlegt (a) vollständig. Die Fensterlänge begrenzt nur, was man über *abwesende* Geräte sagen kann.
|
||||
|
||||
🔴 **Und genau die sind abwesend: die beiden Fund-F-MACs gibt es gerade nicht.**
|
||||
`38:A5:C9:C0:73:1E` (`10.40.30.178`) und `38:A5:C9:BC:33:31` (`10.40.30.179`) stehen in `/ip arp` auf
|
||||
**`status=failed`**, haben **keine DHCP-Lease**, stehen **nicht** in der Registration-Table und sind
|
||||
**auch HA unbekannt** (kein `device_tracker` zu diesen MACs). ⇒ **Das Phänomen ist an diesen Geräten
|
||||
derzeit nicht reproduzierbar, weil die Geräte weg sind.**
|
||||
📌 **Merkregel — dieselbe wie beim P750W:** bevor ein periodisches Trennen als Funk- oder
|
||||
Konfigurationsfehler untersucht wird, **erst prüfen, ob die betroffenen Geräte überhaupt da sind.**
|
||||
Beim Drucker war die Fehldiagnose „Power-Save", hier wäre sie „VAP-Re-Apply" geworden.
|
||||
|
||||
⇒ **Fund F wird geschlossen:** was auch immer der 5:05-Takt war, er lag **geräteseitig**, und die zwei
|
||||
betroffenen Geräte sind vom Netz. Netzseitig ist nichts zu tun. **Nicht erneut aufgreifen**, solange
|
||||
`.30.178`/`.179` nicht wieder da sind *und* das Muster erneut gemessen wird.
|
||||
|
||||
🟡 **Nebenbefund mit Substanz — der DTU ist der einzige Wackelkandidat, den es wirklich gibt.**
|
||||
`54:F2:9F:12:51:F0` (DTU/Wechselrichter, auf `cap-wifi4-virtual1` = **ap1**) war der **einzige** Client
|
||||
mit einem Uptime-Rücksetzer: 43 min Sitzung, dann weg für ein Sample, dann wieder da — **bei −52 dBm**,
|
||||
also bei tadellosem Signal. Das ist derselbe Mechanismus, der schon die Transaktion
|
||||
`TP17.1-dtu-pin-ap2` begründet hat: **ein ESP hat EIN Radio, sein eigener AP und die STA-Verbindung
|
||||
teilen sich den Kanal.** Periode hier 43 min, nicht 5:05 — es ist **nicht** Fund F, aber es ist echt.
|
||||
⇒ Gehört zur ohnehin vorgesehenen Neubewertung des DTU-Pins, sobald der Kanalplan steht (s.o.).
|
||||
|
||||
<details><summary>📜 Ursprüngliche Fund-F-Formulierung (2026-08-22)</summary>
|
||||
|
||||
1. 🟡 **Fund F — IoT-Clients trennen exakt alle ~5:05 bei gutem Signal** (−59 dBm, vier Messpaare,
|
||||
auf 3 s reproduzierbar → **ein Timer, kein Funk**). Muss **am Manager** nachgemessen werden, die
|
||||
AP-Tabellen sind unter CAPsMAN leer. Kandidaten und der Test, der sie trennt:
|
||||
(a) periodischer VAP-Re-Apply (Fund D war die Ursache → prüfen, ob es nach dem Fix weg ist);
|
||||
(b) DHCP-`lease-time` der beiden MACs (`38:A5:C9:C0:73:1E`, `38:A5:C9:BC:33:31`) gegen die Periode legen;
|
||||
(c) das Gerät selbst mit 5-min-Duty-Cycle → dann bleibt es unverändert.
|
||||
🚫 **Nicht die alte Power-Save-Merkregel wiederbeleben** — die ist am 2026-08-13 als Fehldiagnose
|
||||
zurückgezogen (`ref_wifi_powersave_multicast`).
|
||||
|
||||
</details>
|
||||
|
||||
2. ⚖️ **Entscheidung 5 GHz auf 20 MHz** (statt heute 20/40) — **✅ 5-GHz-Umfeldscan durchgeführt
|
||||
2026-08-24, Empfehlung: NICHT verschmälern, Entscheidung an einen Auslöser hängen.**
|
||||
⚠️ **2,4 GHz NICHT vorschnell drosseln** — dort hängen ~20 IoT-Geräte.
|
||||
|
||||
**Messung** (`/interface wifi scan` **am Manager**, cap-wifi3 = ap1 / cap-wifi1 = ap2, 10–12 s):
|
||||
|
||||
| vom Radio auf | fremdes 5-GHz-Netz | Kanal | Signal | Abstand zum eigenen AP |
|
||||
|---|---|---|---|---|
|
||||
| **ap1** (5180/Ce) | `Drei_H288A_5G_acHG` | 5745/ac/**80 MHz** | **−86 dBm** | 19 dB darunter |
|
||||
| **ap2** (5220/Ce) | `LiwestBB93-5G` | 5200/ac/**80 MHz** (Block 5180–5240) | **−89 dBm** | 24 dB darunter |
|
||||
|
||||
⇒ **Das 5-GHz-Band ist an beiden Standorten praktisch leer.** Genau **ein** Fremdnetz je AP, beide
|
||||
rund 20 dB unter dem eigenen Pegel. Der Nachbar `LiwestBB93-5G` liegt mit 80 MHz zwar formal über
|
||||
**beiden** unserer Blöcke — bei −89 dBm ist das ohne Wirkung.
|
||||
|
||||
🎯 **Warum das gegen 20 MHz spricht — die zwei üblichen Gründe treffen hier nicht zu:**
|
||||
- **Fremdinterferenz:** existiert nicht (−86/−89). Ein schmalerer Kanal hätte nichts abzuwehren.
|
||||
- **Selbstüberlappung:** existiert nicht. ap1 `5180/Ce` = ch36+40, ap2 `5220/Ce` = ch44+48 —
|
||||
überlappungsfrei (Fund C, 08-17).
|
||||
- **Bleibt allein das +3 dB Link-Budget** — und das ist zum guten Teil **schon vorhanden**: in einem
|
||||
40-MHz-BSS sendet die Ratenanpassung an schwache Clients ohnehin auf dem Primärkanal in 20 MHz.
|
||||
Den Ceiling zahlen aber **alle** Clients dauerhaft. **Der Nutzen geht an die, die ihn schon haben.**
|
||||
- **Und es löst das gemessene Problem nicht:** die offenen Schwachstellen sind die
|
||||
Redundanz-Lücken (Schlafzimmer −84,6 / 38 %, Bad −82,3 / 24 %) **am Gegen-AP**. +3 dB macht aus
|
||||
38 % Verlust keine 0 % — das ist eine Platzierungs-/AP-Zahl-Frage, keine Kanalbreiten-Frage.
|
||||
|
||||
🔑 **Der echte Auslöser ist die AP-Zahl, nicht die Interferenz** — und das ist der Grund, die
|
||||
Entscheidung *jetzt* zu treffen statt sie offenzulassen. Beide Kanäle stehen hart in **Band A**
|
||||
(`ch-5g-ap1`=5180, `ch-5g-ap2`=5220, kein `skip-dfs-channels`), und Band A hat unter ETSI genau
|
||||
**4 × 20 MHz = 2 × 40 MHz**. Mit zwei APs ist es damit **exakt aufgebraucht**:
|
||||
|
||||
| Szenario | bei 40 MHz | bei 20 MHz |
|
||||
|---|---|---|
|
||||
| 2 APs (heute) | ✅ passt genau, überlappungsfrei | ✅ passt, +3 dB, halber Ceiling |
|
||||
| **3.–4. AP** | ❌ Band A voll ⇒ **nur mit DFS** (Band B ch52–64: Radar-Events, 60 s CAC) | ✅ 4 saubere Kanäle, **kein DFS** |
|
||||
|
||||
⇒ **Empfehlung: 20/40 MHz beibehalten.** Die Verschmälerung ist heute ein Preis ohne Gegenwert.
|
||||
Sie wird **fällig, sobald ein dritter AP dazukommt** — dann lautet die Wahl nicht mehr „20 gegen 40",
|
||||
sondern **„20 MHz flottenweit" gegen „DFS akzeptieren"**, und dafür ist 20 MHz die ruhigere Antwort.
|
||||
Ein Zwischenweg existiert (ap1 behält 40 MHz, AP 2+3 fahren 20 MHz auf 5220/5240) und braucht
|
||||
ebenfalls kein DFS.
|
||||
📌 **Damit ist dieser Punkt entscheidungsreif und an einen Auslöser gebunden statt offen.**
|
||||
|
||||
#### 📐 Kanalplan für den dritten AP ▸ **gerechnet 2026-08-24, DFS vom User genehmigt**
|
||||
> **User-Vorgaben (2026-08-24):** 20/40 MHz bleibt · **DFS ist genehmigt**, soll aber auf **ap1 oder ap2**
|
||||
> liegen, nicht auf dem Neuen · der Neue ist ein **MikroTik-Deckenstrahler mit ax** · **möglicherweise
|
||||
> kommt er an die Stelle eines der bisherigen APs** · der vorhandene Outdoor-AP bleibt konzeptionell außen vor.
|
||||
|
||||
**Das Kanalbudget unter ETSI (Indoor), als Rechnung statt als Gefühl.** Ein 40-MHz-Block darf keine
|
||||
Bandgrenze überschreiten — `[48+52]` ist deshalb unmöglich, und daran hängt die ganze Knappheit:
|
||||
|
||||
| Band | Frequenzen | Kanäle | 40-MHz-Blöcke | DFS | EIRP |
|
||||
|---|---|---|---|---|---|
|
||||
| **A** | 5150–5250 | 36/40/44/48 | **2** (`5180/Ce`, `5220/Ce`) | nein | 23 dBm |
|
||||
| **B** | 5250–5350 | 52/56/60/64 | **2** (`5260/Ce`, `5300/Ce`) | **ja** + TPC | 23 dBm |
|
||||
| **C** | 5470–5725 | 100–140 | **5** | **ja** + TPC | 30 dBm, outdoor erlaubt |
|
||||
|
||||
Heute belegen zwei APs mit 40 MHz **ganz Band A**. Genau das macht den dritten AP zur Bandfrage — und
|
||||
mit genehmigtem DFS ist sie lösbar, ohne irgendwo auf 20 MHz zu gehen.
|
||||
|
||||
**Empfohlene Zuordnung (3 APs, alle 40 MHz):**
|
||||
|
||||
| AP | Rolle | Kanal 5 GHz | Band | DFS | 2,4 GHz |
|
||||
|---|---|---|---|---|---|
|
||||
| **cAP ax (neu)** | Primär, beste Position | **`5180/Ce`** (36+40) | A | **nein** | 2437 (ch6) |
|
||||
| **ap1** hAP ax² | Sekundär | **`5260/Ce`** (52+56) | B | ja | 2412 (ch1) |
|
||||
| **ap2** hAP ax² | Sekundär | **`5300/Ce`** (60+64) | B | ja | 2462 (ch11) |
|
||||
| — | **Ausweichspur, frei halten** | `5220/Ce` (44+48) | A | — | — |
|
||||
|
||||
- **Warum Band B und nicht Band C für die zwei DFS-Radios:** gleicher 23-dBm-Deckel wie Band A (kein
|
||||
Leistungssprung zwischen den APs) und **CAC 60 s statt 600 s** — die 10-Minuten-Prüfung gilt im
|
||||
Wetterradar-Bereich von Band C. Band C bleibt damit frei für den Outdoor-AP, wo seine 30 dBm auch
|
||||
hingehören.
|
||||
- **Warum der Neue das nicht-DFS-Band bekommt:** DFS kostet **60 s CAC bei jedem Boot und jeder
|
||||
Provisionierung** plus einen erzwungenen Kanalwechsel bei Radar-Erkennung. Diese Kosten gehören auf
|
||||
die Radios, deren Fläche von einem anderen AP mitgetragen wird — nicht auf den bestplatzierten.
|
||||
Christophs Instinkt ist hier richtig.
|
||||
- **2,4 GHz fällt nebenbei sauber:** ch1/6/11 ist mit drei APs erstmals vollständig überlappungsfrei
|
||||
möglich (heute 1/11 mit einer Lücke in der Mitte).
|
||||
|
||||
🔴 **Zwei Fallen, ohne die der Plan teurer wird als er aussieht:**
|
||||
1. **Eine hart gepinnte Frequenz macht DFS zum Single Point of Failure.** `ch-5g-ap1`/`-ap2` tragen heute
|
||||
**je genau einen** Wert (`frequency=5180` bzw. `5220`). Bei einer Radar-Erkennung **muss** das Radio
|
||||
den Kanal verlassen — hat es keinen zweiten konfiguriert, bleibt das 5-GHz-Funk dieses APs **unten**.
|
||||
⇒ Für DFS gehört in das Channel-Objekt eine **Liste** (z.B. `frequency=5260,5280,5220`), damit der AP
|
||||
neu wählen kann. Das ist der Unterschied zwischen „DFS ist unkritisch" und „DFS kostet einen AP".
|
||||
2. **Vor dem Umzug die *erreichte* Sendeleistung messen — das ist Fund B, nochmal.** Band B/C verlangen
|
||||
TPC; ohne TPC sinkt der erlaubte EIRP um 3 dB. TP25 Fund B war exakt dieses Muster: **18 dBm gesetzt,
|
||||
9 dBm gesendet**, weil ch149 unter ETSI leistungsbegrenzt ist. ⇒ **Diskriminierender Test, bevor
|
||||
irgendwas gekauft oder umgezogen wird:** ap2 testweise auf `5260`, dann `/interface wifi monitor`
|
||||
lesen und die tatsächliche `tx-power` gegen die heutigen 18 dBm auf `5220` stellen. Kostet eine
|
||||
Config-Transaktion und zehn Minuten, und entscheidet, ob der Plan trägt.
|
||||
|
||||
⚖️ **Zur Platzierung — die Messung widerspricht dem Aufstellen an einer bisherigen Stelle.**
|
||||
Die Referenzmessung vom 08-22 sagt: **17 von 21 Raumabschnitten haben 0 % Verlust.** Zusätzliche
|
||||
*Abdeckung* ist damit wertlos. Der ganze Wert liegt im **Gegen-AP**: Schlafzimmer −84,6 / 38 %,
|
||||
Bad −82,3 / 24 %, Küche −82,0 / 23 %.
|
||||
|
||||
| Variante | was sie bringt |
|
||||
|---|---|
|
||||
| Neuer AP an **bisheriger** Stelle | besseres Deckenbild an einem Punkt, **der schon funktioniert**. Die Drei-Zimmer-Lücke bleibt **unverändert** — gleicher Standort, gleicher Schatten. |
|
||||
| Neuer AP an **neuer** Stelle (Richtung Schlafzimmer/Bad/Küche) | schließt die Lücke, also genau die Redundanz, die gekauft werden soll. |
|
||||
| 🎯 **Beides: cAP ax an die bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten** | bestes Deckenbild dort, wo die Clients sind, **und** eine zweite Option in den drei Räumen. Braucht keine vierte Antenne, nur eine Steckdose/PoE im Zielraum. |
|
||||
|
||||
⇒ **Empfehlung: Variante 3.** Sie ist der einzige Weg, bei dem der Deckenstrahler an den guten Platz
|
||||
darf, *ohne* dass die gemessene Schwäche unangetastet bleibt.
|
||||
- **Voraussetzung Neuer AP:** PoE-in am Zielort (die cAP-ax-Reihe hat einen Gigabit-PoE-Port, kein
|
||||
Netzteil im Lieferumfang üblich) und ein Trunk mit VLAN 10/30/40/50/90/99 wie bei ap1/ap2. Der AP
|
||||
läuft auf `wifi-qcom` und tritt damit als **regulärer CAP** dem Manager bei — kein neues Modell im GC,
|
||||
nur ein weiteres Gerät plus zwei `channel`-Objekte. ⚠️ **Exaktes Modell vor dem Kauf prüfen**
|
||||
(`cAP ax` vs. `cAP XL ax`) — beide sind ax/wifi-qcom, unterscheiden sich in Abdeckung und Preis.
|
||||
- **Wenn später doch der Outdoor-AP dazukommt:** er gehört nach **Band C** (30 dBm, outdoor zulässig);
|
||||
Band A/B sind dann von den drei Indoor-APs belegt und die Ausweichspur `5220/Ce` ist aufgebraucht.
|
||||
|
||||
#### 🔴 Fund E — **WIEDER GEÖFFNET 2026-08-30: die Wiedervorlage-Bedingung ist eingetreten**
|
||||
|
||||
Fund E war „abgestuft, NICHT umsetzen" mit einer ausdrücklichen Bedingung: *„Erst wieder
|
||||
aufgreifen, wenn ein Sticky-Fall erneut gemessen ist."* Am **2026-08-30** ist genau das passiert.
|
||||
|
||||
**Auslöser war eine unabhängige User-Meldung**, nicht die Suche danach: *„die letzten Tage/Wochen
|
||||
regelmäßig mit dem iPhone den WiFi-Link verloren obwohl bester Kontakt. Ich lande auf 5G und nicht
|
||||
am alten AP oder dem anderen."* 🔑 **Auf Mobilfunk zu fallen ist kein Roaming-Problem** — bei
|
||||
schlechtem Roaming bliebe das Telefon im WLAN und wäre nur langsam. Es heißt: die Assoziation ist
|
||||
gestorben und die Neuanmeldung gescheitert.
|
||||
|
||||
**Neue Messung (Manager-Log, 2026-08-30):** dasselbe iPhone `F2:62:4C:E1:AF:09` machte **16
|
||||
Bandwechsel in 20 Minuten** zwischen `cap-wifi5` (ap3, 5 GHz) und `cap-wifi6` (ap3, 2,4 GHz),
|
||||
zweimal im Abstand von **einer Sekunde**, bei **−53 … −81 dBm**. Kurz darauf dasselbe zwischen
|
||||
`cap-wifi1`↔`cap-wifi2` — also **an ap2**. ⇒ **Nicht ap3-spezifisch, sondern an jedem AP.**
|
||||
Das deckt sich mit der Ursprungsmessung vom 08-20 (sechs Flaps in 22 Minuten) und mit
|
||||
`…@wifi2 disconnected, not responding, signal strength −47` — **Abbruch bei ausgezeichnetem Signal**.
|
||||
|
||||
⚠️ **Zwei Symptome, die NICHT gleichgesetzt werden dürfen:** die Band-Oszillation (beide Male
|
||||
gemessen) und der Abbruch *„not responding"* bei gutem Signal. Christophs Beschreibung passt auf das
|
||||
**zweite**. Ob dieselbe Ursache dahintersteht, ist **offen**.
|
||||
|
||||
**Zeitachse:** am **2026-08-17** liefen TP28 (CAPsMAN-Cutover) und TP27 (7.24) gleichzeitig. Vorher
|
||||
waren ap1/ap2 Standalone **ohne Steering und ohne Neighbor-Group**, und `sec-main` fuhr
|
||||
`ft=yes ft-over-ds=`**`no`**. Beides änderte sich an diesem Tag. Das Beschwerdefenster deckt sich —
|
||||
**Korrelation, kein Beweis.**
|
||||
|
||||
**Was die MikroTik-Doku dazu hergibt (recherchiert 2026-08-30):**
|
||||
- `transition-threshold` Default **−80 dBm**, `-time` **10 s**, `-request-period` **30 s**,
|
||||
`-count` **3**. Unsere Steering-Objekte setzen **nichts** davon. Bei −53…−74 dBm wird das iPhone
|
||||
also kaum Transition-Kandidat ⇒ **unsolicited BTM scheidet als Treiber weitgehend aus.**
|
||||
- **WPA3 + 802.11r:** die Doku nennt **nur** „802.11r geht nicht über **OWE**". Eine
|
||||
WPA3-PSK/FT-Inkompatibilität steht dort **nicht** — sie kursiert in Foren. **Hypothese.**
|
||||
- **`2g-probe-delay`**: dokumentiertes Band-Steering (2,4-GHz-AP ignoriert die ersten 3
|
||||
Probe-Requests je Client pro 60 s). Bei uns **nicht gesetzt**.
|
||||
- Bei **dynamischen** Neighbor-Groups markiert RouterOS 5 GHz als *preferable*. Wir nutzen die
|
||||
**statische** Gruppe `bf-main` über beide Bänder; ob die Präferenz dort greift, sagt die Doku
|
||||
nicht ⇒ **offene Frage**.
|
||||
|
||||
**✅ Erledigt 2026-08-30: die Beweislage ist jetzt dauerhaft.** `wireless`- und `caps`-Logs gehen in
|
||||
den Kollektor (`10.40.20.18:514`, dieselbe Senke wie TP5), und `/system logging` steht seit heute
|
||||
unter der **27. ComplianceRule `system-logging`** — vorher war der Block unter keiner Regel und im
|
||||
Template gar nicht vorhanden. Vorher lief alles nur in einen 75-Zeilen-Ringpuffer.
|
||||
|
||||
**🔴 NACHTRAG 2026-08-31 — die Daten sind da, und sie kippen die Reihenfolge.** Nach einer Nacht
|
||||
Logging: **559 `wireless`-Zeilen, 439 davon (79 %) vom iPhone.** Entscheidend ist die Trennschärfe —
|
||||
`reauthenticating` tritt **93 von 93 Mal bei genau einem Client** auf (dem iPhone) und **93 von 93
|
||||
Mal auf genau einer SSID** (`dirtyvader-main`). Was `main` von den anderen fünf unterscheidet, ist
|
||||
**ein einziges Attribut: `ft=yes ft-over-ds=yes`**. Dieselben APs, dieselben Radios, derselbe
|
||||
Zeitraum — die SSIDs mit `ft=no` zeigen **null** Ereignisse. Das ist eine saubere Kontrollgruppe;
|
||||
die WPA3+802.11r-Hypothese ist damit **durch eigene Messung gestützt**, nicht mehr Forenwissen.
|
||||
Verlauf am 31.08.: 07:25 (10×) → 07:34 (21×), eskalierend, teils mehrere pro Sekunde, endend mit
|
||||
der Flucht auf einen **25 dB schlechteren** AP (`-89 dBm`).
|
||||
⇒ **`ft-over-ds=no` ist jetzt Hebel 1**, nicht `2g-probe-delay`: Letzteres adressiert
|
||||
Band-Steering, hier liegt ein **Authentifizierungs**problem vor. Details und Messrezept im
|
||||
[Handoff vom 2026-08-31](handoff-20260831-wlan-fund-e.md).
|
||||
|
||||
**🚫 Kein Funk-Parameter wird geändert, solange keine Daten da sind.** Vier Hebel, **einzeln** und je
|
||||
als Transaktion (`gc-transaktion.py`). **Reihenfolge korrigiert am 2026-08-31** durch den Befund oben —
|
||||
die alte Liste führte `2g-probe-delay` an, das aber **Band-Steering** adressiert, während hier ein
|
||||
**Authentifizierungs**problem gemessen ist:
|
||||
|
||||
| # | Hebel | Begründung der Reihenfolge | Risiko | Stand |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **`ft-over-ds=no`** auf `sec-main` | nimmt die Änderung vom 08-17 zurück; FT bleibt über `ft=yes` (over-the-air) erhalten. Kleinster Eingriff, der die verdächtige Achse trifft | gering | ❌ **widerlegt** — lief 6,4 h allein → 124× (≈19/h) + 2 Ausfälle, **zurückgebaut 2026-09-01** |
|
||||
| 2 | `ft=no` auf `sec-main` | falls (1) nicht reicht: FT komplett aus. Kostet Roaming-*Geschwindigkeit*, nicht Roaming | mittel | ✅ **gezogen 2026-09-04** (User-Entscheid, Dauerzustand — Template + bf-golden-config + Nautobot-Sync) |
|
||||
| 3 | `2g-probe-delay=yes` (2,4-GHz-Configs) | MikroTiks *dafür gebautes* Werkzeug; verzögert nur Probe-Antworten und **kann keinen Client stranden lassen**. Adressiert die **Band-Oszillation** — ein anderes Symptom | sehr gering | ⏹️ entfällt (Fund E geschlossen) |
|
||||
| 4 | `signal-range`-Boden per `access-list` (Fund E im Original) | **kann Clients aussperren** — genau das war 08-22 das Gegenargument; nur mit gemessenem Wert | hoch | ⏹️ entfällt (Fund E geschlossen) |
|
||||
|
||||
> ✅ **Ergebnis 2026-09-02 (Abschlussmessung, [Handoff](handoff-20260902-tp25-fund-e-messung.md)):** Der Sturm ist mit
|
||||
> `ft-over-ds=yes` **und** 7.24.1 verschwunden — 09-02 nur **4 reauth in 8,6 h** (alle iPhone `F2:62`, isolierte
|
||||
> Einzel, 0 Ausfälle), Verlauf **218 → 40 → 4**. Vorbedingung geprüft (Core-Uptime 1d18h, `caps joined=0`).
|
||||
> ⇒ **7.24.1 war die Ursache/der Fix, nicht `ft-over-ds`.** Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen,
|
||||
> **kein Geräte-Write** (Soll stand: `ft=yes ft-over-ds=yes`, Compliance 140/140). **Am 2026-09-04 hat
|
||||
> Christoph `ft=no` auf `sec-main` als Dauerzustand entschieden** (Hebel 2 doch gezogen). Der folgende Detailblock zu
|
||||
> Hebel 1 ist historisch.
|
||||
|
||||
#### 🔬 Hebel 1 — gezogen am 2026-08-31 08:09, Messung läuft bis 2026-09-01
|
||||
|
||||
**Was steht:** `sec-main` am Core auf **`ft=yes ft-over-ds=no`**. Abnahme **am Manager** über alle
|
||||
Master-VAPs — `cap-wifi3/4/5/6` (= ap2 + ap3) melden `ft=true ft-over-ds=false`; `cap-wifi1/2` (= ap1)
|
||||
sind leer, weil ap1 funkstill ist. Danach `remote-cap provision [find]`; alle drei CAPs kamen mit
|
||||
`state=Ok` zurück, alle VAPs `running=yes` **am CAP gelesen**, und das iPhone hing 71 s später wieder
|
||||
auf `dirtyvader-main` mit **`auth-type=ft-wpa3-psk`** und **frischer DHCP-Lease** `10.40.10.196` —
|
||||
also die vorgeschriebene Abnahme (Lease, nicht Registration-Table). 30 Clients insgesamt zurück.
|
||||
|
||||
**Governance:** Transaktion `TP25-fund-e-ft-over-ds` (Scope `wifi-security`, Ablauf 2026-09-03),
|
||||
Geräte-Backup `pre-tp25-funde-ftoverds.backup` auf dem Core, Snapshot
|
||||
[`docs/transaktionen/TP25-fund-e-ft-over-ds.snapshot.rsc`](transaktionen/TP25-fund-e-ft-over-ds.snapshot.rsc).
|
||||
Compliance danach **134/135**, und der Diff ist **exakt** `ft-over-ds=yes → no` auf `sec-main` —
|
||||
kein Kollateral-Drift durch die Re-Provisionierung. Das GC-Template wird **beim Commit** nachgezogen,
|
||||
nicht vorher: sonst wäre der laufende Versuch unsichtbar und `status` könnte ihn nicht mehr von einem
|
||||
Versehen unterscheiden.
|
||||
|
||||
🔴 **Die `cap-wifiN`-Karte hat sich durch die Re-Provisionierung VERSCHOBEN** — wer die Logs vom
|
||||
31.08. auswertet, muss den Schnitt kennen, sonst ordnet er Ereignisse dem falschen AP zu:
|
||||
|
||||
| | vor 08:09 (Log 07:xx) | ab 08:09 |
|
||||
|---|---|---|
|
||||
| ap1 (funkstill) | — | `cap-wifi1` / `cap-wifi2` |
|
||||
| ap2 | `cap-wifi1` / `cap-wifi2` | `cap-wifi3` / `cap-wifi5` |
|
||||
| ap3 | `cap-wifi5` / `cap-wifi6` | `cap-wifi4` / `cap-wifi6` |
|
||||
|
||||
⚠️ **Korrektur am Befund: `main` unterscheidet sich in ZWEI Attributen, nicht einem.** Neben
|
||||
`ft=yes ft-over-ds=yes` fährt `sec-main` als einzige SSID **`authentication-types=wpa3-psk` allein**;
|
||||
die fünf anderen sind `wpa2-psk,wpa3-psk`. Die Kontrollgruppe trennt also FT **und** WPA3-only nicht
|
||||
voneinander. Das ändert nichts an der Hebelreihenfolge (die Foren-Hypothese nennt genau die
|
||||
Kombination WPA3+FT), aber die Formulierung „genau ein Attribut" ist zu stark — **wenn Hebel 1 und 2
|
||||
beide nichts bewegen, ist `wpa2-psk,wpa3-psk` auf `sec-main` der nächste Kandidat, nicht `signal-range`.**
|
||||
|
||||
**Messung (Erfolgskriterium): `reauthenticating` für `F2:62:4C:E1:AF:09` geht auf ~0, ohne dass die
|
||||
anderen SSIDs neue Ereignisse zeigen. Die Zahl MUSS sich bewegen.**
|
||||
|
||||
```bash
|
||||
ssh root@10.40.20.100 "pct exec 400 -- sh -c 'cat /opt/tp5-syslog/log/10.40.20.1/2026-0*-*.log |
|
||||
grep reauthenticating | grep -oE \"([0-9A-F]{2}:){5}[0-9A-F]{2}\" | sort | uniq -c'"
|
||||
```
|
||||
|
||||
Baseline **93** (alle iPhone, alle `main`); davon **79 am 31.08., alle in Stunde 07**, der letzte um
|
||||
**07:34:43** — also **vor** dem Eingriff. Zwischen 08:00 und 08:15 kein einziges. Das ist noch **kein
|
||||
Ergebnis**, nur der saubere Schnitt.
|
||||
|
||||
*(Der Defekt war nie gegenstandslos, nur unbelegt: Fund B/C wurden am 08-20 mit ap1 auf `ether15`
|
||||
gemessen — also in genau der Stellung, in der ap1 heute wieder hängt. Er ist platzierungsunabhängig.)*
|
||||
|
||||
**✅ Erledigte Funde — Ergebnis und Merkregel (Volltext im Archiv):**
|
||||
|
||||
| Fund | Ergebnis | Merkregel, die bleibt |
|
||||
|---|---|---|
|
||||
| **A** 2,4 GHz fuhr 40 MHz statt der profilierten 20 | ✅ 08-17: Inline-`channel.*` auf `wifi1/wifi2` geleert → `2412`/`2462` @ 20 MHz | **Inline-Overrides gewinnen gegen das verlinkte Profil** — die SoT war die ganze Zeit richtig, das Gerät hat sie nicht befolgt |
|
||||
| **B** ap2 sendete 5 GHz mit **9 statt 18 dBm** | ✅ 08-17: `5220/ax/Ce/I`, **18 dBm** | Der Fix ist gemessen, **der Mechanismus nicht** — beide ersten Erklärungen (ETSI-Band C, „80 MHz kostet generell") sind widerlegt. Resthypothese + diskriminierender Test (`ap2 auf 5180/Ceee`) stehen im Archiv und gehören **vor** einen Hardwarekauf |
|
||||
| **C** 80 MHz kostete Randabdeckung | ✅ 08-17: `ch-5g` auf `20/40mhz`; ap1 `5180/Ce`, ap2 `5220/Ce`, überlappungsfrei | Unter ETSI gibt es im Indoor-Band **nur einen** 80-MHz-Block — zwei APs können dort nicht beide 80 MHz fahren. Genau deshalb war ap2 je auf 5745 gelandet |
|
||||
| **D** `w2-iot` trug einen Channel-Override auf einem **Slave**-VAP | ✅ 08-17: Overrides auf beiden APs entfernt, `w2-iot` seither `BR` | `band` ist eine Eigenschaft des **Radios**, nicht eines Slave-VAPs. War der Mechanismus hinter „alle SSIDs da, `-iot` beharrlich nicht" |
|
||||
| **Drift-Lücke** — genau diese Overrides lagen außerhalb jeder ComplianceRule | ✅ 08-18 mit dem TP28-GC-Umbau: `wifi-*`-Features hängen am Core, drei neue Rules; ein CAP hat keine Inline-Overrides mehr | **„Compliant" heißt nur: die abgedeckten Sektionen sind sauber.** Fund A und D blieben seit 08-08 unentdeckt, während die Flotte durchgehend compliant meldete |
|
||||
|
||||
🔴 **Deploy-Gotchas aus derselben Arbeit (kosten sonst zwei Deploys, Volltext im Archiv):**
|
||||
`/interface/wifi/set <if> !channel` **löscht die Inline-Sub-Properties NICHT** — es wirft nicht, es tut
|
||||
nichts; richtig ist einzeln `!channel.width !channel.band !channel.skip-dfs-channels`. Und bei
|
||||
Kanalarbeit **Breite VOR Frequenz** ändern, sonst erzeugt man auf dem Weg eine Co-Channel-Kollision.
|
||||
⚠️ `steering=`/Channel-Änderungen **re-applien alle VAPs und werfen jeden Client kurz raus** — nicht
|
||||
während eines Calls deployen, `/system backup` je AP vorher.
|
||||
|
||||
🔬 **Messwerkzeug — vier Regeln, jede aus einem Fehlbefund geboren:**
|
||||
- 🔴 **Für einen Kanal-/Umfeldscan NICHT `list_wifi_interfaces` benutzen** (Fund 2026-08-24): das Tool
|
||||
gibt `security.passphrase` **aller** Profile im Klartext aus — ein Read für die Kanalbreite hat so
|
||||
fünf WLAN-Passphrasen in ein Transkript geschrieben, den IoT-PSK inklusive. Für Kanäle reicht
|
||||
`/interface wifi channel print detail`; für die `cap-wifiN`-Zuordnung `… print where master=yes`
|
||||
mit Feldauswahl. **Der Scan selbst (`/interface wifi scan`) ist unbedenklich.**
|
||||
- 🔴 **Die `cap-wifiN`-Nummern sind NICHT stabil.** Der Manager vergibt sie in der Reihenfolge, in der
|
||||
sich die CAPs anmelden — nach dem ap1-Rückbau sind `cap-wifi1/2` = **ap2** und `cap-wifi3/4` = **ap1**,
|
||||
umgekehrt zur früher fest verdrahteten Tabelle. ⇒ **Die Zuordnung gehört zum Messlauf**, nicht zur
|
||||
Auswertungszeit: [`tp25-walk-messung.sh`](tp25-walk-messung.sh) friert sie als `ap-map.txt` ein, die
|
||||
Auswertung lässt die Namen sonst roh — **unbeschriftet ist ehrlicher als falsch beschriftet.**
|
||||
**Läufe vor dem 2026-08-22 sind nicht rekonstruierbar**; wer ihre AP-Namen zitiert, prüft sie an der
|
||||
Physik (der AP im Raum liefert dort das stärkste Signal).
|
||||
- 🔴 **Ein vom Pin abgewiesener Verbindungsversuch ist in Ping-Daten nicht von einem Funkloch zu
|
||||
unterscheiden** — und der Manager protokolliert ihn auf `info`-Ebene **gar nicht**. Android quittiert
|
||||
die Abweisung mit „Verbindung fehlgeschlagen" und **wählt das Netz ab**; danach misst der Rest des
|
||||
Rundgangs Stille. Die Klick-Konsole schlägt deshalb Alarm, sobald ein **gepinnter** Client nirgends
|
||||
hängt (`sta_iface=absent` in `sampler-<name>.csv`) — denn „der Gegen-AP trägt hier nicht" **ist das
|
||||
Messergebnis**.
|
||||
- 📐 **Eine Runde, N Messpunkte** (seit 2026-09-04): die frei roamenden Clients zeigen, was ein normaler
|
||||
Client wählt — interessant sind die **gepinnten**. Jeder Client wird **band-genau** auf einen AP
|
||||
gezwungen ([`tp25-pin-multi.sh`](tp25-pin-multi.sh), `TP25_PINSPEC="name|mac|ap|band;…"`), direkt aus
|
||||
der Klick-Konsole. Gute Referenz + schlechte Sonde = die Abdeckung steht, die Rückfallebene nicht.
|
||||
Memory `ref_gegen_ap_messung`. *(Die alte 2-Client-`access-list`-Automatik über `tp25-pin-s22.sh` ist
|
||||
mit dem Multi-Pin-Umbau entfallen; `tp25-pin-s22.sh` liefert nur noch die `map`.)*
|
||||
- ▶️ **Start ohne Werkzeugkette: `bash docs/tp25-runde.sh [name]`** — legt den Lauf unter
|
||||
`docs/messdaten/tp25-runde-<name>/` an, friert die AP-Karte ein, startet Konsole (Port 3031) und
|
||||
Messung, sagt die **vom Interface gelesene** Adresse fürs Telefon an. In der Konsole: **pinnen**
|
||||
(N Clients je AP/Band) → **gehen** (🚶/📍) → **Räume antippen** → **stoppen**; beim Beenden löst der
|
||||
Wrapper den Multi-Pin. Auswertung: `tp25-gehweg-auswertung.py --dir …`.
|
||||
- ℹ️ **Und das alles passiert TROTZ aktivem 802.11k/v und FT** (`st-main` mit `rrm=yes`/`wnm=yes`,
|
||||
`ft-over-ds=yes`, live geprüft). Die Merkregel `ref_ft_is_not_roaming` („11k/v fehlen") ist
|
||||
**historisch**. Bei −48…−55 dBm kostet eine Übergabe 0–2,6 s, meist unter einer halben Sekunde —
|
||||
**nicht die Übergabe ist teuer, sondern die schwache Zone.**
|
||||
|
||||
**User-Randbedingung (2026-08-17, wörtlich): „ich weigere mich einen anderen access point zu kaufen
|
||||
solange ich nicht das gefühl habe, wir haben alles aus dem ap rausgeholt."** Das ist die
|
||||
**Abnahmebedingung dieses TPs** — keine Hardware-Empfehlung, bevor die Parameter stimmen. Der Boden ist
|
||||
gelegt (beide APs 18 dBm, überlappungsfrei, 20 MHz auf 2,4 GHz, ap2 auf 2,5 m); was übrig bleibt, ist
|
||||
die Redundanzfrage oben.
|
||||
|
||||
- **Warum eigener TP:** Christoph hat am 2026-08-16/17 sechs konkrete Beobachtungen gemeldet und
|
||||
ausdrücklich **einen** TP dafür verlangt; sie hängen alle an derselben Ursache-Klasse (Funkparameter,
|
||||
die nicht nach Manual gesetzt sind). **TP25 übernimmt den offenen Rest von TP3.3** (Re-Walk,
|
||||
Dead-Spot-Kartierung — mit der Raumkarte vom 08-22 faktisch geleistet). TP3.2/TP3.3 stehen als Historie.
|
||||
- **Stand bei Sitzungsende 2026-08-22:** Pin gelöst, Automatik aus, `crs326-core` unverändert und
|
||||
**transaktionsfrei** — die `access-list` steht unter keiner ComplianceRule, es gab nichts zu committen.
|
||||
*(Seit 2026-08-23 gibt es die Rule `wifi-access-list`, s. TP22-Nachtrag — Fund E wäre ab jetzt
|
||||
transaktionsfähig.)*
|
||||
- 📋 **Übergabe:** [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md) ·
|
||||
Messungen: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md),
|
||||
[`tp25-runde-20260822-2330-auswertung.md`](tp25-runde-20260822-2330-auswertung.md)
|
||||
- **Abh.:** TP22 ✅ (GC-Deploy-Pipeline). Verwandt **TP3.2** (`docs/wifi-p750w-coverage-and-map.md`),
|
||||
**TP29** (nutzt dieselbe Datenquelle als Sensor). **Kein Blocker für irgendetwas.**
|
||||
|
||||
#### 🔬 Fund E — 2026-09-01: der `ap2`-Teilbefund (gültig) und eine widerlegte Heilungs-Wertung
|
||||
|
||||
**Anlass:** Christoph meldete **2026-08-31 23:02** eine Störung am Schreibtisch (5G, iPhone, kein
|
||||
Telefonat). Analyse (alles read-only am Manager `crs326-core`):
|
||||
|
||||
- **AP-Karte** (stabil seit letztem CAP-Rejoin 15:57, kein Rejoin danach — Attribution belastbar):
|
||||
`cap-wifi5/6 = ap2`, `cap-wifi3/4 = ap3`, `cap-wifi1/2 = ap1` (funkstill, Radios da, 0 Clients).
|
||||
- **23:02-Ereignis:** iPhone `F2:62:4C:E1:AF:09` ping-pongt im Sekundentakt `cap-wifi5 ↔ cap-wifi6`
|
||||
= **ap2s 5G (5220) ↔ ap2s 2,4G (ch11/2462)** — Band-Steering-Thrash auf **demselben** AP, plus
|
||||
`disconnected, not responding` um 22:37:40 (−76) und **23:01:00** (−82). Feldstärke der Roams
|
||||
**−75…−88 dBm**.
|
||||
- 🔴 **Christophs Fakt kippt die Coverage-Deutung:** iPhone und **ap2 nur 2 m** entfernt, gleicher
|
||||
Raum, ap2 auf 2,3 m. Bei 2 m Sicht wären −35…−45 dBm normal. −80 auf 2 m ist **kein** Coverage-
|
||||
und **kein** FT-Problem. Dazu: **aktuell 0 Clients auf ap2**, alle 29 auf ap3 — die Clients *meiden*
|
||||
ap2. ⇒ **ap2s Funk selbst unterperformt** (RF/Antenne/HW) und/oder die **DTU-Störung auf ap2/ch11**
|
||||
(Transaktion `TP17.1-dtu-pin-ap2`, Wechselrichter `54:F2:9F:12:51:F0` mit −59 dBm auf ap2s 2,4-Radio).
|
||||
- **In-situ zu messen** (nachts nicht möglich — Phone idle auf ap3): Registration-Signal live prüfen,
|
||||
während ein Client auf ap2 hängt; Kandidaten-Eingriffe — DTU-Pin lösen/verlegen, `dirtyvader-main`
|
||||
auf ap2 temporär deaktivieren (ap3 übernimmt den Schreibtisch), ap2-Antenne/HW prüfen. **Keine
|
||||
Änderung ohne diese Messung.**
|
||||
|
||||
🔴 **WIDERRUFEN am 2026-09-01 vormittags — der zweite Teil dieses Abschnitts war ein Messfehler.**
|
||||
Ursprünglich stand hier: „Fund E ist im sauberen Fenster geheilt — 0× `reauthenticating` seit dem
|
||||
14:36-Reboot (`sinceMinutes=1440`, total 0), also hat Hebel 1 sein Ziel erreicht."
|
||||
|
||||
**Warum das nicht trägt:** `get_log` liest den **flüchtigen Ringpuffer des Routers**, nicht den
|
||||
Kollektor. Live nachgemessen fasst er **1000 Zeilen und reicht bis 01.09. 00:38:58 zurück** — keine
|
||||
9 Stunden. Der Core-Reboot um 14:36 hatte ihn geleert; der 31.08. Vormittag war darin gar nicht
|
||||
enthalten. `total 0` hieß **„im Puffer nichts", nicht „in 24 h nichts"**. Das ist exakt die Falle,
|
||||
wegen der am 30.08. das persistente Logging gebaut wurde.
|
||||
|
||||
**Was der Kollektor zeigt:** im upgrade-freien Fenster 08:09–14:36 (Hebel 1 **allein**) **124×
|
||||
`reauthenticating`** — 09h=1 · 11h=2 · 12h=20 · 13h=31 · 14h=70, **alle vom iPhone allein**, dazu
|
||||
Christophs zwei erlebte Ausfälle 13:16 und 14:23. Zwischen 09:00 und 14:36 lag **kein Reboot**.
|
||||
⇒ Hebel 1 wurde am 2026-09-01 **zurückgebaut**; der Rückbau ist das entscheidende Experiment
|
||||
(s. Ist-Stand-Block oben).
|
||||
|
||||
✅ **Der `ap2`-Teilbefund oben bleibt davon unberührt und gültig** — er ist unabhängig gemessen und
|
||||
erklärt die 23:02-Störung, die weder FT noch Coverage erklärt.
|
||||
|
||||
**Commit-Weg (wartet auf Christophs Go — git-push-Kette):** Template `nautobot/mikrotik_routeros.j2`
|
||||
**Z. 220** `sec-main` von `ft-over-ds=yes` → `ft-over-ds=no`; dann Template-Repo nach Nautobot syncen,
|
||||
`gc-configplan.py --refresh-compliance` (Backup→Intended→Compliance), dann
|
||||
`gc-transaktion.py commit --change-id TP25-fund-e-ft-over-ds`. Danach `wifi-security` wieder driftfrei.
|
||||
🔴 Kreuzt git-push (Backup-Guard + Commit-Hinweis-Regel) und Live-Nautobot-Jobs — **nicht autonom.**
|
||||
Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-verdacht.md).
|
||||
|
||||
---
|
||||
|
||||
<a id="tp25-lang"></a>
|
||||
|
||||
## TP25 — Langfassung (Messchronik + Funde im Volltext, Stand 2026-08-22)
|
||||
|
||||
> ⚠️ **TP25 ist NICHT abgeschlossen.** Der geltende Stand, das Offene (Fund F, die
|
||||
> 20-MHz-Entscheidung) und die Merkregeln stehen in [`teilprojekte.md` → TP25](teilprojekte.md).
|
||||
> Hier liegt **wortgleich** die Fassung, die bis zum 2026-08-23 dort stand — 394 Zeilen Herleitung:
|
||||
> ✅ **TP25 ist ABGESCHLOSSEN (2026-09-15).** Der aktive Stand (Kondensat) steht oben unter
|
||||
> [TP25](#tp25). Hier liegt **wortgleich** die Fassung, die bis zum 2026-08-23 dort stand — 394 Zeilen Herleitung:
|
||||
> die Messchronik (A)–(G) mit allen zwischendurch korrigierten Irrtümern, die Funde A–F im Volltext,
|
||||
> die beantworteten Fragen (Beobachtung 5 „CAPsMAN oder Nautobot GC?", Herkunft der ch149-Notiz,
|
||||
> `2ghz-g` für IoT, Deckenmontage-APs) und die Umsetzungsreihenfolge mit den Deploy-Gotchas.
|
||||
@@ -1786,6 +2303,9 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein.
|
||||
---
|
||||
|
||||
### TP34 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ▸ ❌ **VERWORFEN (2026-09-08)**
|
||||
> ➡️ **Fortsetzung 2026-09-22: `TP34.2 — Interne DNS-Namen als Daten`** (Gitea #73, Prosa in
|
||||
> `docs/teilprojekte.md` → `### TP34.2`). Dort lebt der *Daten → gerenderte Config*-Gedanke weiter;
|
||||
> **verworfen bleibt nur das Caddyfile-Rendering** (dieser Abschnitt hier).
|
||||
> 🔴 **Nachtrag 2026-09-08 (verworfen):** Der Ansatz „Caddyfile via Golden Config rendern" ist
|
||||
> zurückgezogen (Commits `d63dc7b`, `bca8f6e`; Gitea-Issue #24 → `status:dropped`, closed). Der
|
||||
> `420.teilzeitladen.xyz`-Block wurde wieder aus dem GC-gerenderten Caddyfile entfernt; **der
|
||||
|
||||
@@ -1102,509 +1102,11 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
- **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 ▸ ⏳ **ap2-UNTERPUNKT ✅ WIDERLEGT 2026-09-14 (In-situ, [Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)): ap2 sendet auf 2 m mit −49…−51 dBm, stabil, 0 Roams — Hardware sauber, kein Tausch begründet. Terrasse = 5-GHz-Zellkante (−76…−81 vs. 2,4 GHz −65…−73), Thrash nur in der Band-Überlappungszone −70…−77. Hebel „Access-List-Band-Boden" in der Machbarkeitsprüfung GESCHEITERT (kein `band`-Property, cap-wifiN-Rejoin-Falle) — s. Block unten** — **FUND E ✅ GESCHLOSSEN 2026-09-02: die Abschlussmessung ist ruhig (4 reauth in 8,6 h, 0 Ausfälle, Verlauf 218→40→4) ⇒ `7.24.1` (TP27) war die Ursache, nicht `ft-over-ds`. Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen, kein Geräte-Write** ([Handoff](handoff-20260902-tp25-fund-e-messung.md)) · Kern erledigt: 🟢 **(2026-08-22) Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). **Fund F ✅ aufgelöst (08-24, geräteseitig — kein Netzfehler)**, **5-GHz-Umfeldscan ✅ (08-24)**, Kanalplan für AP 3 ✅ gerechnet. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
> 📚 **Kondensiert am 2026-08-23 (Doku-Session, Punkt F).** Dieser Abschnitt war mit 394 Zeilen
|
||||
> 18 % der Datei. Die **Langfassung steht vollständig und unverändert** im Archiv:
|
||||
> [`teilprojekte-archiv.md` → TP25 (Langfassung)](teilprojekte-archiv.md#tp25-lang) — Messchronik (A)–(G),
|
||||
> Funde A–E im Volltext, die beantworteten Fragen (Beobachtung 5, ch149-Herkunft, `2ghz-g`,
|
||||
> Deckenmontage), Umsetzungsreihenfolge mit allen Deploy-Gotchas. Hier steht: **Stand, Offenes,
|
||||
> Merkregeln.** Wer an TP25 weiterarbeitet, kommt mit diesem Abschnitt aus; wer wissen will
|
||||
> *warum* etwas so entschieden wurde, liest die Langfassung.
|
||||
### TP25 — WLAN-Stabilisierung ▸ ✅ **FERTIG (2026-09-15)** → [Archiv](teilprojekte-archiv.md#tp25)
|
||||
|
||||
**Stand 2026-09-15 — AP-Zuordnung DEFINITIV geklärt, Tausch-Befund ap2↔ap3** ([Handoff](handoff-20260915-tp25-zuordnung-und-redundanz.md)). Positionen per IP+MAC+Switch-Port (MNDP/Bridge-Host/Port-Link) live belegt: **ap1 = Sideboard/Wohnzimmer (`core ether3`), ap2 = Technik UG (`core ether21`), ap3 = Technik OG (`branch ether24`)**. Der geplante Tausch (ap1↔ap3, cAP ax → Sideboard) hat faktisch **ap2↔ap3** gedreht — ap1 blieb stehen, der cAP ax steht jetzt in OG. MAC-Korrektur: ap2 `ether1` ist `:39`, nicht `:3A` (`:3A` = Bridge/Mgmt-MAC). Rundgang (natürlicher Zustand, 1-s-Takt): **0 Disconnects**, −45…−77 dBm, Roaming über alle 3 APs; **Terrasse −58 stabil** (jetzt cAP ax/OG, 2,4G), ap1 nur kurz gestreift. ap1-Aus-Test: Main-Client voll abgedeckt (tiefster Wert −80), **alle 27 IOT kehren zurück**, aber ~4 Wohnzimmer-IOT marginal −77…−80 ⇒ ap1 technisch redundant, nicht ausfallkritisch. ap1 wieder an (Revert). Offen: (1) ap1 behalten/abbauen, (2) Tausch auf Plan korrigieren (cAP ax → Sideboard?), (3) Drei-Räume-Schatten (Schlafzimmer/Bad/Küche) gezielt nachmessen — noch nicht verifiziert, (4) Doku-Drift (diese Session).
|
||||
|
||||
**Stand 2026-09-14, 20:05 — ap2-In-situ: Hardware entlastet, Terrasse = 5-GHz-Zellkante,
|
||||
Band-Boden nicht sauber ausdrückbar** ([Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)).
|
||||
Live-Session auf der Terrasse (Christoph kooperierte physisch — der Test, der am 08-31 „nachts
|
||||
nicht möglich" war): das Fund-E-iPhone flappte 18:53–19:18 ~30×, fast ausschließlich
|
||||
**ap2-5G ↔ ap2-2,4G** (−68…−81), mit 3 Abwürfen (2 Call-Aussetzer). Vor ap2 (1–2 m): **−49…−51 dBm,
|
||||
>2 min stabil, 0 Roams** ⇒ der offene Verdacht „ap2 RF/Antenne/HW" (23:02-Befund, 2 m/−80) ist
|
||||
**in situ widerlegt** — der 23:02-Wert reproduziert sich seit dem 09-04-Kanalplan (5300/5320,
|
||||
tx-power 18 live) nicht mehr. Am Terrassenplatz ist **2,4 GHz klar besser** (−65…−73 vs. 5 GHz
|
||||
−76…−81) und das Telefon dort **ruhig** (36 s im 1-s-Takt, 0 Roams) — es thrasht nur in der
|
||||
Überlappungszone −70…−77 beider Bänder; zusätzlich client-getrieben (floh 19:44 selbst von −52
|
||||
auf 2,4/−77; Steering hat keine Schwellen, der AP drängt nichts). Mit `ft=no` ist jeder Roam ein
|
||||
spürbarer Voll-Reconnect ⇒ so sterben Anrufe. **Hebel „Access-List-Boden < −76 auf 5G":
|
||||
Machbarkeitsprüfung negativ** — `/interface/wifi/access-list` hat kein `band`-Property,
|
||||
Interface-Scoping bricht am cap-wifiN-Rejoin, SSID-/MAC-Scope träfen 2,4 GHz mit (Schlafzimmer
|
||||
−84 ⇒ Lockout). **Nicht umgesetzt.** Verbleibende Hebel: (1) ✅ **DURCH 2026-09-14, 20:30 Uhr** — `ft=yes ft-over-ds=yes`
|
||||
uniform auf `sec-main` (Ruecknahme der 09-04-Entscheidung; Go per Fragenrunde nach Dry-Run). Weg:
|
||||
Template-Edit + `push-gc-template.py` (GC-Commit `744a17e3`) → `--refresh-compliance` (Drift
|
||||
`wifi-security` wie erwartet) → `gc-manual-plan.py --deploy` (Plan `b1135f3e`, Completed, Asserts gruen;
|
||||
ein Polling-Timeout am Skript war folgenlos — Geraet stand bereits auf ft=yes) → Refresh: **140/140, 0
|
||||
Drifts**. CAP-Abnahme: **keine** Inline-Overrides auf ap1/ap2/ap3. Commands-File:
|
||||
[`docs/transaktionen/TP25-ft-yes-secmain.commands.rsc`](transaktionen/TP25-ft-yes-secmain.commands.rsc).
|
||||
Abnahme ✅ **DURCH (2026-09-14, 20:33–20:34 Uhr, live)**: Anruf auf der Terrasse ueberstand
|
||||
**fuenf Band-Wechsel binnen 60 s** (nur `roamed to`, keine `disconnected` mehr im Fenster —
|
||||
Christoph: „call steht"). (2) Deckungsfix
|
||||
Variante 3 (ap1-5G erreichte die Terrasse mit −64) — beendet das Springen selbst.
|
||||
Nachtrag 20:37: ein **Totalverlust** anderer Klasse (Roam auf ap1-2,4 bei −89, danach iOS-Aufgeben
|
||||
ohne jeden Reconnect-Versuch; identisch schon 19:01 mit ft=no gesehen) — **keine ft=yes-Regression**,
|
||||
sondern der strukturelle Rest. **Wechselrichter/DTU (Hoymiles HMS-800W-2T, geht mit der Sonne an/ab,
|
||||
09-14: 07:09–19:35): nachts unbeteiligt; tagsüber ~5:05-Zyklus (connect/`connection lost`/reconnect +
|
||||
DHCP-Churn) auf ap2-2,4/ch11 bei −53.** Messplan „DTU vs. Terrasse" angelegt ([Issue #17, Kommentar
|
||||
#225](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-225)): Referenz-Client auf
|
||||
ch11 pinnen (tp25-Methodik), 30–60 min Ping, Korrelation gegen die 5:05-Taktmarken. ⚠️ **Korrektur
|
||||
09-14 abends ([Kommentar #227](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-227)):
|
||||
Der urspruenglich genannte Hebel „OpenDTU/AhoyDTU-Dongle" existiert fuer DASSES Geraet NICHT** — HMS-
|
||||
**XXXXW**-2T (W = eingebaute DTU „DTUBI", protobuf-Protokoll) wird von AhoyDTU/OpenDTU nicht
|
||||
unterstuetzt (lumapu/ahoy#1189 seit 09-2023 offen; Christophs eigene AhoyDTU nachweislich
|
||||
inkompatibel; nur die Nicht-W-Variante HMS-800-**2T** ginge per CMT2300-Funk). Die lokale Ebene
|
||||
(hms-mqtt-publisher = „hmsmqtt", dtuGateway, ha-hoymiles-wifi) redet **mit der internen DTU uebers
|
||||
Netzwerk** — keines davon nimmt die DTU von der Luft, hmsmqtt pollt sogar zusaetzlich. **Einziger
|
||||
Airtime-Hebel bleibt DNS-Hoheit ueber die Cloud**, von Christoph bewusst zurueckgestellt (Cloud-
|
||||
Aufzeichnungen noch benoetigt, bis hmsmqtt + WLAN stabil); ob eine DTU ohne Cloud ruhiger oder
|
||||
gespraechiger wird, ist selbst eine offene Messfrage. Pin auf anderen AP kontraindiziert
|
||||
(−53 direkt an ap2); 5-Min-Takt ist S-Miles-Telemetrie per Design, kein Ethernet an der DTU-WLite.
|
||||
✅ **Netzwerk-Mechanismus geklaert (09-14, Nacht; [Issue #17, Kommentar
|
||||
#228](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-228)):** Der **Fund-F-5:05-Zyklus
|
||||
ist das Cloud-Recovery-Verhalten der DTU.** Wireshark-Analyse (hms-mqtt-publisher PR #110, HMS-800W-2T):
|
||||
lokaler Poll <30 s resettet den Countdown und setzt Cloud-Updates aus; **nach 4–5 min erfolgloser
|
||||
Cloud-Updates** startet die DTU eine neue TCP-Session — und dabei (unser Log-Beweis 09-14) eine
|
||||
**vollstaendige WiFi-Disassoziation inkl. DHCP-Churn**. Die heute gemessenen Zyklus-Abstaende decken
|
||||
sich mit dem Befund aufs Minutenkomma. Ursache war hmsmqtt mit **30 500 ms** (Grenzzone);
|
||||
DTU-Firmware aelter, hmsmqtt funktioniert (⇒ #134-Key-Rotation-Falle nicht primaer).
|
||||
**Hebel = Poll-Rate, nicht DNS-Hoheit:** Christoph hat **30500 → 45000 ms** gesetzt (09-14 abends;
|
||||
Community-Wert, 10 Monate stabil). ✅ **Verifikation DURCH (09-15, 12:11; [Issue #17, Kommentar
|
||||
#230](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-230)): DTU online seit 06:41:49,
|
||||
uptime 5 h 29 min, 0 Disconnects** (Vergleich 09-14 mit 30,5 s: fuenf Zyklen in den ersten 18 min).
|
||||
Die DTU sitzt heute auf **ap3-2,4** (cap-wifi4-virtual1, −49; cap-Karte unveraendert, Attribution
|
||||
belastbar) — also aktuell auch vom Terrassen-Band ch11 weg. **Fund F ist damit abgeschlossen** —
|
||||
Ursache war die DTU-Cloud-Recovery, getriggert durch hmsmqtt-Polling-Kollision. Messplan #225 ist
|
||||
gegenstandslos (der zu messende Effekt existiert nicht mehr); eine ch11-Tagzeitmessung bleibt nur
|
||||
sinnvoll, falls die Terrasse tagsueber weiterhin Call-Probleme zeigt. 📅 **terminiert ([Issue #17,
|
||||
Kommentar #232](https://gitea.emsenhuber.net/admin/bf-infra/issues/17#issuecomment-232)): Deckungsfix
|
||||
Variante 3 — Umsetzung 2026-09-15/16** (Christoph: „heute oder morgen"): Deckenstrahler an den guten
|
||||
Platz, verdrängter hAP in den Drei-Raeume-Schatten (Schlafzimmer −84,6/38 %, Bad −82,3/24 %,
|
||||
Kueche −82,0/23 % laut 08-22-Messung). Vorher-Messung Terrasse (Referenz 09-14: 5 GHz −76…−81,
|
||||
2,4 GHz −65…−73) → Umsetzung → Nachher-Messung + Anruf-Abnahme; Kanalplan (09-04) bleibt unberuehrt,
|
||||
nur Platzierung aendert sich; Config-Aenderungen nur ueber den GC-Weg. Offen vor Ort: Zielraum fuer
|
||||
den verdraengten hAP (PoE/Steckdose) und ap1-Uplink-Thema (Sideboard → `core ether3`) mitdenken.
|
||||
Randnotizen: ap1 „FUNKSTILL" im CLAUDE.md ist überholt (dient live, Radar-Event 16:51 auf 5280
|
||||
sauber abgehandelt); Template-Abschnitt [6c] („Fund E WIEDER OFFEN", Stand 08-30) ist inhaltlich
|
||||
überholt — beim nächsten Template-Edit mit aktualisieren, kein separater Edit nur für den Kommentar.
|
||||
|
||||
**Stand 2026-08-22, 23:30 — die Terrasse ist repariert.** Erster Lauf mit beiden Ping-Strecken
|
||||
([`tp25-runde-20260822-2330-auswertung.md`](tp25-runde-20260822-2330-auswertung.md)): **43 % Verlust /
|
||||
20,4 s längste Lücke (08-21) → 9 % / 1,0 s** bei gleichem Gerät und gleicher Rolle. Wirksam war das
|
||||
**Anheben von ap2 auf 2,5 m**; 1,0 s liegt unter der Hörbarkeitsschwelle im Call. Der Dead Spot, der
|
||||
TP25 seit dem 08-20 getragen hat, ist damit weg.
|
||||
|
||||
🔎 **Kernbefund, der bleibt: das Netz ist gut, die Redundanz ist es nicht.** Die frei roamende Referenz
|
||||
hat in **17 von 21** Raumabschnitten 0 % Verlust; der Gegen-AP liefert Schlafzimmer −84,6/38 %,
|
||||
Bad −82,3/24 %, Küche −82,0/23 %, Terrasse −78,7/19 %. **Fällt ein AP aus, ist die halbe Wohnung
|
||||
„verbunden, aber tot".** ⇒ Ein **dritter AP ist nach Christophs eigener Randbedingung derzeit NICHT
|
||||
begründet** — er kaufte Redundanz, keine Abdeckung. Das ist eine andere Frage und Christophs.
|
||||
|
||||
**Beide vormals offenen Punkte sind am 2026-08-24 abgearbeitet** — Fund F ist aufgelöst, der
|
||||
5-GHz-Umfeldscan gefahren und der Kanalplan für einen dritten AP gerechnet. **Was von TP25 bleibt,
|
||||
ist Umsetzung bei Bedarf, keine offene Frage:**
|
||||
1. ✅ **Fund F — GEMESSEN UND AUFGELÖST (2026-08-24): kein netzseitiges Phänomen.**
|
||||
Kollektor am **Manager** (`/interface wifi registration-table`, 15-s-Takt, 29 Samples über 7,7 min,
|
||||
28 Clients), Rohdaten `docs/messdaten/tp25-fundf-20260824.log`. Von den drei Kandidaten sind **zwei
|
||||
erledigt, einer bleibt** — und die zwei fielen ohne Wartezeit:
|
||||
|
||||
| Kandidat | Ergebnis | woran es hängt |
|
||||
|---|---|---|
|
||||
| **(b)** DHCP-`lease-time` gegen die Periode | ❌ **tot** | `dhcp-v30` fährt **`lease-time=1d`** → Renew bei 12 h. Kann 5:05 nicht erzeugen. **Eine Abfrage, keine Messung.** |
|
||||
| **(a)** periodischer VAP-Re-Apply | ❌ **tot** | Ein Re-Apply wirft **alle** Clients des VAP gleichzeitig. Auf `cap-wifi4-virtual1` standen zeitgleich **drei Clients bei 16 h 51 min** Uptime, 25 von 28 über 6 h. Mit einem 5-Minuten-Takt ist das unvereinbar. |
|
||||
| **(c)** das Gerät selbst | 🟡 **bleibt** | Der einzige Client, der im Fenster überhaupt neu verband. |
|
||||
|
||||
🔑 **Der entscheidende Punkt an der Beweisführung: dafür braucht es die vier Zyklen nicht.** Ein
|
||||
einziges Sample mit **16 h 51 min Uptime auf genau dem VAP**, der angeblich alle 5:05 abwirft,
|
||||
widerlegt (a) vollständig. Die Fensterlänge begrenzt nur, was man über *abwesende* Geräte sagen kann.
|
||||
|
||||
🔴 **Und genau die sind abwesend: die beiden Fund-F-MACs gibt es gerade nicht.**
|
||||
`38:A5:C9:C0:73:1E` (`10.40.30.178`) und `38:A5:C9:BC:33:31` (`10.40.30.179`) stehen in `/ip arp` auf
|
||||
**`status=failed`**, haben **keine DHCP-Lease**, stehen **nicht** in der Registration-Table und sind
|
||||
**auch HA unbekannt** (kein `device_tracker` zu diesen MACs). ⇒ **Das Phänomen ist an diesen Geräten
|
||||
derzeit nicht reproduzierbar, weil die Geräte weg sind.**
|
||||
📌 **Merkregel — dieselbe wie beim P750W:** bevor ein periodisches Trennen als Funk- oder
|
||||
Konfigurationsfehler untersucht wird, **erst prüfen, ob die betroffenen Geräte überhaupt da sind.**
|
||||
Beim Drucker war die Fehldiagnose „Power-Save", hier wäre sie „VAP-Re-Apply" geworden.
|
||||
|
||||
⇒ **Fund F wird geschlossen:** was auch immer der 5:05-Takt war, er lag **geräteseitig**, und die zwei
|
||||
betroffenen Geräte sind vom Netz. Netzseitig ist nichts zu tun. **Nicht erneut aufgreifen**, solange
|
||||
`.30.178`/`.179` nicht wieder da sind *und* das Muster erneut gemessen wird.
|
||||
|
||||
🟡 **Nebenbefund mit Substanz — der DTU ist der einzige Wackelkandidat, den es wirklich gibt.**
|
||||
`54:F2:9F:12:51:F0` (DTU/Wechselrichter, auf `cap-wifi4-virtual1` = **ap1**) war der **einzige** Client
|
||||
mit einem Uptime-Rücksetzer: 43 min Sitzung, dann weg für ein Sample, dann wieder da — **bei −52 dBm**,
|
||||
also bei tadellosem Signal. Das ist derselbe Mechanismus, der schon die Transaktion
|
||||
`TP17.1-dtu-pin-ap2` begründet hat: **ein ESP hat EIN Radio, sein eigener AP und die STA-Verbindung
|
||||
teilen sich den Kanal.** Periode hier 43 min, nicht 5:05 — es ist **nicht** Fund F, aber es ist echt.
|
||||
⇒ Gehört zur ohnehin vorgesehenen Neubewertung des DTU-Pins, sobald der Kanalplan steht (s.o.).
|
||||
|
||||
<details><summary>📜 Ursprüngliche Fund-F-Formulierung (2026-08-22)</summary>
|
||||
|
||||
1. 🟡 **Fund F — IoT-Clients trennen exakt alle ~5:05 bei gutem Signal** (−59 dBm, vier Messpaare,
|
||||
auf 3 s reproduzierbar → **ein Timer, kein Funk**). Muss **am Manager** nachgemessen werden, die
|
||||
AP-Tabellen sind unter CAPsMAN leer. Kandidaten und der Test, der sie trennt:
|
||||
(a) periodischer VAP-Re-Apply (Fund D war die Ursache → prüfen, ob es nach dem Fix weg ist);
|
||||
(b) DHCP-`lease-time` der beiden MACs (`38:A5:C9:C0:73:1E`, `38:A5:C9:BC:33:31`) gegen die Periode legen;
|
||||
(c) das Gerät selbst mit 5-min-Duty-Cycle → dann bleibt es unverändert.
|
||||
🚫 **Nicht die alte Power-Save-Merkregel wiederbeleben** — die ist am 2026-08-13 als Fehldiagnose
|
||||
zurückgezogen (`ref_wifi_powersave_multicast`).
|
||||
|
||||
</details>
|
||||
|
||||
2. ⚖️ **Entscheidung 5 GHz auf 20 MHz** (statt heute 20/40) — **✅ 5-GHz-Umfeldscan durchgeführt
|
||||
2026-08-24, Empfehlung: NICHT verschmälern, Entscheidung an einen Auslöser hängen.**
|
||||
⚠️ **2,4 GHz NICHT vorschnell drosseln** — dort hängen ~20 IoT-Geräte.
|
||||
|
||||
**Messung** (`/interface wifi scan` **am Manager**, cap-wifi3 = ap1 / cap-wifi1 = ap2, 10–12 s):
|
||||
|
||||
| vom Radio auf | fremdes 5-GHz-Netz | Kanal | Signal | Abstand zum eigenen AP |
|
||||
|---|---|---|---|---|
|
||||
| **ap1** (5180/Ce) | `Drei_H288A_5G_acHG` | 5745/ac/**80 MHz** | **−86 dBm** | 19 dB darunter |
|
||||
| **ap2** (5220/Ce) | `LiwestBB93-5G` | 5200/ac/**80 MHz** (Block 5180–5240) | **−89 dBm** | 24 dB darunter |
|
||||
|
||||
⇒ **Das 5-GHz-Band ist an beiden Standorten praktisch leer.** Genau **ein** Fremdnetz je AP, beide
|
||||
rund 20 dB unter dem eigenen Pegel. Der Nachbar `LiwestBB93-5G` liegt mit 80 MHz zwar formal über
|
||||
**beiden** unserer Blöcke — bei −89 dBm ist das ohne Wirkung.
|
||||
|
||||
🎯 **Warum das gegen 20 MHz spricht — die zwei üblichen Gründe treffen hier nicht zu:**
|
||||
- **Fremdinterferenz:** existiert nicht (−86/−89). Ein schmalerer Kanal hätte nichts abzuwehren.
|
||||
- **Selbstüberlappung:** existiert nicht. ap1 `5180/Ce` = ch36+40, ap2 `5220/Ce` = ch44+48 —
|
||||
überlappungsfrei (Fund C, 08-17).
|
||||
- **Bleibt allein das +3 dB Link-Budget** — und das ist zum guten Teil **schon vorhanden**: in einem
|
||||
40-MHz-BSS sendet die Ratenanpassung an schwache Clients ohnehin auf dem Primärkanal in 20 MHz.
|
||||
Den Ceiling zahlen aber **alle** Clients dauerhaft. **Der Nutzen geht an die, die ihn schon haben.**
|
||||
- **Und es löst das gemessene Problem nicht:** die offenen Schwachstellen sind die
|
||||
Redundanz-Lücken (Schlafzimmer −84,6 / 38 %, Bad −82,3 / 24 %) **am Gegen-AP**. +3 dB macht aus
|
||||
38 % Verlust keine 0 % — das ist eine Platzierungs-/AP-Zahl-Frage, keine Kanalbreiten-Frage.
|
||||
|
||||
🔑 **Der echte Auslöser ist die AP-Zahl, nicht die Interferenz** — und das ist der Grund, die
|
||||
Entscheidung *jetzt* zu treffen statt sie offenzulassen. Beide Kanäle stehen hart in **Band A**
|
||||
(`ch-5g-ap1`=5180, `ch-5g-ap2`=5220, kein `skip-dfs-channels`), und Band A hat unter ETSI genau
|
||||
**4 × 20 MHz = 2 × 40 MHz**. Mit zwei APs ist es damit **exakt aufgebraucht**:
|
||||
|
||||
| Szenario | bei 40 MHz | bei 20 MHz |
|
||||
|---|---|---|
|
||||
| 2 APs (heute) | ✅ passt genau, überlappungsfrei | ✅ passt, +3 dB, halber Ceiling |
|
||||
| **3.–4. AP** | ❌ Band A voll ⇒ **nur mit DFS** (Band B ch52–64: Radar-Events, 60 s CAC) | ✅ 4 saubere Kanäle, **kein DFS** |
|
||||
|
||||
⇒ **Empfehlung: 20/40 MHz beibehalten.** Die Verschmälerung ist heute ein Preis ohne Gegenwert.
|
||||
Sie wird **fällig, sobald ein dritter AP dazukommt** — dann lautet die Wahl nicht mehr „20 gegen 40",
|
||||
sondern **„20 MHz flottenweit" gegen „DFS akzeptieren"**, und dafür ist 20 MHz die ruhigere Antwort.
|
||||
Ein Zwischenweg existiert (ap1 behält 40 MHz, AP 2+3 fahren 20 MHz auf 5220/5240) und braucht
|
||||
ebenfalls kein DFS.
|
||||
📌 **Damit ist dieser Punkt entscheidungsreif und an einen Auslöser gebunden statt offen.**
|
||||
|
||||
#### 📐 Kanalplan für den dritten AP ▸ **gerechnet 2026-08-24, DFS vom User genehmigt**
|
||||
> **User-Vorgaben (2026-08-24):** 20/40 MHz bleibt · **DFS ist genehmigt**, soll aber auf **ap1 oder ap2**
|
||||
> liegen, nicht auf dem Neuen · der Neue ist ein **MikroTik-Deckenstrahler mit ax** · **möglicherweise
|
||||
> kommt er an die Stelle eines der bisherigen APs** · der vorhandene Outdoor-AP bleibt konzeptionell außen vor.
|
||||
|
||||
**Das Kanalbudget unter ETSI (Indoor), als Rechnung statt als Gefühl.** Ein 40-MHz-Block darf keine
|
||||
Bandgrenze überschreiten — `[48+52]` ist deshalb unmöglich, und daran hängt die ganze Knappheit:
|
||||
|
||||
| Band | Frequenzen | Kanäle | 40-MHz-Blöcke | DFS | EIRP |
|
||||
|---|---|---|---|---|---|
|
||||
| **A** | 5150–5250 | 36/40/44/48 | **2** (`5180/Ce`, `5220/Ce`) | nein | 23 dBm |
|
||||
| **B** | 5250–5350 | 52/56/60/64 | **2** (`5260/Ce`, `5300/Ce`) | **ja** + TPC | 23 dBm |
|
||||
| **C** | 5470–5725 | 100–140 | **5** | **ja** + TPC | 30 dBm, outdoor erlaubt |
|
||||
|
||||
Heute belegen zwei APs mit 40 MHz **ganz Band A**. Genau das macht den dritten AP zur Bandfrage — und
|
||||
mit genehmigtem DFS ist sie lösbar, ohne irgendwo auf 20 MHz zu gehen.
|
||||
|
||||
**Empfohlene Zuordnung (3 APs, alle 40 MHz):**
|
||||
|
||||
| AP | Rolle | Kanal 5 GHz | Band | DFS | 2,4 GHz |
|
||||
|---|---|---|---|---|---|
|
||||
| **cAP ax (neu)** | Primär, beste Position | **`5180/Ce`** (36+40) | A | **nein** | 2437 (ch6) |
|
||||
| **ap1** hAP ax² | Sekundär | **`5260/Ce`** (52+56) | B | ja | 2412 (ch1) |
|
||||
| **ap2** hAP ax² | Sekundär | **`5300/Ce`** (60+64) | B | ja | 2462 (ch11) |
|
||||
| — | **Ausweichspur, frei halten** | `5220/Ce` (44+48) | A | — | — |
|
||||
|
||||
- **Warum Band B und nicht Band C für die zwei DFS-Radios:** gleicher 23-dBm-Deckel wie Band A (kein
|
||||
Leistungssprung zwischen den APs) und **CAC 60 s statt 600 s** — die 10-Minuten-Prüfung gilt im
|
||||
Wetterradar-Bereich von Band C. Band C bleibt damit frei für den Outdoor-AP, wo seine 30 dBm auch
|
||||
hingehören.
|
||||
- **Warum der Neue das nicht-DFS-Band bekommt:** DFS kostet **60 s CAC bei jedem Boot und jeder
|
||||
Provisionierung** plus einen erzwungenen Kanalwechsel bei Radar-Erkennung. Diese Kosten gehören auf
|
||||
die Radios, deren Fläche von einem anderen AP mitgetragen wird — nicht auf den bestplatzierten.
|
||||
Christophs Instinkt ist hier richtig.
|
||||
- **2,4 GHz fällt nebenbei sauber:** ch1/6/11 ist mit drei APs erstmals vollständig überlappungsfrei
|
||||
möglich (heute 1/11 mit einer Lücke in der Mitte).
|
||||
|
||||
🔴 **Zwei Fallen, ohne die der Plan teurer wird als er aussieht:**
|
||||
1. **Eine hart gepinnte Frequenz macht DFS zum Single Point of Failure.** `ch-5g-ap1`/`-ap2` tragen heute
|
||||
**je genau einen** Wert (`frequency=5180` bzw. `5220`). Bei einer Radar-Erkennung **muss** das Radio
|
||||
den Kanal verlassen — hat es keinen zweiten konfiguriert, bleibt das 5-GHz-Funk dieses APs **unten**.
|
||||
⇒ Für DFS gehört in das Channel-Objekt eine **Liste** (z.B. `frequency=5260,5280,5220`), damit der AP
|
||||
neu wählen kann. Das ist der Unterschied zwischen „DFS ist unkritisch" und „DFS kostet einen AP".
|
||||
2. **Vor dem Umzug die *erreichte* Sendeleistung messen — das ist Fund B, nochmal.** Band B/C verlangen
|
||||
TPC; ohne TPC sinkt der erlaubte EIRP um 3 dB. TP25 Fund B war exakt dieses Muster: **18 dBm gesetzt,
|
||||
9 dBm gesendet**, weil ch149 unter ETSI leistungsbegrenzt ist. ⇒ **Diskriminierender Test, bevor
|
||||
irgendwas gekauft oder umgezogen wird:** ap2 testweise auf `5260`, dann `/interface wifi monitor`
|
||||
lesen und die tatsächliche `tx-power` gegen die heutigen 18 dBm auf `5220` stellen. Kostet eine
|
||||
Config-Transaktion und zehn Minuten, und entscheidet, ob der Plan trägt.
|
||||
|
||||
⚖️ **Zur Platzierung — die Messung widerspricht dem Aufstellen an einer bisherigen Stelle.**
|
||||
Die Referenzmessung vom 08-22 sagt: **17 von 21 Raumabschnitten haben 0 % Verlust.** Zusätzliche
|
||||
*Abdeckung* ist damit wertlos. Der ganze Wert liegt im **Gegen-AP**: Schlafzimmer −84,6 / 38 %,
|
||||
Bad −82,3 / 24 %, Küche −82,0 / 23 %.
|
||||
|
||||
| Variante | was sie bringt |
|
||||
|---|---|
|
||||
| Neuer AP an **bisheriger** Stelle | besseres Deckenbild an einem Punkt, **der schon funktioniert**. Die Drei-Zimmer-Lücke bleibt **unverändert** — gleicher Standort, gleicher Schatten. |
|
||||
| Neuer AP an **neuer** Stelle (Richtung Schlafzimmer/Bad/Küche) | schließt die Lücke, also genau die Redundanz, die gekauft werden soll. |
|
||||
| 🎯 **Beides: cAP ax an die bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten** | bestes Deckenbild dort, wo die Clients sind, **und** eine zweite Option in den drei Räumen. Braucht keine vierte Antenne, nur eine Steckdose/PoE im Zielraum. |
|
||||
|
||||
⇒ **Empfehlung: Variante 3.** Sie ist der einzige Weg, bei dem der Deckenstrahler an den guten Platz
|
||||
darf, *ohne* dass die gemessene Schwäche unangetastet bleibt.
|
||||
- **Voraussetzung Neuer AP:** PoE-in am Zielort (die cAP-ax-Reihe hat einen Gigabit-PoE-Port, kein
|
||||
Netzteil im Lieferumfang üblich) und ein Trunk mit VLAN 10/30/40/50/90/99 wie bei ap1/ap2. Der AP
|
||||
läuft auf `wifi-qcom` und tritt damit als **regulärer CAP** dem Manager bei — kein neues Modell im GC,
|
||||
nur ein weiteres Gerät plus zwei `channel`-Objekte. ⚠️ **Exaktes Modell vor dem Kauf prüfen**
|
||||
(`cAP ax` vs. `cAP XL ax`) — beide sind ax/wifi-qcom, unterscheiden sich in Abdeckung und Preis.
|
||||
- **Wenn später doch der Outdoor-AP dazukommt:** er gehört nach **Band C** (30 dBm, outdoor zulässig);
|
||||
Band A/B sind dann von den drei Indoor-APs belegt und die Ausweichspur `5220/Ce` ist aufgebraucht.
|
||||
|
||||
#### 🔴 Fund E — **WIEDER GEÖFFNET 2026-08-30: die Wiedervorlage-Bedingung ist eingetreten**
|
||||
|
||||
Fund E war „abgestuft, NICHT umsetzen" mit einer ausdrücklichen Bedingung: *„Erst wieder
|
||||
aufgreifen, wenn ein Sticky-Fall erneut gemessen ist."* Am **2026-08-30** ist genau das passiert.
|
||||
|
||||
**Auslöser war eine unabhängige User-Meldung**, nicht die Suche danach: *„die letzten Tage/Wochen
|
||||
regelmäßig mit dem iPhone den WiFi-Link verloren obwohl bester Kontakt. Ich lande auf 5G und nicht
|
||||
am alten AP oder dem anderen."* 🔑 **Auf Mobilfunk zu fallen ist kein Roaming-Problem** — bei
|
||||
schlechtem Roaming bliebe das Telefon im WLAN und wäre nur langsam. Es heißt: die Assoziation ist
|
||||
gestorben und die Neuanmeldung gescheitert.
|
||||
|
||||
**Neue Messung (Manager-Log, 2026-08-30):** dasselbe iPhone `F2:62:4C:E1:AF:09` machte **16
|
||||
Bandwechsel in 20 Minuten** zwischen `cap-wifi5` (ap3, 5 GHz) und `cap-wifi6` (ap3, 2,4 GHz),
|
||||
zweimal im Abstand von **einer Sekunde**, bei **−53 … −81 dBm**. Kurz darauf dasselbe zwischen
|
||||
`cap-wifi1`↔`cap-wifi2` — also **an ap2**. ⇒ **Nicht ap3-spezifisch, sondern an jedem AP.**
|
||||
Das deckt sich mit der Ursprungsmessung vom 08-20 (sechs Flaps in 22 Minuten) und mit
|
||||
`…@wifi2 disconnected, not responding, signal strength −47` — **Abbruch bei ausgezeichnetem Signal**.
|
||||
|
||||
⚠️ **Zwei Symptome, die NICHT gleichgesetzt werden dürfen:** die Band-Oszillation (beide Male
|
||||
gemessen) und der Abbruch *„not responding"* bei gutem Signal. Christophs Beschreibung passt auf das
|
||||
**zweite**. Ob dieselbe Ursache dahintersteht, ist **offen**.
|
||||
|
||||
**Zeitachse:** am **2026-08-17** liefen TP28 (CAPsMAN-Cutover) und TP27 (7.24) gleichzeitig. Vorher
|
||||
waren ap1/ap2 Standalone **ohne Steering und ohne Neighbor-Group**, und `sec-main` fuhr
|
||||
`ft=yes ft-over-ds=`**`no`**. Beides änderte sich an diesem Tag. Das Beschwerdefenster deckt sich —
|
||||
**Korrelation, kein Beweis.**
|
||||
|
||||
**Was die MikroTik-Doku dazu hergibt (recherchiert 2026-08-30):**
|
||||
- `transition-threshold` Default **−80 dBm**, `-time` **10 s**, `-request-period` **30 s**,
|
||||
`-count` **3**. Unsere Steering-Objekte setzen **nichts** davon. Bei −53…−74 dBm wird das iPhone
|
||||
also kaum Transition-Kandidat ⇒ **unsolicited BTM scheidet als Treiber weitgehend aus.**
|
||||
- **WPA3 + 802.11r:** die Doku nennt **nur** „802.11r geht nicht über **OWE**". Eine
|
||||
WPA3-PSK/FT-Inkompatibilität steht dort **nicht** — sie kursiert in Foren. **Hypothese.**
|
||||
- **`2g-probe-delay`**: dokumentiertes Band-Steering (2,4-GHz-AP ignoriert die ersten 3
|
||||
Probe-Requests je Client pro 60 s). Bei uns **nicht gesetzt**.
|
||||
- Bei **dynamischen** Neighbor-Groups markiert RouterOS 5 GHz als *preferable*. Wir nutzen die
|
||||
**statische** Gruppe `bf-main` über beide Bänder; ob die Präferenz dort greift, sagt die Doku
|
||||
nicht ⇒ **offene Frage**.
|
||||
|
||||
**✅ Erledigt 2026-08-30: die Beweislage ist jetzt dauerhaft.** `wireless`- und `caps`-Logs gehen in
|
||||
den Kollektor (`10.40.20.18:514`, dieselbe Senke wie TP5), und `/system logging` steht seit heute
|
||||
unter der **27. ComplianceRule `system-logging`** — vorher war der Block unter keiner Regel und im
|
||||
Template gar nicht vorhanden. Vorher lief alles nur in einen 75-Zeilen-Ringpuffer.
|
||||
|
||||
**🔴 NACHTRAG 2026-08-31 — die Daten sind da, und sie kippen die Reihenfolge.** Nach einer Nacht
|
||||
Logging: **559 `wireless`-Zeilen, 439 davon (79 %) vom iPhone.** Entscheidend ist die Trennschärfe —
|
||||
`reauthenticating` tritt **93 von 93 Mal bei genau einem Client** auf (dem iPhone) und **93 von 93
|
||||
Mal auf genau einer SSID** (`dirtyvader-main`). Was `main` von den anderen fünf unterscheidet, ist
|
||||
**ein einziges Attribut: `ft=yes ft-over-ds=yes`**. Dieselben APs, dieselben Radios, derselbe
|
||||
Zeitraum — die SSIDs mit `ft=no` zeigen **null** Ereignisse. Das ist eine saubere Kontrollgruppe;
|
||||
die WPA3+802.11r-Hypothese ist damit **durch eigene Messung gestützt**, nicht mehr Forenwissen.
|
||||
Verlauf am 31.08.: 07:25 (10×) → 07:34 (21×), eskalierend, teils mehrere pro Sekunde, endend mit
|
||||
der Flucht auf einen **25 dB schlechteren** AP (`-89 dBm`).
|
||||
⇒ **`ft-over-ds=no` ist jetzt Hebel 1**, nicht `2g-probe-delay`: Letzteres adressiert
|
||||
Band-Steering, hier liegt ein **Authentifizierungs**problem vor. Details und Messrezept im
|
||||
[Handoff vom 2026-08-31](handoff-20260831-wlan-fund-e.md).
|
||||
|
||||
**🚫 Kein Funk-Parameter wird geändert, solange keine Daten da sind.** Vier Hebel, **einzeln** und je
|
||||
als Transaktion (`gc-transaktion.py`). **Reihenfolge korrigiert am 2026-08-31** durch den Befund oben —
|
||||
die alte Liste führte `2g-probe-delay` an, das aber **Band-Steering** adressiert, während hier ein
|
||||
**Authentifizierungs**problem gemessen ist:
|
||||
|
||||
| # | Hebel | Begründung der Reihenfolge | Risiko | Stand |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **`ft-over-ds=no`** auf `sec-main` | nimmt die Änderung vom 08-17 zurück; FT bleibt über `ft=yes` (over-the-air) erhalten. Kleinster Eingriff, der die verdächtige Achse trifft | gering | ❌ **widerlegt** — lief 6,4 h allein → 124× (≈19/h) + 2 Ausfälle, **zurückgebaut 2026-09-01** |
|
||||
| 2 | `ft=no` auf `sec-main` | falls (1) nicht reicht: FT komplett aus. Kostet Roaming-*Geschwindigkeit*, nicht Roaming | mittel | ✅ **gezogen 2026-09-04** (User-Entscheid, Dauerzustand — Template + bf-golden-config + Nautobot-Sync) |
|
||||
| 3 | `2g-probe-delay=yes` (2,4-GHz-Configs) | MikroTiks *dafür gebautes* Werkzeug; verzögert nur Probe-Antworten und **kann keinen Client stranden lassen**. Adressiert die **Band-Oszillation** — ein anderes Symptom | sehr gering | ⏹️ entfällt (Fund E geschlossen) |
|
||||
| 4 | `signal-range`-Boden per `access-list` (Fund E im Original) | **kann Clients aussperren** — genau das war 08-22 das Gegenargument; nur mit gemessenem Wert | hoch | ⏹️ entfällt (Fund E geschlossen) |
|
||||
|
||||
> ✅ **Ergebnis 2026-09-02 (Abschlussmessung, [Handoff](handoff-20260902-tp25-fund-e-messung.md)):** Der Sturm ist mit
|
||||
> `ft-over-ds=yes` **und** 7.24.1 verschwunden — 09-02 nur **4 reauth in 8,6 h** (alle iPhone `F2:62`, isolierte
|
||||
> Einzel, 0 Ausfälle), Verlauf **218 → 40 → 4**. Vorbedingung geprüft (Core-Uptime 1d18h, `caps joined=0`).
|
||||
> ⇒ **7.24.1 war die Ursache/der Fix, nicht `ft-over-ds`.** Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen,
|
||||
> **kein Geräte-Write** (Soll stand: `ft=yes ft-over-ds=yes`, Compliance 140/140). **Am 2026-09-04 hat
|
||||
> Christoph `ft=no` auf `sec-main` als Dauerzustand entschieden** (Hebel 2 doch gezogen). Der folgende Detailblock zu
|
||||
> Hebel 1 ist historisch.
|
||||
|
||||
#### 🔬 Hebel 1 — gezogen am 2026-08-31 08:09, Messung läuft bis 2026-09-01
|
||||
|
||||
**Was steht:** `sec-main` am Core auf **`ft=yes ft-over-ds=no`**. Abnahme **am Manager** über alle
|
||||
Master-VAPs — `cap-wifi3/4/5/6` (= ap2 + ap3) melden `ft=true ft-over-ds=false`; `cap-wifi1/2` (= ap1)
|
||||
sind leer, weil ap1 funkstill ist. Danach `remote-cap provision [find]`; alle drei CAPs kamen mit
|
||||
`state=Ok` zurück, alle VAPs `running=yes` **am CAP gelesen**, und das iPhone hing 71 s später wieder
|
||||
auf `dirtyvader-main` mit **`auth-type=ft-wpa3-psk`** und **frischer DHCP-Lease** `10.40.10.196` —
|
||||
also die vorgeschriebene Abnahme (Lease, nicht Registration-Table). 30 Clients insgesamt zurück.
|
||||
|
||||
**Governance:** Transaktion `TP25-fund-e-ft-over-ds` (Scope `wifi-security`, Ablauf 2026-09-03),
|
||||
Geräte-Backup `pre-tp25-funde-ftoverds.backup` auf dem Core, Snapshot
|
||||
[`docs/transaktionen/TP25-fund-e-ft-over-ds.snapshot.rsc`](transaktionen/TP25-fund-e-ft-over-ds.snapshot.rsc).
|
||||
Compliance danach **134/135**, und der Diff ist **exakt** `ft-over-ds=yes → no` auf `sec-main` —
|
||||
kein Kollateral-Drift durch die Re-Provisionierung. Das GC-Template wird **beim Commit** nachgezogen,
|
||||
nicht vorher: sonst wäre der laufende Versuch unsichtbar und `status` könnte ihn nicht mehr von einem
|
||||
Versehen unterscheiden.
|
||||
|
||||
🔴 **Die `cap-wifiN`-Karte hat sich durch die Re-Provisionierung VERSCHOBEN** — wer die Logs vom
|
||||
31.08. auswertet, muss den Schnitt kennen, sonst ordnet er Ereignisse dem falschen AP zu:
|
||||
|
||||
| | vor 08:09 (Log 07:xx) | ab 08:09 |
|
||||
|---|---|---|
|
||||
| ap1 (funkstill) | — | `cap-wifi1` / `cap-wifi2` |
|
||||
| ap2 | `cap-wifi1` / `cap-wifi2` | `cap-wifi3` / `cap-wifi5` |
|
||||
| ap3 | `cap-wifi5` / `cap-wifi6` | `cap-wifi4` / `cap-wifi6` |
|
||||
|
||||
⚠️ **Korrektur am Befund: `main` unterscheidet sich in ZWEI Attributen, nicht einem.** Neben
|
||||
`ft=yes ft-over-ds=yes` fährt `sec-main` als einzige SSID **`authentication-types=wpa3-psk` allein**;
|
||||
die fünf anderen sind `wpa2-psk,wpa3-psk`. Die Kontrollgruppe trennt also FT **und** WPA3-only nicht
|
||||
voneinander. Das ändert nichts an der Hebelreihenfolge (die Foren-Hypothese nennt genau die
|
||||
Kombination WPA3+FT), aber die Formulierung „genau ein Attribut" ist zu stark — **wenn Hebel 1 und 2
|
||||
beide nichts bewegen, ist `wpa2-psk,wpa3-psk` auf `sec-main` der nächste Kandidat, nicht `signal-range`.**
|
||||
|
||||
**Messung (Erfolgskriterium): `reauthenticating` für `F2:62:4C:E1:AF:09` geht auf ~0, ohne dass die
|
||||
anderen SSIDs neue Ereignisse zeigen. Die Zahl MUSS sich bewegen.**
|
||||
|
||||
```bash
|
||||
ssh root@10.40.20.100 "pct exec 400 -- sh -c 'cat /opt/tp5-syslog/log/10.40.20.1/2026-0*-*.log |
|
||||
grep reauthenticating | grep -oE \"([0-9A-F]{2}:){5}[0-9A-F]{2}\" | sort | uniq -c'"
|
||||
```
|
||||
|
||||
Baseline **93** (alle iPhone, alle `main`); davon **79 am 31.08., alle in Stunde 07**, der letzte um
|
||||
**07:34:43** — also **vor** dem Eingriff. Zwischen 08:00 und 08:15 kein einziges. Das ist noch **kein
|
||||
Ergebnis**, nur der saubere Schnitt.
|
||||
|
||||
*(Der Defekt war nie gegenstandslos, nur unbelegt: Fund B/C wurden am 08-20 mit ap1 auf `ether15`
|
||||
gemessen — also in genau der Stellung, in der ap1 heute wieder hängt. Er ist platzierungsunabhängig.)*
|
||||
|
||||
**✅ Erledigte Funde — Ergebnis und Merkregel (Volltext im Archiv):**
|
||||
|
||||
| Fund | Ergebnis | Merkregel, die bleibt |
|
||||
|---|---|---|
|
||||
| **A** 2,4 GHz fuhr 40 MHz statt der profilierten 20 | ✅ 08-17: Inline-`channel.*` auf `wifi1/wifi2` geleert → `2412`/`2462` @ 20 MHz | **Inline-Overrides gewinnen gegen das verlinkte Profil** — die SoT war die ganze Zeit richtig, das Gerät hat sie nicht befolgt |
|
||||
| **B** ap2 sendete 5 GHz mit **9 statt 18 dBm** | ✅ 08-17: `5220/ax/Ce/I`, **18 dBm** | Der Fix ist gemessen, **der Mechanismus nicht** — beide ersten Erklärungen (ETSI-Band C, „80 MHz kostet generell") sind widerlegt. Resthypothese + diskriminierender Test (`ap2 auf 5180/Ceee`) stehen im Archiv und gehören **vor** einen Hardwarekauf |
|
||||
| **C** 80 MHz kostete Randabdeckung | ✅ 08-17: `ch-5g` auf `20/40mhz`; ap1 `5180/Ce`, ap2 `5220/Ce`, überlappungsfrei | Unter ETSI gibt es im Indoor-Band **nur einen** 80-MHz-Block — zwei APs können dort nicht beide 80 MHz fahren. Genau deshalb war ap2 je auf 5745 gelandet |
|
||||
| **D** `w2-iot` trug einen Channel-Override auf einem **Slave**-VAP | ✅ 08-17: Overrides auf beiden APs entfernt, `w2-iot` seither `BR` | `band` ist eine Eigenschaft des **Radios**, nicht eines Slave-VAPs. War der Mechanismus hinter „alle SSIDs da, `-iot` beharrlich nicht" |
|
||||
| **Drift-Lücke** — genau diese Overrides lagen außerhalb jeder ComplianceRule | ✅ 08-18 mit dem TP28-GC-Umbau: `wifi-*`-Features hängen am Core, drei neue Rules; ein CAP hat keine Inline-Overrides mehr | **„Compliant" heißt nur: die abgedeckten Sektionen sind sauber.** Fund A und D blieben seit 08-08 unentdeckt, während die Flotte durchgehend compliant meldete |
|
||||
|
||||
🔴 **Deploy-Gotchas aus derselben Arbeit (kosten sonst zwei Deploys, Volltext im Archiv):**
|
||||
`/interface/wifi/set <if> !channel` **löscht die Inline-Sub-Properties NICHT** — es wirft nicht, es tut
|
||||
nichts; richtig ist einzeln `!channel.width !channel.band !channel.skip-dfs-channels`. Und bei
|
||||
Kanalarbeit **Breite VOR Frequenz** ändern, sonst erzeugt man auf dem Weg eine Co-Channel-Kollision.
|
||||
⚠️ `steering=`/Channel-Änderungen **re-applien alle VAPs und werfen jeden Client kurz raus** — nicht
|
||||
während eines Calls deployen, `/system backup` je AP vorher.
|
||||
|
||||
🔬 **Messwerkzeug — vier Regeln, jede aus einem Fehlbefund geboren:**
|
||||
- 🔴 **Für einen Kanal-/Umfeldscan NICHT `list_wifi_interfaces` benutzen** (Fund 2026-08-24): das Tool
|
||||
gibt `security.passphrase` **aller** Profile im Klartext aus — ein Read für die Kanalbreite hat so
|
||||
fünf WLAN-Passphrasen in ein Transkript geschrieben, den IoT-PSK inklusive. Für Kanäle reicht
|
||||
`/interface wifi channel print detail`; für die `cap-wifiN`-Zuordnung `… print where master=yes`
|
||||
mit Feldauswahl. **Der Scan selbst (`/interface wifi scan`) ist unbedenklich.**
|
||||
- 🔴 **Die `cap-wifiN`-Nummern sind NICHT stabil.** Der Manager vergibt sie in der Reihenfolge, in der
|
||||
sich die CAPs anmelden — nach dem ap1-Rückbau sind `cap-wifi1/2` = **ap2** und `cap-wifi3/4` = **ap1**,
|
||||
umgekehrt zur früher fest verdrahteten Tabelle. ⇒ **Die Zuordnung gehört zum Messlauf**, nicht zur
|
||||
Auswertungszeit: [`tp25-walk-messung.sh`](tp25-walk-messung.sh) friert sie als `ap-map.txt` ein, die
|
||||
Auswertung lässt die Namen sonst roh — **unbeschriftet ist ehrlicher als falsch beschriftet.**
|
||||
**Läufe vor dem 2026-08-22 sind nicht rekonstruierbar**; wer ihre AP-Namen zitiert, prüft sie an der
|
||||
Physik (der AP im Raum liefert dort das stärkste Signal).
|
||||
- 🔴 **Ein vom Pin abgewiesener Verbindungsversuch ist in Ping-Daten nicht von einem Funkloch zu
|
||||
unterscheiden** — und der Manager protokolliert ihn auf `info`-Ebene **gar nicht**. Android quittiert
|
||||
die Abweisung mit „Verbindung fehlgeschlagen" und **wählt das Netz ab**; danach misst der Rest des
|
||||
Rundgangs Stille. Die Klick-Konsole schlägt deshalb Alarm, sobald ein **gepinnter** Client nirgends
|
||||
hängt (`sta_iface=absent` in `sampler-<name>.csv`) — denn „der Gegen-AP trägt hier nicht" **ist das
|
||||
Messergebnis**.
|
||||
- 📐 **Eine Runde, N Messpunkte** (seit 2026-09-04): die frei roamenden Clients zeigen, was ein normaler
|
||||
Client wählt — interessant sind die **gepinnten**. Jeder Client wird **band-genau** auf einen AP
|
||||
gezwungen ([`tp25-pin-multi.sh`](tp25-pin-multi.sh), `TP25_PINSPEC="name|mac|ap|band;…"`), direkt aus
|
||||
der Klick-Konsole. Gute Referenz + schlechte Sonde = die Abdeckung steht, die Rückfallebene nicht.
|
||||
Memory `ref_gegen_ap_messung`. *(Die alte 2-Client-`access-list`-Automatik über `tp25-pin-s22.sh` ist
|
||||
mit dem Multi-Pin-Umbau entfallen; `tp25-pin-s22.sh` liefert nur noch die `map`.)*
|
||||
- ▶️ **Start ohne Werkzeugkette: `bash docs/tp25-runde.sh [name]`** — legt den Lauf unter
|
||||
`docs/messdaten/tp25-runde-<name>/` an, friert die AP-Karte ein, startet Konsole (Port 3031) und
|
||||
Messung, sagt die **vom Interface gelesene** Adresse fürs Telefon an. In der Konsole: **pinnen**
|
||||
(N Clients je AP/Band) → **gehen** (🚶/📍) → **Räume antippen** → **stoppen**; beim Beenden löst der
|
||||
Wrapper den Multi-Pin. Auswertung: `tp25-gehweg-auswertung.py --dir …`.
|
||||
- ℹ️ **Und das alles passiert TROTZ aktivem 802.11k/v und FT** (`st-main` mit `rrm=yes`/`wnm=yes`,
|
||||
`ft-over-ds=yes`, live geprüft). Die Merkregel `ref_ft_is_not_roaming` („11k/v fehlen") ist
|
||||
**historisch**. Bei −48…−55 dBm kostet eine Übergabe 0–2,6 s, meist unter einer halben Sekunde —
|
||||
**nicht die Übergabe ist teuer, sondern die schwache Zone.**
|
||||
|
||||
**User-Randbedingung (2026-08-17, wörtlich): „ich weigere mich einen anderen access point zu kaufen
|
||||
solange ich nicht das gefühl habe, wir haben alles aus dem ap rausgeholt."** Das ist die
|
||||
**Abnahmebedingung dieses TPs** — keine Hardware-Empfehlung, bevor die Parameter stimmen. Der Boden ist
|
||||
gelegt (beide APs 18 dBm, überlappungsfrei, 20 MHz auf 2,4 GHz, ap2 auf 2,5 m); was übrig bleibt, ist
|
||||
die Redundanzfrage oben.
|
||||
|
||||
- **Warum eigener TP:** Christoph hat am 2026-08-16/17 sechs konkrete Beobachtungen gemeldet und
|
||||
ausdrücklich **einen** TP dafür verlangt; sie hängen alle an derselben Ursache-Klasse (Funkparameter,
|
||||
die nicht nach Manual gesetzt sind). **TP25 übernimmt den offenen Rest von TP3.3** (Re-Walk,
|
||||
Dead-Spot-Kartierung — mit der Raumkarte vom 08-22 faktisch geleistet). TP3.2/TP3.3 stehen als Historie.
|
||||
- **Stand bei Sitzungsende 2026-08-22:** Pin gelöst, Automatik aus, `crs326-core` unverändert und
|
||||
**transaktionsfrei** — die `access-list` steht unter keiner ComplianceRule, es gab nichts zu committen.
|
||||
*(Seit 2026-08-23 gibt es die Rule `wifi-access-list`, s. TP22-Nachtrag — Fund E wäre ab jetzt
|
||||
transaktionsfähig.)*
|
||||
- 📋 **Übergabe:** [`handoff-tp25-session-20260821.md`](handoff-tp25-session-20260821.md) ·
|
||||
Messungen: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md),
|
||||
[`tp25-runde-20260822-2330-auswertung.md`](tp25-runde-20260822-2330-auswertung.md)
|
||||
- **Abh.:** TP22 ✅ (GC-Deploy-Pipeline). Verwandt **TP3.2** (`docs/wifi-p750w-coverage-and-map.md`),
|
||||
**TP29** (nutzt dieselbe Datenquelle als Sensor). **Kein Blocker für irgendetwas.**
|
||||
|
||||
#### 🔬 Fund E — 2026-09-01: der `ap2`-Teilbefund (gültig) und eine widerlegte Heilungs-Wertung
|
||||
|
||||
**Anlass:** Christoph meldete **2026-08-31 23:02** eine Störung am Schreibtisch (5G, iPhone, kein
|
||||
Telefonat). Analyse (alles read-only am Manager `crs326-core`):
|
||||
|
||||
- **AP-Karte** (stabil seit letztem CAP-Rejoin 15:57, kein Rejoin danach — Attribution belastbar):
|
||||
`cap-wifi5/6 = ap2`, `cap-wifi3/4 = ap3`, `cap-wifi1/2 = ap1` (funkstill, Radios da, 0 Clients).
|
||||
- **23:02-Ereignis:** iPhone `F2:62:4C:E1:AF:09` ping-pongt im Sekundentakt `cap-wifi5 ↔ cap-wifi6`
|
||||
= **ap2s 5G (5220) ↔ ap2s 2,4G (ch11/2462)** — Band-Steering-Thrash auf **demselben** AP, plus
|
||||
`disconnected, not responding` um 22:37:40 (−76) und **23:01:00** (−82). Feldstärke der Roams
|
||||
**−75…−88 dBm**.
|
||||
- 🔴 **Christophs Fakt kippt die Coverage-Deutung:** iPhone und **ap2 nur 2 m** entfernt, gleicher
|
||||
Raum, ap2 auf 2,3 m. Bei 2 m Sicht wären −35…−45 dBm normal. −80 auf 2 m ist **kein** Coverage-
|
||||
und **kein** FT-Problem. Dazu: **aktuell 0 Clients auf ap2**, alle 29 auf ap3 — die Clients *meiden*
|
||||
ap2. ⇒ **ap2s Funk selbst unterperformt** (RF/Antenne/HW) und/oder die **DTU-Störung auf ap2/ch11**
|
||||
(Transaktion `TP17.1-dtu-pin-ap2`, Wechselrichter `54:F2:9F:12:51:F0` mit −59 dBm auf ap2s 2,4-Radio).
|
||||
- **In-situ zu messen** (nachts nicht möglich — Phone idle auf ap3): Registration-Signal live prüfen,
|
||||
während ein Client auf ap2 hängt; Kandidaten-Eingriffe — DTU-Pin lösen/verlegen, `dirtyvader-main`
|
||||
auf ap2 temporär deaktivieren (ap3 übernimmt den Schreibtisch), ap2-Antenne/HW prüfen. **Keine
|
||||
Änderung ohne diese Messung.**
|
||||
|
||||
🔴 **WIDERRUFEN am 2026-09-01 vormittags — der zweite Teil dieses Abschnitts war ein Messfehler.**
|
||||
Ursprünglich stand hier: „Fund E ist im sauberen Fenster geheilt — 0× `reauthenticating` seit dem
|
||||
14:36-Reboot (`sinceMinutes=1440`, total 0), also hat Hebel 1 sein Ziel erreicht."
|
||||
|
||||
**Warum das nicht trägt:** `get_log` liest den **flüchtigen Ringpuffer des Routers**, nicht den
|
||||
Kollektor. Live nachgemessen fasst er **1000 Zeilen und reicht bis 01.09. 00:38:58 zurück** — keine
|
||||
9 Stunden. Der Core-Reboot um 14:36 hatte ihn geleert; der 31.08. Vormittag war darin gar nicht
|
||||
enthalten. `total 0` hieß **„im Puffer nichts", nicht „in 24 h nichts"**. Das ist exakt die Falle,
|
||||
wegen der am 30.08. das persistente Logging gebaut wurde.
|
||||
|
||||
**Was der Kollektor zeigt:** im upgrade-freien Fenster 08:09–14:36 (Hebel 1 **allein**) **124×
|
||||
`reauthenticating`** — 09h=1 · 11h=2 · 12h=20 · 13h=31 · 14h=70, **alle vom iPhone allein**, dazu
|
||||
Christophs zwei erlebte Ausfälle 13:16 und 14:23. Zwischen 09:00 und 14:36 lag **kein Reboot**.
|
||||
⇒ Hebel 1 wurde am 2026-09-01 **zurückgebaut**; der Rückbau ist das entscheidende Experiment
|
||||
(s. Ist-Stand-Block oben).
|
||||
|
||||
✅ **Der `ap2`-Teilbefund oben bleibt davon unberührt und gültig** — er ist unabhängig gemessen und
|
||||
erklärt die 23:02-Störung, die weder FT noch Coverage erklärt.
|
||||
|
||||
**Commit-Weg (wartet auf Christophs Go — git-push-Kette):** Template `nautobot/mikrotik_routeros.j2`
|
||||
**Z. 220** `sec-main` von `ft-over-ds=yes` → `ft-over-ds=no`; dann Template-Repo nach Nautobot syncen,
|
||||
`gc-configplan.py --refresh-compliance` (Backup→Intended→Compliance), dann
|
||||
`gc-transaktion.py commit --change-id TP25-fund-e-ft-over-ds`. Danach `wifi-security` wieder driftfrei.
|
||||
🔴 Kreuzt git-push (Backup-Guard + Commit-Hinweis-Regel) und Live-Nautobot-Jobs — **nicht autonom.**
|
||||
Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-verdacht.md).
|
||||
> **Abgeschlossen:** Platzierung final (ap1 Sideboard · ap2 Technik UG · ap3 Technik OG), Fund E
|
||||
> geschlossen (`7.24.1` war die Ursache, nicht `ft-over-ds`), alle vier Handoff-Punkte aufgelöst,
|
||||
> Gitea #17 closed. Volltext (Kondensat + Langfassung) im Archiv.
|
||||
|
||||
<a id="tp28"></a>
|
||||
|
||||
@@ -1867,20 +1369,24 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört
|
||||
> Gerät. Deshalb steht hier die Liste, nicht der Termin.
|
||||
|
||||
- **Auslösekriterium (statt Datum):** wenn die offenen TPs abgearbeitet sind bzw. Christoph das
|
||||
Projekt schließt. 🔴 **Harte Ausnahme, die den Termin vorzieht:** ein *neuer* Leak, ein
|
||||
fremdzugängliches Gerät oder ein Personenwechsel — dann sofort und einzeln, nicht gebündelt.
|
||||
Projekt schließt. *(2026-09-21: eine frühere „harte Ausnahme für neue Leaks" wurde **gestrichen** —
|
||||
sie widersprach dem Bündelungszweck dieses TPs und der Praxis: auch die leak-getriebenen Posten
|
||||
#1 und #5–#9 stehen unverändert im Bündel. Ein Transkript-Leak zieht **keinen** Posten vor.)*
|
||||
|
||||
| # | Credential | Umfang | Anlass | Vorarbeit | Preis der Rotation |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | **MikroTik `admin`** | core · branch · ap1 · ap2 (**ein** geteilter Wert) | 🔴 **zweimal in ein Transkript geraten** (2026-08-22 via `sshpass -p`; 2026-08-23 durch einen Enumerationsfehler) | ✅ **Etappe 1 durch** (s.u.) | nach Etappe 2 nur noch `~/.secrets/credentials.env` + `~/.claude.json` + MikroMCP-Neustart |
|
||||
| 2 | **IoT-WLAN-PSK** (`sec-iot`) | `dirtyvader-iot`, 26 Clients | steht **im Klartext** im TP17-Text ⇒ auch in der **Git-Historie**; Redigieren holt ihn dort nicht raus | Hebel `multi-passphrase` am Core verifiziert vorhanden, Tabelle leer | 23 Shellys per Skript + **3 von Hand** (`.249` NSPanel Pro, `.244` Nuki Bridge, `.227` SwitchBot) |
|
||||
| 3 | **Shelly-Gerätepasswort** *(vormals TP17.2)* | 23 Shellys | Härtung (bisher **keine** Auth auf den Geräten) | ✅ Werkzeug fertig (`migrate.py cmd_auth` + Digest-Fallback) | **je Gerät ein HA-Re-Auth-Flow** — das ist der Aufwand, nicht `SetAuth` |
|
||||
| 4 | **CF-Access-Service-Token** (`CF_API_TOKEN`) | Cloudflare-Zugang, s. TP14.4 | ⏳ **läuft am 2027-01-09 ohnehin ab** — harter Termin, unabhängig vom Projektende | Runbook liegt (`tp14.4-cf-access-token-runbook.md`) | Token neu ausstellen + `~/.secrets` nachziehen; **account-owned**, anderer verify-Endpoint |
|
||||
| 4 | **CF-Access-Service-Token** (`CF_API_TOKEN`) | Cloudflare-Zugang, s. TP14.4 | 🔴 **2026-09-21 erneut ins Transkript geraten** (Session-`grep` unmaskiert). Der bisherige harte Termin ist **entfallen**: ⏳ „2027-01-09" galt für das längst revokte Alt-Token `9984078812bc…`; das **live** `CF_API_TOKEN` (id `493ed8bd…`) hat **kein** Ablaufdatum — verifiziert 2026-09-21 via `/accounts/{id}/tokens/verify`, `expires_on: null` | Runbook liegt (`tp14.4-cf-access-token-runbook.md`) | Token neu ausstellen + `~/.secrets` nachziehen; **account-owned**, anderer verify-Endpoint |
|
||||
| 5 | **Nautobot-Tokens** (`NAUTOBOT_SUPERUSER_API_TOKEN`, `NAUTOBOT_GITEA_TOKEN`) | Nautobot-Superuser + Gitea-Anbindung | 🔴 in der TP16.3-§2/§3-Session (2026-08-31) durch schwache `sed`-Maskierung **ins Transkript** geraten | Werte in `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored) | neu ausstellen + Stack-YML nachziehen; Superuser-Token wird von den `gc-*`-Skripten gelesen |
|
||||
| 6 | **MariaDB `root`** (LXC 302, `10.40.20.23`) | root-DB-Login | 🔴 am 2026-09-01 (TP39) ins Transkript geraten — die `/root/.my.cnf` trug `password <wert>` **ohne `=`**, die Feld-Maskierung ankerte auf `=` und griff nicht | Wert in `/root/.my.cnf` (chmod 600) | `SET PASSWORD` + `.my.cnf` nachziehen; **kein** externer Konsument (Dienste nutzen eigene DB-User) ⇒ billigste Rotation der Liste |
|
||||
| 7 | **`TERPENE_ANDROID_KEYSTORE_PW`** | Terpene-Android-TWA-Signing (`android/android.keystore`, alias `terpene`) | 🔴 am 2026-09-01 (TP41) durch **Regex-Suffix-Loch** (`_PW` nicht abgedeckt) ins Transkript geraten | Wert in `~/.secrets/credentials.env` + Keystore-Datei | **Store-/Key-Passwort** via `keytool -storepasswd`/`-keypasswd` ändern + `credentials.env` nachziehen; Konsument = Android-Build/Play-Signing (**Cross-Projekt blaueblüten**) — den **Key selbst NICHT** neu erzeugen |
|
||||
| 8 | **`N8N_SMTP_PASS`** (Gmail-App-PW für `johnny.claw.0x00@gmail.com`) | SMTP-Versand aus n8n (Portainer-Stack 174 `n8n`, macmini) | 🔴 am 2026-09-01 (n8n-mcp-SSRF-Session) beim Ausgeben des Stack-174-Files ins Transkript geraten — Redaktionsmuster hatte `PASSWORD`, aber **nicht bare `PASS`** → `N8N_SMTP_PASS=<wert>` blieb stehen | Wert in Portainer-Stack 174 (`environment`) | **Gmail-App-Passwort widerrufen** (Google-Konto → App-Passwörter) + neues erzeugen, Stack-174-Env nachziehen + redeploy; Konsument = n8n-SMTP-Node(s) |
|
||||
| 9 | **`GITEA_ACCESS_TOKEN`** (API-Zugang `gitea.emsenhuber.net`) | Session-Tooling (`bin/gitea-tp-sync.py`, Ad-hoc-API-Calls) | 🔴 am 2026-09-14 (TP25-In-situ-Session) **ins Transkript** geraten: Env-Parse-Fehler schob den Token in die URL-Konstruktion, `urllib` warf `ValueError: unknown url type` und druckte ihn im Traceback | Mechanismus zu: Token gehört **nur in den `Authorization`-Header, nie in eine URL** — und der Fehlerpfad, der ihn enthält, darf ihn nicht ausgeben (gleiche Familie wie #1/#5/#6/#7/#8, diesmal Unterklasse „Fehlerausgabe statt Maskierung") | neu anlegen (Gitea → Settings → Applications), `~/.hermes/.env` nachziehen, altes widerrufen; Konsumenten = `gitea-tp-sync.py` + Session-Ad-hoc-Calls |
|
||||
| 10 | **`N8N_MCP_BEARER`** (n8n-Public-API) | n8n-MCP `macmini:3001` — in **7 Projekten** referenziert (`bf-infra`, `blaueblueten-n8n`, `travelmate`, `poulios-recherche`, `ha-xstate`, `valamar`, `uwe-job-bewerbung`) | 🔴 am 2026-09-22 (Env-/OpenRouter-Session) **ins Transkript** geraten: die Redaktions-Alternation führte `BEARER` nicht, und der Schlüssel endet **ohne `=`** auf den Bezeichner — die Feld-Ersetzung hatte damit keine Ankerstelle | Ablage nur `~/.secrets/exports.zsh`; alle Konsumenten sind `${…}`-Referenzen (kein Klartext in den Repos, geprüft) | Token in n8n neu erzeugen, `~/.secrets` nachziehen, **App-Neustart** (MCP-Env wird beim Start gelesen) — danach 7 Projekte ohne Edit, weil sie nur referenzieren |
|
||||
| 11 | **`TERPENE_RO_URL`** (PG-User `terpene_ro`, DB `terpene`) | Terpenfinder-DB read-only `10.40.20.20:5432` — **Cross-Projekt (blaueblüten)**; einziger Konsument ist `~/.secrets/exports.zsh` (+ `.bak`) | 🔴 am 2026-09-22 (Env-/OpenRouter-Session) **ins Transkript** geraten — **neue Unterklasse derselben Familie:** das Passwort steckt in der **URL** (`…://user:PASS@host`), die Feld-Maskierung ankert auf `=` und findet dort keine Zeile zum Ersetzen (dieselbe Lücke wie #9: was nicht als Feld auftritt, wird nicht maskiert — das geteilte Redaktions-Snippet deckt diesen Fall **nicht** ab) | Wert nur in `~/.secrets/exports.zsh`; **kein** Konsument in Code oder Configs | `ALTER ROLE terpene_ro … PASSWORD '<neu>'` am NAS (`10.40.20.20`), dann `exports.zsh` nachziehen; **kein** Geräte-Roundtrip ⇒ billigste Rotation nach #6. ⚠️ Cross-Projekt: nur mit Christophs Okay anfassen |
|
||||
| 12 | **OpenRouter-API-Key** (`sk-or-v1-cb6…258`) | Claude-Desktop-3P-Gateway: CCR-Provider in `~/.claude-code-router/config.sqlite` (`Providers[].api_key`) **+** 3P-Config `e1219ea4` (`inferenceGatewayApiKey`) | 🔴 am 2026-09-23 (Claude-Desktop/OpenRouter-Session) ins Transkript geraten — das `sed` ankerte auf den **geratenen** Namen `apiKey`, der echte Schlüssel heißt `inferenceGatewayApiKey` ⇒ kein Match (gleiche Familie wie #5/#10/#11, neue Unterklasse s. Merkregel unten). Zweiter Fund derselben Session: `head -c 400` auf `~/.claude-code-router/bin/ccr-claude-code-api-key-claude-code` gab den **CCR-Profil-Token** aus (local-only `127.0.0.1`, geringe Reichweite — nicht in der Zählung) | ⚠️ **kein `~/.secrets`-Eintrag** — der Wert lebt nur in `config.sqlite` + 3P-Config, und er ist **noch gültig** (200, verifiziert 2026-09-23 via `/api/v1/key`) | neu ausstellen (openrouter.ai/keys); danach **zwei** Konsumenten: `config.sqlite` (`Providers[].api_key`, mit CCR-Neustart) + 3P-Config (`inferenceGatewayApiKey`) — beide müssen mit, sonst steht die App still (3P ist alles-oder-nichts) |
|
||||
|
||||
> ✅ **Der Leak-MECHANISMUS hinter #1 ist seit 2026-08-31 zu — der Wert bleibt trotzdem verbrannt.**
|
||||
> Alle sechs Skripte im Repo, die `sshpass -p <PW>` benutzten (`nautobot/gc-transaktion.py` und
|
||||
@@ -1923,6 +1429,18 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört
|
||||
> fehlerhaften Wert nicht mitschleppen). Das Redaktions-Snippet oben hilft hier nicht — es
|
||||
> kann nichts maskieren, was als Fehlermeldung und nicht als Dateiinhalt kommt.
|
||||
>
|
||||
> 🔴 **#12 ist die siebte Panne derselben Familie — und wieder eine neue Unterklasse** (SHELLY 08-28,
|
||||
> Nautobot-Tokens 08-31, MariaDB-root #6, Keystore-`_PW` #7, SMTP-`PASS` #8, Gitea-Traceback #9):
|
||||
> diesmal war die Quelle **JSON**, nicht Env. Das geteilte Snippet oben ist auf **`KEY=VALUE`-Zeilen**
|
||||
> gebaut (Anker `([A-Z0-9_]*(…|KEY|…))=[^[:space:]]*`) — es kann `"inferenceGatewayApiKey": "…"`
|
||||
> gar nicht treffen: **camelCase**-Schlüssel, kein `=`, kein Zeilenanfang. Wer JSON mit dem Env-Snippet
|
||||
> redigiert, maskiert **nichts** — und genau das ist passiert; das danach handgetippte `sed` ankerte
|
||||
> auf den **geratenen** Namen `apiKey` statt auf den echten. **Merkregel: für JSON einen JSON-bewussten
|
||||
> Masker benutzen** — Objekt laden, über **Schlüsselnamen** filtern (case-insensitiv, Teilstring ok),
|
||||
> jeden Treffer durch `<redacted>` ersetzen; nie das Env-Snippet, nie ein geratenes Muster.
|
||||
> Und: **Schlüsselnamen erst auflisten, dann maskieren** — „ich weiß schon, wie der Key heißt" ist
|
||||
> genau die Annahme, die hier gerissen ist.
|
||||
>
|
||||
> ℹ️ **#3 war bis 2026-08-24 TP17.2** und ist jetzt vollständig hier (s. Detailabschnitt unten) —
|
||||
> es benutzt denselben Geräte-Durchgang wie #2, **ein Reboot statt zwei**. #4 gehört sachlich zu
|
||||
> **TP14.4**, das als TP bestehen bleibt (dort steckt das CF-Access-Profil, nicht nur der Token).
|
||||
@@ -2037,8 +1555,12 @@ Es ist **ein** Passwort für alle vier Geräte — die „Überlebensversicherun
|
||||
- **CF-Access-Service-Token** — **Rotationsposten #4**, am 2026-08-24 aus den Follow-ups hierher
|
||||
gezogen (stand dort als „TP14.4 — CF-Access-Service-Token rotieren"). Sachlich gehört er zu
|
||||
**TP14.4** (CF-Access-Profil für Claude, Runbook `docs/tp14.4-cf-access-token-runbook.md`); hier
|
||||
steht er, damit die Rotationsliste vollständig ist. ⚠️ Der `CF_API_TOKEN` läuft ohnehin
|
||||
**2027-01-09** ab — spätestens dann ist dieser Posten fällig, unabhängig vom Projektende.
|
||||
steht er, damit die Rotationsliste vollständig ist. ⚠️ ~~Der `CF_API_TOKEN` läuft ohnehin
|
||||
**2027-01-09** ab~~ — **korrigiert 2026-09-21:** dieses Datum gehörte zum revokten Alt-Token
|
||||
`9984078812bc…`; das **live** `CF_API_TOKEN` (id `493ed8bd…`) hat **kein** Ablaufdatum
|
||||
(`expires_on: null`, via `/accounts/{id}/tokens/verify`). Der harte Termin ist damit weg —
|
||||
**Anlass** ist jetzt der **Leak vom 2026-09-21** (🔴 siehe Posten #4 oben). Der Posten bleibt
|
||||
**gebündelt** und wird nicht vorgezogen (Auslösekriterium oben).
|
||||
|
||||
#### Posten #3 im Detail — Shelly-Gerätepasswort (`SetAuth`), vormals TP17.2
|
||||
*Am 2026-08-24 als eigenes TP aufgelöst und hierher gezogen — Text unverändert übernommen.*
|
||||
@@ -2506,7 +2028,7 @@ wo er ist, ohne nachzusehen.**
|
||||
5. **Backup-Guard** — `~/dev/.bin/backup-guard.sh`, Pre-Commit-Hook pro Projekt, „3 Tage aktiv"-Definition: 5 Commits oder 1000 LOC diff. **✅ 2026-09-12 abgenommen:** Guard (112 Z., `check`/`mark`/`status`) + `.git/hooks/pre-commit` in `bf-infra` live verdrahtet; der Hook hat beim TP36-Live-Commit real gegriffen („Projekt aktiv 12 Commits/198 LOC, Backup markiert"). ⚠️ **Bekannte Grenze:** `check` prüft nur *Existenz* der Guard-Datei, nicht deren Alter — ein alter `mark` (hier 2026-08-29) lässt den Commit durch. Das ist als Push-Erinnerung gewollt, keine Frische-Garantie.
|
||||
6. **`/projektstatus`-Skill global** — als Erstes, vor den anderen Schichten. Alle Projekte verwenden dasselbe Skill. **✅ 2026-08-28:** `/projektstatus`-Skill nach `~/.config/opencode/skills/projektstatus/SKILL.md` kopiert (`cp`, nicht `ln -s`) — opencode scannt diesen Default-Pfad, kein `skills.paths`-Eintrag in der JSONC nötig. Lehre im CLAUDE.md-Lessons-Block.
|
||||
- **Abgrenzung:** TP35 ist Konzept + Schichtung, nicht die Umsetzung. Pro Projekt eigene Session, eigener Handover-Post, eigene Mini-Anpassung.
|
||||
- **Verzahnt mit:** TP30 (Credentials), TP34 (Repository-Pattern), TP22 (Nautobot-GC als Vorbild für SSoT + Compliance), TP11 (Monitoring).
|
||||
- **Verzahnt mit:** TP30 (Credentials), TP34.2 (Daten→Config-Pattern), TP22 (Nautobot-GC als Vorbild für SSoT + Compliance), TP11 (Monitoring).
|
||||
- **Erste Session für Scope-Ausarbeitung:** Schichten 1–5 + Skill-Setup; Handover-Posts pro Projekt (Schicht 3 + 4 ergeben die Posts).
|
||||
- **Referenzen:** Handoff 2026-08-28 (`docs/handoff-20260828-pre-opencode-migration.md`) · Backups `~/dev/.pre-opencode-migration-backups/20260828-150335/` · MCP-Inventur `bf-infra/mcp/INVENTORY.md` · Lessons `bf-infra/CLAUDE.md` Abschnitt „Lessons for Sessions" · `docs/handoff-20260828-mcp-bestand-und-performance.md` (MCP-Bestand-Korrektur + Mac-mini-Restriktion).
|
||||
|
||||
@@ -2676,9 +2198,12 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
- **Abgrenzung (wichtig, sonst überlappt es mit zwei offenen TPs):**
|
||||
- **TP34** (Caddyfile via GC) rendert *Config aus Daten*. TP37 liefert **die Daten**, auf denen TP34
|
||||
aufsetzt — Punkt 1+2 sind TP34s fehlende Voraussetzung. **TP37 vor TP34.**
|
||||
⚠️ **Nachtrag 2026-09-22:** TP34 ist **❌ verworfen** (09-08, #24 — der Caddyfile bleibt manuell);
|
||||
der *Daten → gerenderte Config*-Gedanke lebt als **[TP34.2](#tp342)** (interne DNS-Namen) weiter.
|
||||
- **TP36** (SSoT für `teilprojekte.md`) rendert *Doku aus Daten* und braucht dieselben Custom Fields.
|
||||
Läuft parallel, gleiches Fundament.
|
||||
- **Nicht** in TP37: Golden Config für Nicht-Netzgeräte (Caddy/AdGuard deployen) — das ist TP34.
|
||||
- **Nicht** in TP37: Golden Config für Nicht-Netzgeräte (Caddy/AdGuard deployen) — das ist **TP34.2**
|
||||
(das Caddy-Teilstück davon bleibt ❌ verworfen).
|
||||
**Nicht** in TP37: Cloudflare-Writes aus Nautobot heraus.
|
||||
- **Abh.:** TP21/TP22 ✅ (Nautobot + GC-Pipeline stehen) · TP32 ✅ (Nummern-Systematik liefert die
|
||||
Klassifikation für Punkt 1) · TP16.3 (liefert den Anwendungsfall und den Beleg, warum es fehlt).
|
||||
@@ -2694,12 +2219,41 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
Datenmodell **entschieden 2026-09-06 (native Modelle: `virtualization.Cluster/VirtualMachine` +
|
||||
`ipam.Service`, keine App)**; Runbook [`handoff-20260906-tp37-34-36-plan.md`](handoff-20260906-tp37-34-36-plan.md).
|
||||
**Offen bleibt Punkt 4** (Drift-Check gegen die CF-API) — und der Weg **Daten → gerenderte Config**
|
||||
ist nicht mehr TP37, sondern **TP34/TP45 Schicht C**.
|
||||
ist nicht mehr TP37, sondern **TP34.2 / TP45 Schicht C**.
|
||||
⚠️ **Merkregel:** die Zeile stand seit dem 09-06 auf „offen", obwohl die Seeder längst gelaufen waren.
|
||||
Ein Status, der nur in einer Datei lebt, altert still — der Abgleich gegen die API kostet eine Minute.
|
||||
|
||||
---
|
||||
|
||||
<a id="tp342"></a>
|
||||
### TP34.2 — Interne DNS-Namen als Daten (FQDN aus Nautobot) ▸ 🔜 **ANGELEGT 2026-09-22** (Gitea #73) — **gemeinsame Arbeit**
|
||||
> **Auslöser:** beim Schärfen von **TP45 Schicht C (C4)** am 2026-09-22 stand die Frage, was aus dem
|
||||
> verworfenen **TP34** wird. Christoph: *„C4 unter TP45 schärfen, TP34 wird gemeinsame Arbeit. wir beide!"*
|
||||
> → **eigener Träger im TP34-Nummernkreis**. Parent **#24 (TP34) bleibt `status:dropped`** — kein Reopen:
|
||||
> dort wurde *Caddyfile-Rendering* verworfen, hier geht es um *interne DNS-Namen als Daten*. **Kein
|
||||
> Zwillings-TP mit neuer Nummer**, aber auch kein Wiederbeleben eines anderen Gegenstands.
|
||||
|
||||
- **Scope:** Die **27 internen `A`-Statics** auf `crs326-core` (`*.bf.emsenhuber.net`, live geprüft
|
||||
2026-09-22) werden **Daten in Nautobot**; ein Renderer schreibt sie als GC-Soll. **Abnahme:** ein
|
||||
Golden-Config-Recovery von null erzeugt einen Core, in dem die internen Namen **wieder auflösen**.
|
||||
- **Ausdrücklich NICHT Scope:** Caddyfile-/Reverse-Proxy-Rendering (der verworfene TP34-/`.1`-Ansatz) —
|
||||
der `caddy/Caddyfile` bleibt **manuell**.
|
||||
- 🔴 **Warum es das braucht:** die Namen existieren heute **nur am Gerät** — Handarbeit mit
|
||||
Freitext-Kommentaren (`"caddy -> nautobot :8080"`), und GC rendert sie **nirgends**. Ein Recovery
|
||||
von null erzeugt damit einen Core **ohne interne Namensauflösung**: dieselbe **Recovery-Lücke** wie
|
||||
die DHCP-Reservierungen und die `nautobot`-Adressliste (beide inzwischen als Renderer nachgezogen,
|
||||
TP45 Schicht B). ⚠️ **Compliance meldet trotzdem grün — die Zahl prüft nur, was gerendert wird.**
|
||||
- **Datenlage / offener Modell-Entscheid:** `ipam.Service` (**26**) und `ipam.IPAddress` (**38**) stehen
|
||||
(TP37 P1+2 ✅). Für DNS braucht es voraussichtlich `ipam.IPAddress` + `dns_name` je Gerät/Dienst, mit
|
||||
**zwei Zielarten**: Geräte-Statics zeigen auf die echte IP, Proxy-Namen auf **`10.40.20.40`** (Caddy)
|
||||
— dazu je Name ein Kommentar-Feld. Der eigentliche Modell-Entscheid steht noch aus (**nicht raten**).
|
||||
- **Abh.:** TP37 ✅ (Datenmodell) · TP45 **Schicht A** ✅ / **E** offen (die Namen müssen auf die **neuen**
|
||||
Gast-IPs zeigen) · TP21/22 ✅ (GC-Pipeline). **Blockiert:** nichts.
|
||||
- **Abgrenzung:** die `coolify.bf`-Frage bleibt in **TP45 Schicht C** (Port unbekannt — nicht raten);
|
||||
Token-**Rotation** ist TP30.
|
||||
|
||||
---
|
||||
|
||||
### TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot ▸ 🔜 **ANGELEGT 2026-09-21** (Gitea #71)
|
||||
> **Auslöser (Christoph, 2026-09-21):** *„schau dir mal die fqdns von `*.emsenhuber.net` an. viele
|
||||
> verschwunden. sowohl caddy als auch cloudflare."* — und, als der Befund stand:
|
||||
@@ -2737,11 +2291,41 @@ unter `*1A TP5-WOULD-DROP` / `*2D TP5: deny-by-default` (621 Pakete).
|
||||
|---|---|---|
|
||||
| **A** | VLAN20 virtuelle Gäste ins Schema — **Nautobot zuerst** (`.30–.34` → `.154–.156`/`.158`), dann AGH | 🔄 **Nautobot ✅ 2026-09-21** (s. u.) · AGH + übrige Gäste offen |
|
||||
| **B** | VLAN30 — **nur die Pool-Grenze**, Geräte bleiben (Christoph 2026-09-21) | offen |
|
||||
| **C** | DNS/FQDN: `ds-bf` über Caddy, `coolify.bf` neu, `.bf` vollständig auf den Core, AGH nur Filtering, TP34 | offen |
|
||||
| **C** | DNS/FQDN: `ds-bf` über Caddy, `coolify.bf` neu, `.bf` vollständig auf den Core, AGH nur Filtering, TP34.2 | ✅ **C1–C3 durch 2026-09-22** (s. u.) · offen: **C4 → TP34.2** (Gitea #73) + `coolify.bf` |
|
||||
| **D** | Tunnel-Inventar + GC + generische Namen, TP30 #10, Leak-Ursachen | offen |
|
||||
| **E** | nas/pve/macmini mit Doppeladresse — **eigene Session**, pve2 nur reservieren | offen |
|
||||
| **E** | nas/pve/macmini — **neue IPs zuerst am Core** (Sekundär-IP + 1:1-NAT), dann messen und umstellen; nas zuletzt | offen |
|
||||
| **F** | CF-/Tunnel-SSoT + Recovery → **TP46** | offen |
|
||||
|
||||
##### Schicht E — Mechanik (Christoph-Entscheid 2026-09-21, ersetzt „Doppeladresse am Host zuerst")
|
||||
|
||||
**Neue IPs zuerst am Core, nicht am Gerät:** Sekundär-IPs `.110` (nas) · `.121` (pve) · `.130`
|
||||
(macmini) auf der Core-`vlan20` **plus 1:1-NAT** auf das Gerät (`dst-nat` + `src-nat`, platziert
|
||||
**vor** der `no-NAT inter-VLAN`-Regel). 🔴 **Routing allein reicht nicht** — im selben Subnetz
|
||||
antwortet das Gerät dem Client direkt per L2, der Client verwirft eine Antwort von `.10` auf eine
|
||||
Verbindung zu `.110`; der Rückweg braucht `src-nat`, und die Adresse muss **am Core liegen**, sonst
|
||||
beantwortet er kein ARP dafür (**nicht** `proxy-arp` — das antwortet für *jede* freie Adresse im
|
||||
/24). Heute ist die Core-NAT-Kette leer (nur `no-NAT inter-VLAN` + WAN-Masquerade) ⇒ Neubau.
|
||||
|
||||
**Ab dann gemessen:** zwei Zähler je Host — auf der **neuen** IP (NAT-Regel: „wer ist schon
|
||||
umgestellt") und auf der **Alt-IP** (`action=passthrough`, `comment="dualaddr-watch: …"`: „ist noch
|
||||
was los"), beide ohne `log`. **Erst wenn der Alt-IP-Zähler über N Tage stillsteht**, wird das Gerät
|
||||
**zum Siedeln vorgemerkt**: neue IP aufs Gerät → NAT + Sekundär-IP + Zähler raus → Alt-IP entfernen
|
||||
→ Doku/Nautobot nachziehen. Alles **ins Soll**, nicht als Transaktion (Christoph: *„wir haben einen
|
||||
Ist- und einen Sollzustand; die Regel kann uns nicht vorschreiben, wie lange wir für die Überführung
|
||||
brauchen"*).
|
||||
|
||||
**Reihenfolge:** `nas` siedelt **zuletzt und gemeinsam** (die meisten Abhängigkeiten: SMB, Time
|
||||
Machine, Caddy-Backend `ds-bf`), `pve`/`macmini` vorher; **`pve2` nur reservieren** (`.120`).
|
||||
|
||||
**Vorabcheck ✅ 2026-09-21:** NAS-NFS (`/etc/exports`, 9 Shares) und iSCSI (`.10`/`.11`:3260) sind
|
||||
IP-ACL'd, haben aber **keine lebenden Nutzer** — pve hat **null** NAS-Mounts (einzige Remote-Storage
|
||||
ist `pbs 10.40.20.135`), kein LXC hängt am NAS, die Synology-Firewall hat keine Regeln ⇒ der
|
||||
src-nat-Umweg (gleich-subnetzige Clients sehen den Core als Quelle) **bricht nichts**.
|
||||
🔴 **Eigener Aufräumposten:** dieselben Exports stehen komplett auf totem **`10.40.41.x`** plus drei
|
||||
`10.0.0.0/8`-Freigaben (offen für jeden 10/8-Client).
|
||||
⚠️ Der NAS trägt **zwei** VLAN20-Adressen (`.10` `ovs_eth0`, `.11` `ovs_eth1`) — laut Christoph
|
||||
haben die beiden nichts miteinander zu tun; `.11` gehört **nicht** in die Umzugslogik.
|
||||
|
||||
🔴 **Warum Nautobot zuerst.** AGH will `.30`/`.31` — dort saß Nautobot (`.30`/`.31`/`.32`/`.34`).
|
||||
**28 Skript-/Config-Dateien** referenzierten `10.40.20.30` (alle `gc-*.py`, 14× `ingest-*.py`,
|
||||
`tp44-*`, `routeros/fetch-core-cutover-config.py`) — alle am 2026-09-21 mitgezogen. Und: **der
|
||||
@@ -2797,6 +2381,71 @@ bleibt als Aufräumposten offen.
|
||||
verschoben). Bei einem **erfolgreichen** Deploy ist das kosmetisch, bei einem **gescheiterten**
|
||||
nimmt es die Fehlermeldung weg — genau dann, wenn man sie braucht.
|
||||
|
||||
#### ✅ Schicht C durch (2026-09-22) — FQDN-Wiederherstellung, vorgezogen (C1–C3, Commit `6d1d56e`)
|
||||
|
||||
Auslöser: die IP-Umstellung (A/B/E) dauert länger; die internen `*.bf.emsenhuber.net`-Namen mussten
|
||||
**sofort** zurück. Drei Züge, jeder am **Gerät** *und* am **Client** belegt — nicht am Plan-Status.
|
||||
|
||||
**C1 — 17 Namen in den Core** (`nautobot/mikrotik_routeros.j2`, Deploy über den neuen GC-Job
|
||||
`tp45-c-dns`). Der Core hält jetzt **27** `/ip dns static` (vorher 10): die 9 Caddy-Frontends
|
||||
(`nautobot` `pbs` `portainer` `ccu` `iobroker` `z2m25` `ha-mcp` `n8n-mcp` `gitea-mcp`) → `.40` ·
|
||||
`tv.bf`→`10.40.40.11` · `mfc.bf`→`10.40.10.6` · **`ap3.bf`→`.99.13`** (existierte in *keiner* Quelle) ·
|
||||
`pve.bf`/`pve-mcp.bf` von Host-IP auf `.40` · dazu die **5 bare Namen** (`vw` `hass` `n8n` `gitea`
|
||||
`ds-bf`) → `.40` (Entscheid 09-22: lokaler Split-Horizon ist **gewollt**, weil `/admin` nur header-frei
|
||||
geht). 🔑 Der Job liest die Zeilen **aus dem Template selbst** und macht daraus einen **idempotenten
|
||||
Upsert** (`:if ([:len [/ip dns static find where name=X]] = 0) do={ … }`) — `add` allein ist nicht
|
||||
wiederholbar. Abnahme: Geräte-Read **27 Statics** · `--refresh-compliance` → **145/145, 0 Drifts**.
|
||||
|
||||
**C2 — AGH entdoppelt** (`adguard/AdGuardHome.yaml`, Weg `python3 adguard/agh-sync.py push`).
|
||||
`upstream_dns` = **nur** `10.40.20.1`; CF-DoH-Zeile und conditional `[/bf.emsenhuber.net/]`-Forward
|
||||
entfallen; **alle 26 Rewrites raus**. Der Core ist damit die **einzige** Quelle — auch für die bare
|
||||
Namen, die vorher über DoH **öffentlich** aufgelöst wurden und den Client an den CF-Edge schickten
|
||||
(dort setzt der Tunnel `CF-Connecting-IP` → `@cf_admin`-Guard → 404). Vorher/Nachher aller 26 Rewrites
|
||||
geprüft: **verhaltensneutral**, identisches Ziel, eine Quelle statt zwei.
|
||||
Abnahme: `agh-sync.py check` → `.36 == .37` · `/admin` **header-frei 200**, mit `CF-Connecting-IP`
|
||||
weiter **404** · Filter intakt (`doubleclick` → `0.0.0.0`) · öffentliche Auflösung intakt.
|
||||
|
||||
**C3 — `ds-bf` über Caddy** (CF-Write am Tunnel `bf-public` `48d3b0b6`): Ingress-Origin von
|
||||
`http://10.40.20.10:8080` auf `https://10.40.20.40:443` + `originRequest.originServerName`. Öffentlich
|
||||
**200 in 0,24 s** (vorher `000` nach 20 s); `vw`/`hass`/`n8n`/`gitea` unverändert. ⚠️ Der **CF-MCP darf
|
||||
lesen, aber nicht schreiben** (`1001: Not authorized` auf `PUT …/configurations`) — der Write lief über
|
||||
`CF_API_TOKEN` aus `mcp/cloudflare/.env` (Wert nie ausgegeben); Original-Ingress vorher gesichert,
|
||||
nachher gegengeprüft.
|
||||
|
||||
**WARP-Abnahme (Christophs Bedingung) ✅.** Unter verbundenem WARP löst `127.0.2.2/.3` den Namen auf
|
||||
**`.40`** auf, die Route geht über `utun8` (Mesh), `/admin` = **200**. Der zuvor gemeldete 404 des Users
|
||||
kam aus dem **Browser-/mDNSResponder-Cache** (dort lag noch die CF-Anycast-A); nach TTL-Ablauf sauber.
|
||||
**Hebel (a) (Zero-Trust Local Domain Fallback) war nicht nötig.**
|
||||
|
||||
🔴 **Befund, bewusst offen — AAAA bleibt öffentlich.** Der Core hält nur **A**-Statics und ist kein
|
||||
Zonen-Eigentümer: eine AAAA-Abfrage **reicht er weiter** und liefert für die bare Namen die **CF-IPv6**
|
||||
(`2a06:98c1::`). **Heute wirkungslos** — im Netz läuft kein globales IPv6 (der Mac hat keine globale
|
||||
Adresse; `curl -6` landet über `::ffff:10.40.20.40`). **Latent:** sobald IPv6 kommt, geht der
|
||||
`/admin`-Weg wieder über die Edge und die Tür ist zu. Christoph-Entscheid 2026-09-22: **dokumentieren,
|
||||
nicht ändern** — `aaaa_disabled: true` in AGH wäre die Ein-Zeilen-Abhilfe, ist aber netzweit „IPv4-only"
|
||||
und passt nicht zu „AGH filtert nur".
|
||||
|
||||
🔴 **Zweiter Befund — AGH ist doppelt adressiert.** LXC 110/111 tragen im Container **zwei**
|
||||
VLAN20-Adressen: `eth0` `.36`/`.37` (aus `net0`) **plus** `eth1` `.30`/`.31`. Schicht A ist dort
|
||||
**halb** umgestellt — die neue IP ist live und wird per DHCP verteilt (`dns-server=10.40.20.30,10.40.20.31`),
|
||||
die Alt-IP lebt noch. Gehört in die „siedeln"-Messung, nicht in C. ℹ️ **Korrektur einer eigenen
|
||||
Fehlannahme:** `adguard/agh-sync.py` nennt `.36`/`.37` — das ist **kein** Drift, sondern die
|
||||
`net0`-Adresse; nur die Konstante kennt die zweite Adresse nicht.
|
||||
|
||||
🧪 **Messfalle, die fast ein Fehlalarm wurde:** `dig nautobot.bf` **ohne** `.emsenhuber.net` fragt den
|
||||
Namen `nautobot.bf.` im **Root** ab und liefert leer — sieht wie ein nicht gefixter Name aus, war aber
|
||||
die eigene Abfrage. Dieselbe Klasse wie `nc -z` (09-01) und `pct push` (09-21): **erst das eigene
|
||||
Werkzeug verdächtigen.**
|
||||
|
||||
**Offen in C:** `coolify.bf` (nicht ausgerollt, Port unbekannt — **nicht raten**) · **C4 = die internen
|
||||
Namen als Daten** → eigener Träger **[TP34.2](#tp342)** (Gitea #73, angelegt 2026-09-22, gemeinsame Arbeit).
|
||||
🔴 **Was C4 genau ist:** die **27** `A`-Statics auf `crs326-core` sind heute Handarbeit (live geprüft
|
||||
2026-09-22: 27 Einträge, Freitext-Kommentare, `coolify.bf` fehlt) und werden **von GC nirgends gerendert**
|
||||
⇒ ein Recovery von null erzeugt einen Core **ohne** interne Namensauflösung — dieselbe **Recovery-Lücke**
|
||||
wie die DHCP-Reservierungen und die `nautobot`-Adressliste. **Nicht** C4 ist Caddyfile-Rendering: das war
|
||||
TP34 und bleibt ❌ verworfen (09-08). Kosmetisch zurückgestellt: die `ds-bf`-Site steht im `caddy/Caddyfile`
|
||||
weiter im INTERN-Block (nur Beschriftung; ein Caddy-Redeploy allein dafür wäre unverhältnismäßig).
|
||||
|
||||
#### Soll-Oktette (Nachschlagetabelle, s. TP32 für die Herleitung)
|
||||
| Oktett | Rolle | Soll |
|
||||
|---|---|---|
|
||||
@@ -2823,9 +2472,10 @@ NAT/mangle/WG sind Template-Literale, der Firewall-Filter kommt als ConfigContex
|
||||
Lichterketten `.236`/`.237`, Withings `.230`/`.231` … → Christoph: **„VLAN30 nur Pool-Grenze"**.
|
||||
- Pool-Fund: `pool-v20` `.100–.200` hat **`used=0`** (gratis verschiebbar); `pool-v30` `.100–.250`
|
||||
enthält **~25 Statics** → Pool und Statics tauschen praktisch die Plätze.
|
||||
- ⚠️ **`ap3.bf` existiert nirgends** — weder Core-Static noch AGH-Rewrite, obwohl ap3 seit 08-30 live ist.
|
||||
- ⚠️ **`ap3.bf` existierte nirgends** — weder Core-Static noch AGH-Rewrite, obwohl ap3 seit 08-30 live
|
||||
ist. ✅ **Behoben in C1 (09-22):** `ap3.bf` → `.99.13` als Core-Static.
|
||||
|
||||
- **Abh.:** TP32 (Systematik) · TP37 ✅ (Nautobot-Datenmodell) · TP21 ✅. **Blockiert:** TP34 (rendert
|
||||
- **Abh.:** TP32 (Systematik) · TP37 ✅ (Nautobot-Datenmodell) · TP21 ✅. **Blockiert:** TP34.2 (rendert
|
||||
die FQDNs, die hier Daten werden). **Abgrenzung:** VMID-Umnummerierung bleibt draußen; Token-**Rotation**
|
||||
läuft in TP30; Tunnel-GC-Namensgebung hier, die **SSoT/Recovery** in TP46.
|
||||
|
||||
@@ -2970,7 +2620,7 @@ den Weg, über den man den Fehler zurücknehmen wollte.
|
||||
- **🔴 TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN (NEU 2026-09-21, Gitea #71):** der aktuell
|
||||
**dringendste** Posten — die internen `*.bf.emsenhuber.net`-Namen lösen im Browser nicht auf
|
||||
(Ursache: AGH-Rewrites im Filter-Pfad für unfiltrierte Clients übersprungen), und `ds-bf` ist
|
||||
öffentlich kaputt (einziger Ingress-Eintrag, der Caddy umgeht). Danach ist **TP34** erst baubar.
|
||||
öffentlich kaputt (einziger Ingress-Eintrag, der Caddy umgeht). Danach ist **TP34.2** erst baubar.
|
||||
- **TP46 — CF-/Tunnel-SSoT + Recovery (NEU 2026-09-21, Gitea #72):** Push-Weg + Ingress-Origins +
|
||||
DNS + Access-Policies nach Nautobot, Recovery-Runbook. Eigene Baustelle, unabhängig von TP45.
|
||||
- **TP25 — WLAN-Stabilisierung (NEU 2026-08-17):** Audit durch, **6 Funde** (2,4 GHz fährt 40 MHz statt der
|
||||
|
||||
@@ -64,7 +64,12 @@ lokal" — das ist nach dieser Rotation nicht mehr wahr.
|
||||
|
||||
## 4. Fußnote mit Verfallsdatum
|
||||
|
||||
⏰ **Der `CF_API_TOKEN` selbst läuft am 2027-01-09 ab** (Memory `ref_cf_mcp_readonly_token_writes`).
|
||||
⏰ ~~Der `CF_API_TOKEN` selbst läuft am 2027-01-09 ab~~ — **korrigiert 2026-09-21:** das Datum
|
||||
gehörte zum längst **revokten** Alt-Token `9984078812bc…`; das **live** `CF_API_TOKEN`
|
||||
(id `493ed8bd…`) hat **kein** Ablaufdatum (verifiziert via `/accounts/{id}/tokens/verify`,
|
||||
`expires_on: null`). Die Rotation ist damit nicht mehr termin-, sondern **anlassgetrieben**:
|
||||
**Anlass** ist der **Leak vom 2026-09-21**. Sie bleibt **gebündelt** in TP30 (Posten #4) und wird
|
||||
nicht vorgezogen. Memory `ref_cf_mcp_readonly_token_writes` ist nachgezogen.
|
||||
Er ist **account-owned**, verifiziert also über einen anderen Endpoint als ein User-Token — wer das
|
||||
verwechselt, hält ein funktionierendes Token für kaputt. Ohne ihn ist auch diese Rotation nicht mehr
|
||||
möglich, ebensowenig der TP16.3-Schnitt.
|
||||
|
||||
@@ -84,7 +84,7 @@ dns:
|
||||
ipset_file: ""
|
||||
bootstrap_prefer_ipv6: false
|
||||
upstream_timeout: 10s
|
||||
private_<redacted>: []
|
||||
private_networks: []
|
||||
use_private_ptr_resolvers: false
|
||||
local_ptr_upstreams: []
|
||||
use_dns64: false
|
||||
|
||||
@@ -618,7 +618,7 @@ def _tp45_leases_module():
|
||||
return mod
|
||||
|
||||
|
||||
def _lease_move(mac, soll):
|
||||
def _lease_move(mac, soll, tag="tp45-v30"):
|
||||
"""Eine Umzugs-Zeile — EINZEILIG und klammer-vollstaendig (E1015-Lehre, s. _pool_set).
|
||||
|
||||
Der Selektor wird GEZAEHLT: ein `find where`, das 0 trifft, sieht wie
|
||||
@@ -653,8 +653,8 @@ def _lease_move(mac, soll):
|
||||
arg = f'[find where mac-address={mac}]'
|
||||
return (f':if ([:len [/ip dhcp-server lease {find}]] = 1) do={{ '
|
||||
f'/ip dhcp-server lease set {arg} address={soll}; '
|
||||
f':log info ("tp45-v30: {mac} -> {soll}") }} '
|
||||
f'else={{ :log info ("tp45-v30: {mac} NICHT EINDEUTIG -- nicht angefasst") }}')
|
||||
f':log info ("{tag}: {mac} -> {soll}") }} '
|
||||
f'else={{ :log info ("{tag}: {mac} NICHT EINDEUTIG -- nicht angefasst") }}')
|
||||
|
||||
|
||||
def job_tp45_v30_leases():
|
||||
@@ -723,6 +723,209 @@ def job_tp45_v30_leases():
|
||||
return [(["crs326-core"], "\n".join(lines), "TP45-v30-leases")]
|
||||
|
||||
|
||||
def job_tp45_v30_revert():
|
||||
"""RUECKNAHME 2026-09-21: die 26 Shelly-Reservierungen zurueck auf ihre ALTEN Adressen.
|
||||
|
||||
Christoph-Direktive, sofort: *„mach die shellys sofort rueckgaengig! das gibt
|
||||
sonst ein riesen durcheinander mit home assistant"*. Grund: HA hat die Shellys
|
||||
mit ihren alten Adressen eingebunden -- ein Umzug zerreisst die Integrationen.
|
||||
|
||||
🔴 **NUR die Shellys** — das war die erste, eng gefasste Ruecknahme. Die
|
||||
uebrigen 10 Umzuege aus `job_tp45_v30_leases` (NukiBridge .11, P750W .13,
|
||||
DTUBI .14, NukiSmartlock .177, SwitchBot .178, Twinkly .179, lichterketten
|
||||
.190/.191, Withings .195/.196) und die beiden Sonderfaelle (CCU-Leiche
|
||||
entfernt, NSPanelPro statisch auf .12) liess er unangetastet.
|
||||
|
||||
⚠️ **Noch am selben Tag ueberholt:** auf die weitergehende Direktive
|
||||
(„wir sollten ALLE vlan30-aenderungen rueckgaengig machen" — Sperr-Risiko
|
||||
Nuki) folgte `job_tp45_v30_restore`, der auch diese 10 plus beide
|
||||
Sonderfaelle zurueckdreht. Dieser Job bleibt als der engere, zuerst
|
||||
gelaufene Schritt bestehen.
|
||||
|
||||
Die Tabelle steht in `tp45-leases.py` als `REVERT` (abgeleitet aus `MOVES`,
|
||||
gefiltert auf die 26 Shelly-MACs) -- NICHT hier, aus demselben Grund wie
|
||||
`MOVES`: eine zweite Kopie laeuft still auseinander.
|
||||
|
||||
⚠️ **Reihenfolge:** erst `python3 nautobot/tp45-leases.py revert --apply`
|
||||
(Soll zurueck), DANN diesen Job. Umgekehrt meldet GC sofort wieder Drift --
|
||||
das Geraet waere richtig und das Soll falsch.
|
||||
|
||||
Abnahme: Geraete-Read `proplist=address,comment where server=dhcp-v30` -- die
|
||||
26 Shellys muessen auf .202-.226/.233 stehen, die uebrigen 13 Eintraege
|
||||
unveraendert. Danach `--refresh-compliance`, erwartet 145/145 und 0 Drifts.
|
||||
"""
|
||||
mod = _tp45_leases_module()
|
||||
lines = [_lease_move(mac, alt, tag="tp45-v30-revert")
|
||||
for mac, (_neu, alt) in sorted(mod.REVERT.items())]
|
||||
|
||||
# Abnahme-Zeile: die alten Baender muessen wieder voll sein, die neuen leer.
|
||||
# Bewusst ein Bereichs-Paar statt aller 26 Adressen -- .202/.233 rahmen den
|
||||
# alten Shelly-Block ein, .151/.176 den neuen.
|
||||
lines.append(
|
||||
':log info ("tp45-v30-revert: .151=" . [:tostr [:len [/ip dhcp-server lease '
|
||||
'find where address=10.40.30.151]]] . " .176=" . [:tostr [:len '
|
||||
'[/ip dhcp-server lease find where address=10.40.30.176]]] . " .202=" . '
|
||||
'[:tostr [:len [/ip dhcp-server lease find where address=10.40.30.202]]] . '
|
||||
' " .233=" . [:tostr [:len [/ip dhcp-server lease '
|
||||
'find where address=10.40.30.233]]])')
|
||||
|
||||
return [(["crs326-core"], "\n".join(lines), "TP45-v30-shelly-revert")]
|
||||
|
||||
|
||||
def job_tp45_v30_restore():
|
||||
"""VOLLRUECKNAHME 2026-09-21: alle VLAN30-Aenderungen aus B2 zuruecknehmen.
|
||||
|
||||
Christoph-Direktive: *„wir sollten alle vlan30-aenderungen rueckgaengig machen.
|
||||
du koenntest mich auf diesem wege aus meiner wohnung aussperren."*
|
||||
|
||||
Was B2 am 2026-09-21 getan hat, und was hier einzeln zurueckgedreht wird:
|
||||
|
||||
1. **36 Reservierungs-Adressen** verschoben -> hier ALLE 36 zurueck auf ihre
|
||||
`MOVES`-Ist-Adresse (idempotent: die 26 Shellys stehen schon dort, ein
|
||||
zweites `set` auf dieselbe Adresse ist ein No-Op).
|
||||
2. **CCU-Leiche entfernt** (`BC:24:11:8E:0A:57`, .30.232) -> wieder angelegt,
|
||||
wortgleich der Stand vor B2. Ob die Leiche spaeter geloescht werden soll,
|
||||
ist eine **eigene** Entscheidung, nicht Teil dieser Ruecknahme.
|
||||
3. **SONOFF-NSPanelPro auf statisch .12 gepinnt** -> Reservierung entfernt;
|
||||
vor B2 hatte es keine (reiner DHCP-Client auf .249). `remove` ist der
|
||||
Weg, weil RouterOS 7.24 kein `make-dynamic` kennt (s. u.).
|
||||
|
||||
⚠️ **Reihenfolge:** erst `python3 nautobot/tp45-leases.py restore --apply`
|
||||
(Soll zurueck), DANN diesen Job.
|
||||
|
||||
🔴 **Warum die Eile berechtigt war:** die Reservierung eines Geraets mit
|
||||
**lokaler** IP zu aendern, bewegt das Geraet nicht — aber bei einem Geraet,
|
||||
das **DHCP** nutzt, haette die naechste Erneuerung es auf die neue Adresse
|
||||
gezogen, und IP-gebundene Einbindungen (Home Assistant: Nuki, Shelly, …)
|
||||
waeren gerissen. Dass hier nichts passiert ist, war Glueck, kein Entwurf.
|
||||
|
||||
Abnahme: Geraete-Read der v30-Leases + ARP-Band-Zaehlung; danach
|
||||
`--refresh-compliance`, erwartet 145/145 und 0 Drifts.
|
||||
"""
|
||||
mod = _tp45_leases_module()
|
||||
lines = [_lease_move(mac, ist, tag="tp45-v30-restore")
|
||||
for mac, (ist, _soll) in sorted(mod.MOVES.items())]
|
||||
|
||||
# NSPanelPro: die statische Reservierung ENTFERNEN -- vor B2 hatte es keine,
|
||||
# es war ein reiner DHCP-Client (dynamische Lease auf .249).
|
||||
#
|
||||
# 🔴 `make-dynamic` gibt es in RouterOS 7.24 NICHT: `/ip dhcp-server lease
|
||||
# make-dynamic` antwortet `bad command name make-dynamic` (live gemessen
|
||||
# 2026-09-21). Der erste Versuch dieses Jobs ist daran gestorben und hat den
|
||||
# Plan auf `Failed` gesetzt, OBWOHL alle uebrigen Statements gelandet sind —
|
||||
# genau die Klasse „Completed/Failed beweist nichts, das Geraet ist die
|
||||
# Wahrheit". `make-static` existiert (B2 hat es benutzt), das Gegenstueck
|
||||
# fehlt; der Rueckweg ist `remove`.
|
||||
#
|
||||
# ⚠️ Das Panel haelt .12 noch bis zum Lease-Ablauf (~20 h, `status=bound`,
|
||||
# `active-address=10.40.30.12`) und faellt danach in den Pool zurueck.
|
||||
# Das ist eine von B2 erzeugte Uebergangserscheinung, keine neue Aenderung.
|
||||
for add in mod.ADDS:
|
||||
mac = re.search(r"mac-address=(\S+)", add).group(1)
|
||||
find = f'find where mac-address={mac}'
|
||||
arg = f'[find where mac-address={mac}]'
|
||||
lines.append(f':if ([:len [/ip dhcp-server lease {find}]] = 1) do={{ '
|
||||
f'/ip dhcp-server lease remove {arg}; '
|
||||
f':log info ("tp45-v30-restore: NSPanelPro-Reservierung entfernt") }} '
|
||||
f'else={{ :log info ("tp45-v30-restore: NSPanelPro hat keine Reservierung") }}')
|
||||
|
||||
# CCU-Leiche wieder anlegen -- nur wenn sie fehlt.
|
||||
for mac in sorted(mod.REMOVE):
|
||||
find = f'find where mac-address={mac}'
|
||||
lines.append(f':if ([:len [/ip dhcp-server lease {find}]] = 0) do={{ '
|
||||
f'/ip dhcp-server lease {mod.RESTORE_ADD}; '
|
||||
f':log info ("tp45-v30-restore: CCU-Leiche wieder angelegt") }} '
|
||||
f'else={{ :log info ("tp45-v30-restore: CCU-Leiche ist schon da") }}')
|
||||
|
||||
# Abnahme: die alten Baender muessen voll sein, die neuen leer.
|
||||
lines.append(
|
||||
':log info ("tp45-v30-restore: alt .202=" . [:tostr [:len [/ip dhcp-server lease '
|
||||
'find where address=10.40.30.202]]] . " .233=" . [:tostr [:len '
|
||||
'[/ip dhcp-server lease find where address=10.40.30.233]]] . " .244=" . '
|
||||
'[:tostr [:len [/ip dhcp-server lease find where address=10.40.30.244]]] . '
|
||||
' " neu .11=" . [:tostr [:len [/ip dhcp-server lease '
|
||||
'find where address=10.40.30.11]]] . " .151=" . [:tostr [:len '
|
||||
'[/ip dhcp-server lease find where address=10.40.30.151]]] . " .177=" . '
|
||||
'[:tostr [:len [/ip dhcp-server lease find where address=10.40.30.177]]])')
|
||||
|
||||
return [(["crs326-core"], "\n".join(lines), "TP45-v30-restore")]
|
||||
|
||||
|
||||
def _dns_static_lines():
|
||||
"""Die `/ip dns static add`-Zeilen aus dem GC-Template, wortgetreu.
|
||||
|
||||
🔴 **Quelle ist das Template selbst, NICHT eine zweite Kopie.** Eine handgepflegte
|
||||
Liste hier liefe beim ersten Template-Edit auseinander — still, weil beide Seiten
|
||||
fuer sich plausibel aussehen; am Geraet faellt nur auf, dass ein Name fehlt.
|
||||
Und die Kommentare muessen **zeichengleich** sein: Golden Config vergleicht
|
||||
`comment=` mit, ein abweichender Text ist eine Drift.
|
||||
|
||||
Der `/ip dns static`-Block ist ein Literal im Template (im Gegensatz zu
|
||||
`firewall_filter` und `dhcp_leases`, die als Daten aus
|
||||
`local_config_context_data` kommen) — deshalb ist das Parsen hier zulaessig.
|
||||
"""
|
||||
text = (pathlib.Path(__file__).with_name("mikrotik_routeros.j2")).read_text()
|
||||
return [ln.strip() for ln in text.splitlines()
|
||||
if ln.strip().startswith("/ip dns static add ")]
|
||||
|
||||
|
||||
def _dns_upsert(line):
|
||||
"""Eine Template-Zeile -> idempotenter Upsert, EINZEILIG und klammer-vollstaendig.
|
||||
|
||||
`/ip dns static add` ist NICHT idempotent — ein zweiter Lauf legte den Namen ein
|
||||
zweites Mal an. Deshalb: nur anlegen, wenn der Name fehlt, sonst in place setzen.
|
||||
Der Selektor wird wie in `_lease_move` gezaehlt (`find where …` in `[:len …]`,
|
||||
`[find where …]` als Argument) — die vertauschte Form ist ein Syntaxfehler, der
|
||||
als `Completed` durchlaeuft und das Geraet unveraendert laesst.
|
||||
"""
|
||||
addr = re.search(r"address=(\S+)", line).group(1)
|
||||
name = re.search(r"name=(\S+)", line).group(1)
|
||||
cm = re.search(r'comment=("(?:[^"\\]|\\.)*"|\S+)', line)
|
||||
comment = f" comment={cm.group(1)}" if cm else ""
|
||||
return (f':if ([:len [/ip dns static find where name={name}]] = 0) do={{ '
|
||||
f'{line} }} '
|
||||
f'else={{ /ip dns static set [find where name={name}] address={addr}{comment} }}')
|
||||
|
||||
|
||||
def job_tp45_c_dns():
|
||||
"""TP45 Schicht C (2026-09-22): die internen Namen in den CORE — der FQDN-Fix.
|
||||
|
||||
Hintergrund: AGH wertet seine DNS-Rewrites nur im **Filter-Pfad** aus. Fuer
|
||||
unfiltrierte Clients (`dns-bypass-exempt` = beide Mac-NICs, `grp-infra`,
|
||||
`grp-media`, `grp-mgmt`) greifen sie nicht, und die fallen auf den conditional
|
||||
forward `[/bf.emsenhuber.net/] -> 10.40.20.1` zurueck — den Core. Der hielt
|
||||
aber nur 10 Statics; 12 Namen existierten nur als AGH-Rewrite und waren damit
|
||||
**NXDOMAIN**. Vom Client gemessen 2026-09-22: alle 12 leer, `vw.emsenhuber.net`
|
||||
lieferte die Cloudflare-Anycast-IP statt `.40` — und genau daher kam der
|
||||
404 auf `/admin` (der CF-Weg setzt `CF-Connecting-IP`, der `@cf_admin`-Guard
|
||||
antwortet darauf mit 404).
|
||||
|
||||
Dieser Job bringt die Statics auf das Geraet. Danach beantwortet der Core alle
|
||||
internen Namen unabhaengig vom Filterstatus des Clients — und, weil die Namen
|
||||
ueber Caddy laufen, mit gueltigem TLS.
|
||||
|
||||
⚠️ **Die 5 bare Namen (`vw`, `hass`, `n8n`, `gitea`, `ds-bf`) kommen bewusst mit
|
||||
auf `.40`** (Christoph-Entscheid 2026-09-22): der lokale, header-freie Weg ist
|
||||
die einzige Tuer zum Vault-Admin. ⚠️ Nebenwirkung: diese Namen werden damit fuer
|
||||
**alle** VLANs aufloesbar, nicht nur fuer MAIN — MEDIA (`*A`) und GUEST
|
||||
(`*B`+`*A`) erreichen `.40:443` ebenfalls. Einhegung ist ein eigener Schritt.
|
||||
|
||||
Rückweg: die Vorher-Werte stehen als `A1`/`A2`-Tabellen im Plan
|
||||
(`~/.claude/plans/…`); vor dem Deploy liegt ein Backup auf dem Core.
|
||||
"""
|
||||
lines = [_dns_upsert(ln) for ln in _dns_static_lines()]
|
||||
# Abnahme-Zeile: die Zahl der Statics plus drei Stichproben, die den Ausfall
|
||||
# repraesentieren (ap3 gab es vorher nirgends, pve zeigt auf Caddy, vw muss
|
||||
# `.40` sein — nicht die CF-Anycast-IP).
|
||||
lines.append(
|
||||
':do { :log info ("tp45-c-dns: statics=" . [:tostr [:len [/ip dns static find]]] '
|
||||
'. " ap3=" . [:tostr [:len [/ip dns static find where name=ap3.bf.emsenhuber.net]]] '
|
||||
'. " vw=" . [/ip dns static get [find where name=vw.emsenhuber.net] address] '
|
||||
'. " pve=" . [/ip dns static get [find where name=pve.bf.emsenhuber.net] address]) } '
|
||||
f'on-error={{ :log info "tp45-c-dns: Abnahme-Zeile geworfen" }}')
|
||||
return [(["crs326-core"], "\n".join(lines), "TP45-c-dns")]
|
||||
|
||||
|
||||
JOBS = {
|
||||
"drift-cleanup": job_drift_cleanup,
|
||||
"mgmt-acl": job_mgmt_acl,
|
||||
@@ -730,8 +933,11 @@ JOBS = {
|
||||
"tp38-oob": job_tp38_oob,
|
||||
"tp45-agh-fw": job_tp45_agh_fw,
|
||||
"tp45-agh-dhcp": job_tp45_agh_dhcp,
|
||||
"tp45-c-dns": job_tp45_c_dns,
|
||||
"tp45-pool-v20": job_tp45_pool_v20,
|
||||
"tp45-v30-leases": job_tp45_v30_leases,
|
||||
"tp45-v30-revert": job_tp45_v30_revert,
|
||||
"tp45-v30-restore": job_tp45_v30_restore,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -312,18 +312,49 @@
|
||||
# FT-Transition statt Voll-Reassoziation. Das Band-Springen selbst endet erst
|
||||
# mit dem Deckungsfix (TP25 Variante 3). Siehe teilprojekte.md TP25 (Stand 09-14).
|
||||
|
||||
# Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich):
|
||||
# core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12
|
||||
# Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich; ap3 ergaenzt 2026-09-22):
|
||||
# core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12 | ap3 .99.13
|
||||
#
|
||||
# 🔴 TP45 Schicht C (2026-09-22): DIESE Liste ist die EINZIGE Quelle fuer interne Namen.
|
||||
# AGH wertet seine DNS-Rewrites nur im Filter-Pfad aus; fuer unfiltrierte Clients
|
||||
# (dns-bypass-exempt, grp-infra/-media/-mgmt) greifen sie nicht — die landen hier.
|
||||
# Hier fehlten 12 Namen, genau das war der FQDN-Ausfall.
|
||||
# Merkregel: was in AGH als Rewrite steht, muss auch hier stehen.
|
||||
/ip dns static add address=10.40.99.1 comment="crs326-core (L3)" name=core.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.99.2 comment=crs326-branch name=branch.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.99.11 comment="ap1 = reiner AP" name=ap1.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.99.12 comment="ap2 = reiner AP" name=ap2.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.99.13 comment="ap3 = reiner CAP (cAP ax)" name=ap3.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.167 name=macmini.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.100 name=pve.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.173 name=pve-mcp.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.10 name=nas.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.126 name=docker.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.20 name=platform-pg.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.10.6 comment="Brother MFC (VLAN10)" name=mfc.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.40.11 comment="TV (VLAN40)" name=tv.bf.emsenhuber.net type=A
|
||||
|
||||
# Caddy-Frontends (TP31): Dienste mit eigener Site laufen ueber den Reverse Proxy, damit das
|
||||
# TLS-Zertifikat gueltig ist. Quelle: caddy/Caddyfile (live geprueft 2026-09-22).
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> nautobot :8080" name=nautobot.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> pbs :8007" name=pbs.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> portainer :9443" name=portainer.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> ccu :80" name=ccu.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> iobroker :8081" name=iobroker.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> z2m25 :8080" name=z2m25.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> home-assistant mcp :9583" name=ha-mcp.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> n8n-mcp :3001" name=n8n-mcp.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> gitea-mcp :3000" name=gitea-mcp.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> pve :8006" name=pve.bf.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="caddy -> pve-mcp :3000" name=pve-mcp.bf.emsenhuber.net type=A
|
||||
|
||||
# Oeffentliche Namen (bare emsenhuber.net) — real via CF Access + Tunnel. Sie stehen hier NUR,
|
||||
# damit LAN-Clients direkt auf Caddy kommen: der CF-Weg setzt CF-Connecting-IP, und der
|
||||
# @cf_admin-Guard in caddy/Caddyfile antwortet darauf mit 404 (TP41 T2). Der lokale,
|
||||
# header-freie Weg ist die einzige Tuer zum Vault-Admin (Christoph-Entscheid 2026-09-22).
|
||||
/ip dns static add address=10.40.20.40 comment="vw via caddy (Vault-Admin nur lokal)" name=vw.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="hass via caddy" name=hass.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="n8n via caddy" name=n8n.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="gitea via caddy" name=gitea.emsenhuber.net type=A
|
||||
/ip dns static add address=10.40.20.40 comment="ds-bf via caddy" name=ds-bf.emsenhuber.net type=A
|
||||
|
||||
# [7] NAT
|
||||
/ip firewall nat add action=accept chain=srcnat comment="TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" dst-address=10.40.0.0/16
|
||||
|
||||
@@ -49,6 +49,10 @@ Drift mehr (genau der Grund, warum `ingest-core-firewall.py` manuell bleibt).
|
||||
python3 nautobot/tp45-leases.py seed --force --apply # Neuschnitt, ueberschreibt
|
||||
python3 nautobot/tp45-leases.py move # Dry-Run, s. MOVES
|
||||
python3 nautobot/tp45-leases.py move --apply # B2
|
||||
python3 nautobot/tp45-leases.py revert # Dry-Run, s. REVERT (nur Shellys)
|
||||
python3 nautobot/tp45-leases.py revert --apply # Ruecknahme 2026-09-21
|
||||
python3 nautobot/tp45-leases.py restore # Dry-Run: ALLES zurueck auf vor B2
|
||||
python3 nautobot/tp45-leases.py restore --apply # Vollruecknahme 2026-09-21
|
||||
|
||||
Token: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored), NAUTOBOT_SUPERUSER_API_TOKEN.
|
||||
Der Wert wird nie ausgegeben.
|
||||
@@ -116,12 +120,49 @@ MOVES = {
|
||||
# .150 (Ender3V3KE) und .180 (espressif/Airfryer) bleiben, wo sie sind.
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RUECKNAHME 2026-09-21 (Christoph-Direktive, sofort): NUR die Shellys.
|
||||
#
|
||||
# Die 26 Shelly-Reservierungen gehen auf ihre ALTEN Adressen zurueck. Grund:
|
||||
# Home Assistant hat sie mit den alten Adressen eingebunden -- der Umzug haette
|
||||
# die HA-Integrationen zerrissen. Die uebrigen 10 Umzuege (NukiBridge, P750W,
|
||||
# DTUBI, NukiSmartlock, SwitchBot, Twinkly, Lichterketten, Withings) BLEIBEN.
|
||||
#
|
||||
# Bewusst als eigene Menge und nicht als Bereichs-Ableitung: welche MAC eine
|
||||
# Shelly ist, steht nur im Kommentar der Soll-Zeile, nicht im MOVES-Key.
|
||||
# ---------------------------------------------------------------------------
|
||||
SHELLY = {
|
||||
"E4:B0:63:D7:C7:A4", "28:37:2F:36:F4:7C", "E4:B0:63:E4:EF:7C",
|
||||
"E4:B0:63:E4:0F:30", "D8:85:AC:0C:5C:C8", "D0:CF:13:D8:18:FC",
|
||||
"D0:CF:13:D9:94:E8", "E4:B0:63:FA:FD:E0", "E8:F6:0A:61:88:08",
|
||||
"E4:B0:63:FC:57:E4", "E4:B0:63:FA:6C:00", "48:F6:EE:C8:E6:AC",
|
||||
"48:F6:EE:DD:6A:F4", "E4:B0:63:FC:59:48", "E4:B0:63:FA:D5:34",
|
||||
"10:20:BA:FA:F5:40", "10:20:BA:FB:6D:64", "48:F6:EE:CC:16:E4",
|
||||
"10:20:BA:FB:70:FC", "98:A3:16:7C:5F:EC", "20:6E:F1:01:F9:F8",
|
||||
"A0:85:E3:C8:65:34", "AC:15:18:7B:62:20", "14:33:5C:D8:2D:7C",
|
||||
"98:A3:16:79:38:50", "98:A3:16:7A:A3:54",
|
||||
}
|
||||
if len(SHELLY) != 26:
|
||||
raise SystemExit("SHELLY hat %d MACs statt 26 -- Tabelle beschaedigt." % len(SHELLY))
|
||||
# Schluessel = MAC, Wert = (aktuelle Soll-Adresse, Adresse davor) -- also die
|
||||
# Umkehrung von MOVES, aber nur fuer den Shelly-Block.
|
||||
REVERT = {mac: (soll, ist) for mac, (ist, soll) in MOVES.items() if mac in SHELLY}
|
||||
if len(REVERT) != 26:
|
||||
raise SystemExit("REVERT hat %d Eintraege statt 26 -- MOVES/SHELLY passen nicht zusammen."
|
||||
% len(REVERT))
|
||||
|
||||
# CCU-Leiche: VLAN30-Reservierung `pve-homematic-raspi-ccu`, status=waiting,
|
||||
# last-seen=never. Die CCU ist live in VLAN20 (10.40.20.232, ARP+HTTP belegt,
|
||||
# Caddys ccu.bf proxyt dorthin) — dieser Eintrag ist ein Zombie aus dem
|
||||
# Core-Cutover. Er wird ENTFERNT, nicht umgezogen.
|
||||
REMOVE = {"BC:24:11:8E:0A:57"}
|
||||
|
||||
# Die Zeile, mit der B2 die CCU-Leiche entfernt hat -- wortgleich der Zustand
|
||||
# VOR B2, Quelle `routeros/tp21-core-dhcp-from-ap1.rsc` Z. 70 (dort ohne
|
||||
# `disabled`). Fuer den vollstaendigen Rueckweg wird sie wieder angelegt.
|
||||
RESTORE_ADD = ("add address=10.40.30.232 comment=pve-homematic-raspi-ccu "
|
||||
"mac-address=BC:24:11:8E:0A:57 server=dhcp-v30")
|
||||
|
||||
# SONOFF-NSPanelPro hat KEINE Reservierung, nur eine dynamische Lease auf .249.
|
||||
# Wird in B2 per make-static + set address gepinnt. Zeile hier nur als Soll.
|
||||
ADDS = [
|
||||
@@ -207,7 +248,7 @@ def show(old, new):
|
||||
|
||||
def main():
|
||||
argv = sys.argv[1:]
|
||||
if not argv or argv[0] not in ("seed", "move", "check"):
|
||||
if not argv or argv[0] not in ("seed", "move", "revert", "restore", "check"):
|
||||
raise SystemExit(__doc__.strip().splitlines()[-2].strip())
|
||||
cmd, apply_ = argv[0], "--apply" in argv
|
||||
dev = device()
|
||||
@@ -232,6 +273,57 @@ def main():
|
||||
new, when = backup_lines()
|
||||
print("== seed aus Backup %s (%d Zeilen)%s =="
|
||||
% (when, len(new), " [--force, ueberschreibt %d Zeilen]" % len(soll) if soll else ""))
|
||||
elif cmd == "restore":
|
||||
if not soll:
|
||||
raise SystemExit("Soll ist leer — nichts wiederherzustellen.")
|
||||
drop = {mac_of(a) for a in ADDS}
|
||||
new, seen, moved, dropped = [], set(), [], []
|
||||
for line in soll:
|
||||
mac = mac_of(line)
|
||||
if mac in drop:
|
||||
dropped.append(mac)
|
||||
continue
|
||||
if mac in MOVES:
|
||||
ist_ip, soll_ip = MOVES[mac]
|
||||
cur = re.search(r"address=(\S+)", line).group(1)
|
||||
if cur != ist_ip:
|
||||
if cur != soll_ip:
|
||||
raise SystemExit("%s: Soll traegt weder %s noch %s — Soll, MOVES und "
|
||||
"Wirklichkeit auseinander. Zeile: %s"
|
||||
% (mac, ist_ip, soll_ip, line[:120]))
|
||||
line = set_address(line, ist_ip)
|
||||
moved.append(mac)
|
||||
seen.add(mac)
|
||||
new.append(line)
|
||||
fehlend = set(MOVES) - seen
|
||||
if fehlend:
|
||||
raise SystemExit("MOVES nennt MACs, die im Soll fehlen: %s"
|
||||
% ", ".join(sorted(fehlend)))
|
||||
for mac in sorted(REMOVE):
|
||||
if mac not in seen:
|
||||
new.append(RESTORE_ADD)
|
||||
moved.append(mac)
|
||||
print("== restore: %d Adressen zurueck, %d Zeilen entfernt, %d wieder angelegt "
|
||||
"(Soll jetzt %d Zeilen) ==" % (len(moved), len(dropped), len(REMOVE), len(new)))
|
||||
elif cmd == "revert":
|
||||
if not soll:
|
||||
raise SystemExit("Soll ist leer — es gibt nichts zurueckzunehmen.")
|
||||
new, seen = [], set()
|
||||
for line in soll:
|
||||
mac = mac_of(line)
|
||||
if mac in REVERT:
|
||||
neu_ip, alt_ip = REVERT[mac]
|
||||
if re.search(r"address=(\S+)", line).group(1) != neu_ip:
|
||||
raise SystemExit("%s: Soll traegt nicht %s — Soll und REVERT auseinander. "
|
||||
"Zeile: %s" % (mac, neu_ip, line[:120]))
|
||||
line = set_address(line, alt_ip)
|
||||
seen.add(mac)
|
||||
new.append(line)
|
||||
if seen != set(REVERT):
|
||||
raise SystemExit("REVERT nennt MACs, die im Soll fehlen: %s"
|
||||
% ", ".join(sorted(set(REVERT) - seen)))
|
||||
print("== revert: %d Shelly-Reservierungen zurueck auf die alten Adressen =="
|
||||
% len(REVERT))
|
||||
else:
|
||||
if not soll:
|
||||
raise SystemExit("Soll ist leer — erst `seed` laufen lassen (Schicht B1).")
|
||||
|
||||
Reference in New Issue
Block a user