From 08a1aaa80d18d9b0b957bc71d7055934c410488c Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Sat, 3 Oct 2026 13:12:26 +0200 Subject: [PATCH] SSoT: Status-Marker aus den TP-Headern entfernt, Sync-Werkzeug auf Anlege-Richtung festgelegt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die `### TPnn`-Header trugen weiterhin Status-Anspruche (▸ ✅ FERTIG …, ▸ ⏳ CUTOVER …), obwohl Index/Status seit dem TP36-Cutover in Gitea liegt. Das war eine dritte Statusquelle, die den Labels nachlief — Header-Marker datierten teils Wochen vor dem Issue-Update. - 75 Header (32 aktiv + 43 Archiv) auf `### TPnn · Gitea #N — — ` umgestellt. Caveats, Warnungen, Querverweise und Archiv-Links bleiben erhalten, nur der Status faellt weg. Status-Marker im Fliesstext (Messchronik, Unterpunkte) bleiben — sie beschreiben Historie. - TP47 fehlte als Gitea-Issue komplett (nur in der Datei seit 808d3a1) -> als #74 angelegt. - bin/gitea-tp-sync.py schrieb in die falsche Richtung: der Abgleich leitete den Zielzustand aus dem Datei-Marker ab (fest ✅) und haette #24 (TP34, bewusst status:dropped) auf status:done gezogen. Jetzt werden nur noch FEHLENDE Archiv-Issues angelegt, bestehende nie angefasst. - CLAUDE.md: Header-Form + Werkzeug-Richtung festgehalten. - Announcement 2026-10-03 für die naechste Session. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 12 +- bin/gitea-tp-sync.py | 113 ++++++++++--------- docs/announcements.md | 18 +++ docs/handoff-20260928-core-wghub-ping-fix.md | 52 +++++++++ docs/teilprojekte-archiv.md | 86 +++++++------- docs/teilprojekte.md | 64 +++++------ 6 files changed, 215 insertions(+), 130 deletions(-) create mode 100644 docs/handoff-20260928-core-wghub-ping-fix.md diff --git a/CLAUDE.md b/CLAUDE.md index fe8ac90..4e2148c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,18 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17), Firewa - **SSoT-Dreiteilung:** **Index/Status = Gitea-Issues** (`admin/bf-infra`, Labels `status:*`/`thema:*`) · **Prosa/Detail = Repo** (`docs/teilprojekte.md` aktiv + `teilprojekte-archiv.md`, `### TPnn`-Abschnitte) · **Live-Gerätedaten = Nautobot** (Compliance-Zahl, RouterOS-Version …). Ein Status ändert sich **in Gitea**, - nicht mehr in einer Repo-Datei. Sync-/Abgleich-Werkzeug fürs Archiv: `bin/gitea-tp-sync.py` (Dry-Run default, + nicht mehr in einer Repo-Datei. Archiv-Backfill-Werkzeug `bin/gitea-tp-sync.py` (Dry-Run default, Token aus `~/.hermes/.env`, **nie ausgeben**). +- 🔴 **Die TP-Header in beiden Dateien tragen KEINEN Status mehr** (seit 2026-10-03). Form: + `### TP25 · Gitea #17 — — `. Der `· Gitea #N`-Zeiger ist die Brücke zum Issue. + Wer einen Status sucht, liest das **Label im Board** — nicht den Datei-Kopf. Der alte `▸ ✅ FERTIG …`-Anspruch + war eine dritte Statusquelle und ist entfernt; die Header-Marker waren teils älter als die Issues. + ⚠️ **Status-Marker im Fließtext** (in `└─`-Zeilen, Unterpunkten, Messchronik) bleiben erlaubt — sie + beschreiben historische Ereignisse, keinen aktuellen Stand. +- 🔴 **`bin/gitea-tp-sync.py` schreibt nur noch NEUE Issues** für Archiv-TPs, die im Board fehlen. + Existierende Issues werden **nie** angefasst — kein Label-, kein State-Patch. Die Vorgängerversion + glich bestehende Issues gegen den Datei-Marker ab und zog so #24 (`status:dropped` → `done`) und + #32 auf falsche Labels. **Richtung Datei → Gitea ist nur fürs *Anlegen* zulässig, nie für *Status*.** - **Branch-Konvention:** Branch `tpNN-kurz` **jederzeit** mitten in der Session möglich (`git switch -c`, uncommittete Arbeit wandert mit); für Multi-Session-TPs der Normalfall. **Merge nach `main`, sobald der Code/Config des TP steht.** Weil Steuerungs-Updates nach **Gitea** gehen (nicht in `teilprojekte.md`), diff --git a/bin/gitea-tp-sync.py b/bin/gitea-tp-sync.py index 4623715..99de9ee 100644 --- a/bin/gitea-tp-sync.py +++ b/bin/gitea-tp-sync.py @@ -1,22 +1,28 @@ #!/usr/bin/env python3 """ -TP36 (reframed) — teilprojekte.md Index/Status → Gitea Issues (admin/bf-infra). +TP36 (reframed) — Archiv-Backfill: fehlende Archiv-TPs als Gitea-Issues anlegen. -Idempotenter Sync (get-or-create, Match ueber "TPnn" im Titel): - - Archiv-Header in docs/teilprojekte-archiv.md -> closed Issues (nur TPs, die in Gitea - NICHT offen sind; ein reopentes TP mit Archiv-Zeiger bleibt offen). - - Labels status:* (Marker) + thema:* (Gruppierung) werden vorab get-or-create. +🔴 RICHTUNG (seit 2026-10-03): **Gitea ist die SSoT fuer Index/Status.** Dieses Werkzeug +liest die Datei NUR, um zu pruefen, ob jedes archivierte TP ein Issue hat. Es schreibt +**ausschliesslich** NEUE Issues fuer noch nicht existierende Archiv-TPs — existierende +Issues werden NIE angefasst (kein Label-, kein State-Patch). + +Warum das wichtig ist: die Vorgaenger-Version glich auch bestehende Issues ab und leitete +den Zielzustand aus dem Datei-Marker ab (fest `✅`). Damit konnte sie ein bewusst gesetztes +Label ueberschreiben — so wurde #24 (TP34, `status:dropped`) auf `status:done` und #32 +(TP43) auf ein falsches `thema:` gezogen. Die Richtung Datei → Gitea fuer *Status* ist +seit dem TP36-Cutover (2026-09-07) falsch; sie bleibt hier nur fuer das *Anlegen* erhalten. + +Abgrenzung: + - Aktive TPs: werden DIREKT als Gitea-Issue gepflegt (nicht hier). + - Status/Thema/State bestehender Issues: aendert nur der Mensch im Board. + - Per-TP-Prosa: bleibt in docs/teilprojekte.md / teilprojekte-archiv.md. -SSoT-Modell (seit TP36-Cutover 2026-09-07): Gitea fuehrt Index+Status — aktive TPs werden -DIREKT als Gitea-Issue angelegt/gepflegt, NICHT mehr aus einer Datei-Tabelle abgeleitet -(die Quick-Index-Tabelle wurde aus teilprojekte.md entfernt). Dieses Werkzeug haelt nur noch -die ARCHIVIERTEN (closed) TPs mit teilprojekte-archiv.md idempotent abgeglichen und fasst -offene Issues nie an. Die per-TP-Prosa bleibt in docs/teilprojekte.md / teilprojekte-archiv.md. Token: ~/.hermes/.env (GITEA_ACCESS_TOKEN, GITEA_HOST) — wird NIE ausgegeben. Aufruf: - python3 bin/gitea-tp-sync.py # Dry-Run (default) — zeigt, was passieren wuerde - python3 bin/gitea-tp-sync.py --commit # legt Labels + Issues an / gleicht sie ab + python3 bin/gitea-tp-sync.py # Dry-Run (default) — zeigt, was fehlt + python3 bin/gitea-tp-sync.py --commit # legt fehlende Archiv-Issues an """ import json import os @@ -33,16 +39,6 @@ HERMES = Path.home() / ".hermes" / ".env" OWNER_REPO = "admin/bf-infra" COMMIT = "--commit" in sys.argv -# Marker -> (status-label, gitea-state) -MARKER = { - "🔜": ("status:todo", "open"), - "⏳": ("status:wip", "open"), - "⏸️": ("status:paused", "open"), - "💡": ("status:idea", "open"), - "🟢": ("status:concept", "open"), - "✅": ("status:done", "closed"), - "❌": ("status:dropped", "closed"), -} LABEL_COLORS = { "status:todo": "#ededed", "status:wip": "#fbca04", "status:paused": "#c5def5", "status:idea": "#d4c5f9", "status:concept": "#bfdadc", "status:done": "#0e8a16", @@ -105,23 +101,31 @@ def api(method, path, body=None): # --------------------------------------------------------------------------- parse def parse_archiv(exclude_nums): - """Archiv-Header -> archivierte TPs. exclude_nums = TP-Nummern, die in Gitea OFFEN sind - (reopente TPs mit Archiv-Zeiger) -> die bleiben offen, nicht als closed abgleichen.""" + """Archiv-Header -> archivierte TPs, die ein Issue bekommen SOLLEN. + + Header-Form seit 2026-10-03: `### TP25 · Gitea #17 — WLAN-Stabilisierung — …` + Der Status-Anspruch (frueher `▸ ✅ FERTIG …`) ist entfernt; die Datei traegt keinen + Status mehr. Der Gitea-Zeiger `· Gitea #N` ist die Bruecke, falls er schon da ist. + + exclude_nums = TP-Nummern, die in Gitea OFFEN sind -> die brauchen kein neues Issue. + """ out = [] - hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\s*—\s*(.+?)\s*▸", ) + hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\b\s*(?:·\s*Gitea\s*#(\d+)\s*)?—\s*(.+?)\s*$") seen = set() for line in ARCHIV.read_text().splitlines(): m = hdr.match(line) if not m: continue - tp, title = m.groups() + tp, issue_no, title = m.groups() num = tp[2:] if num in exclude_nums or num in seen: continue seen.add(num) - short = re.sub(r"\*\*|`", "", title).strip()[:80] - out.append({"tp": tp, "num": num, "marker": "✅", - "title": short, "date": "", "src": "archiv"}) + # Caveat hinter dem zweiten ` — ` abschneiden; nur der Kurztitel gehoert ins Issue. + short = re.split(r"\s+—\s+", title)[0] + short = re.sub(r"\*\*|`", "", short).strip()[:80] + out.append({"tp": tp, "num": num, "issue": issue_no, + "title": short, "src": "archiv"}) return out @@ -173,43 +177,44 @@ def main(): print(f"Geparst: {len(items)} archivierte TP (aktive TPs = SSoT in Gitea: " f"{len(open_nums)} offen, nicht angetastet)\n") - created = updated = 0 + created = skipped = 0 for it in items: - status_label, state = MARKER.get(it["marker"], ("status:todo", "open")) + # Archiv-TPs sind abgeschlossen -> neu angelegte Issues gehen direkt auf closed. + status_label, state = "status:done", "closed" labels = [status_label] th = THEME.get(it["num"]) if th: labels.append(f"thema:{th}") title = f"{it['tp']} — {it['title']}" body = (f"Steuerungs-SSoT: Index/Status hier in Gitea; **Prosa/Detail** im Repo unter " - f"`docs/{'teilprojekte-archiv.md' if it['src']=='archiv' else 'teilprojekte.md'}` " - f"(Abschnitt `{it['tp']}`).\n\n" - f"- Status: {it['marker']} ({status_label})\n" - + (f"- Abschluss: {it['date']}\n" if it["date"] else "")) + f"`docs/teilprojekte-archiv.md` (Abschnitt `{it['tp']}`).\n\n" + f"- Status: ✅ ({status_label})\n" + f"- Angelegt aus dem Archiv-Backfill (`bin/gitea-tp-sync.py`).\n") want_ids = [label_id[l] for l in labels if l in label_id] cur = by_tp.get(it["tp"]) - if cur is None: - print(f" [{'+' if COMMIT else 'DRY'}] NEW {title} {labels} state={state}") - if COMMIT: - sc, res = api("POST", f"/repos/{OWNER_REPO}/issues", - {"title": title, "body": body, "labels": want_ids}) - if state == "closed" and isinstance(res, dict) and res.get("number"): - api("PATCH", f"/repos/{OWNER_REPO}/issues/{res['number']}", {"state": "closed"}) - created += 1 - else: - have = {l["name"] for l in cur.get("labels", [])} - drift = (have != set(labels)) or (cur.get("state") != state) - if drift: - print(f" [{'~' if COMMIT else 'DRY'}] UPD #{cur['number']} {title} " - f"{sorted(have)}->{labels} state {cur.get('state')}->{state}") - if COMMIT: - api("PATCH", f"/repos/{OWNER_REPO}/issues/{cur['number']}", - {"labels": want_ids, "state": state}) - updated += 1 + if cur is not None: + # 🔴 Existierendes Issue: NICHT anfassen. Weder Label noch State noch Body. + # Status/Thema aendert nur der Mensch im Board (Gitea = SSoT seit TP36-Cutover). + skipped += 1 + continue + + # Fehlt das Issue, obwohl der Header schon einen Zeiger traegt? -> Zeiger ist stale. + hint = f" ⚠ Datei-Header nennt bereits Gitea #{it['issue']} — Issue fehlt!" if it.get("issue") else "" + print(f" [{'+' if COMMIT else 'DRY'}] NEW {title} {labels} state={state}{hint}") + if COMMIT: + sc, res = api("POST", f"/repos/{OWNER_REPO}/issues", + {"title": title, "body": body, "labels": want_ids}) + if isinstance(res, dict) and res.get("number"): + api("PATCH", f"/repos/{OWNER_REPO}/issues/{res['number']}", {"state": "closed"}) + print(f" -> #{res['number']} angelegt (closed). Zeiger in " + f"docs/teilprojekte-archiv.md nachtragen: `· Gitea #{res['number']}`") + else: + print(f" !! Anlegen fehlgeschlagen: {res}") + created += 1 print(f"\n{'Angelegt' if COMMIT else 'Wuerde anlegen'}: {created} · " - f"{'Abgeglichen' if COMMIT else 'Wuerde abgleichen'}: {updated}") + f"unveraendert gelassen (SSoT = Gitea): {skipped}") if not COMMIT: print("Dry-Run — nichts geschrieben. Mit --commit ausfuehren.") diff --git a/docs/announcements.md b/docs/announcements.md index 9658212..03eac02 100644 --- a/docs/announcements.md +++ b/docs/announcements.md @@ -13,6 +13,24 @@ zeigt die Einträge, die seit dem letzten `/news`-Lauf dazugekommen sind (Rest a --- +## 2026-10-03 — TP-Header tragen keinen Status mehr (SSoT-Drift geschlossen) +**Betrifft:** alle, die `docs/teilprojekte.md` oder `teilprojekte-archiv.md` lesen oder editieren, und +alle, die ein TP anlegen/abschließen. +**Aktion:** Die `### TPnn`-Header haben die Form `### TP25 · Gitea #17 — — `. +Der Status-Anspruch (`▸ ✅ FERTIG (2026-09-15)`, `▸ ⏳ CUTOVER …`) ist **entfernt** — er war eine +dritte Statusquelle neben Issue und Prosa und lief den Labels nachweislich hinterher. **Wer einen +Status braucht, liest das `status:*`-Label im Gitea-Board**, nicht den Datei-Kopf — `· Gitea #N` +ist die Brücke. Status-Marker **im Fließtext** (Messchronik, `└─`-Zeilen) bleiben: sie beschreiben +Historie, keinen Stand. + +Dazu zwei Befunde, die dabei behoben wurden: +- **TP47 existierte nur in der Datei**, ohne Gitea-Issue (seit Commit `808d3a1`, 2026-09-27) — jetzt + **#74**. Das war ein echter SSoT-Bruch, kein fehlender Zeiger. +- **`bin/gitea-tp-sync.py` schrieb in die falsche Richtung**: der Abgleich leitete den Zielzustand + aus dem Datei-Marker ab (fest `✅`) und hätte #24 (TP34, bewusst `status:dropped`) auf `status:done` + gezogen. Das Werkzeug schreibt jetzt **nur noch fehlende** Archiv-Issues neu und fasst bestehende + **nie** an. + ## 2026-09-12 — Golden-Config-Backups laufen jetzt täglich (vorher KEIN Scheduler) **Betrifft:** alle, die sich auf GC-Backups/Compliance oder `gc-snapshot.py`-Werte verlassen. **Aktion:** Es gab **nie** einen automatischen GC-Backup-Zeitplan — Backup/Intended/Compliance liefen diff --git a/docs/handoff-20260928-core-wghub-ping-fix.md b/docs/handoff-20260928-core-wghub-ping-fix.md new file mode 100644 index 0000000..fff425f --- /dev/null +++ b/docs/handoff-20260928-core-wghub-ping-fix.md @@ -0,0 +1,52 @@ +# Handoff 2026-09-28 — Core erreicht FM/DW-Spoke-Netze nicht aus eigener Kraft + +## Kontext / Auslöser +Christoph konnte von Notebook + macmini immer `10.101.0.254` (FM-Standort-GW) pingen, eine +überforderte Vorgänger-Session hatte behauptet, der Core „komme dort nicht hin". Befund +sollte jetzt geprüft und ausgeräumt werden. + +## Befund (alles live verifiziert 2026-09-28, crs326-core) +| Quelle | → 10.101.0.254 (FM-GW) | → 10.101.0.10 (FM-Host) | +|---|---|---| +| macmini (VLAN20, gw 10.40.20.1) | ✅ 0 % Loss | ✅ 0 % Loss | +| Notebook (VLAN10, gw 10.40.10.1) | ✅ 0 % Loss | ✅ 0 % Loss | +| **Core default** (`/ping` ohne src) | ❌ **100 % Loss** | ❌ **100 % Loss** | +| **Core mit `src-address=10.40.10.1`** | ✅ **0 % Loss** (ttl 64) | ✅ **0 % Loss** (ttl 63) | + +## Root Cause +`wg-hub` (TP10 Multi-Site VPN Hub) hat **keine IP-Adresse** — weder live +(`/ip address print where interface="wg-hub"` → leer) noch im GC-Template +(`mikrotik_routeros.j2:379`, nur listen-port/mtu/name/private-key). + +FM-seitig erlaubt die FritzBox als AllowedIPs Richtung Hub `10.40.0.0/16` +(`routeros/fritzbox-wg-lanlan-klickanleitung.md`). Weitergeleiteter Client-Traffic +(Quell-IP 10.40.x) liegt in diesem Raum → läuft. **Core-eigener** Traffic ohne +explizite Quell-IP: RouterOS wählt mangels IP auf wg-hub eine Quell-IP **außerhalb** +von 10.40.0.0/16 (vermutlich WAN `90.146.33.177`) → FM routet die Antwort nicht zurück +→ Timeout. Der Tunnel selbst ist up (FM-Peer last-handshake ~1m45s, rx 140.8 GiB). + +## SSoT / Governance +Die Core-Config liegt im GC-Template `nautobot/mikrotik_routeros.j2`. Dort existieren: +- `:375` `/ip route add ... dst-address=10.101.0.0/24 gateway=wg-hub comment="TP10: FM spoke"` +- `:376` `/ip route add ... dst-address=10.95.10.0/24 gateway=wg-hub comment="TP10: DW spoke"` +- `:379-381` wg-hub Interface + fm/dw Peers (Privkey redacted) + +Ein Fix gehört **ins Template**, nicht als Direkt-Write aufs Gerät (GC-Compliance-Regel). +Deploy via `nautobot/gc-configplan.py` / GC-Job „Backup → Intended → Compliance". + +## Fix-Optionen (noch nicht entschieden) +1. **`preferred-source` auf die zwei TP10-Routen** (Zeile 375/376, z. B. `10.40.10.1`). + Kein FM-Eingriff nötig (FM erlaubt 10.40.0.0/16 bereits). Minimal-invasiv. **Empfohlen.** +2. wg-hub eine IP geben (z. B. dedizierte Adresse aus 10.40.0.0/16) — mehr Risiko, IP-Raum- + Eingriff, FM-Abgleich nötig. +3. FM-seitig AllowedIPs um die Core-WAN-IP erweitern — FritzBox-WG-Config eingeschränkt, nicht sauber. + +## Offene Entscheidungen +- Welche `preferred-source`-IP? (10.40.10.1 = MAIN-SVI ist naheliegend.) +- TP10 ist im Archiv „KERN DURCH, offen nur DDNS-Updater". Neuer Befund → TP10-Issue + `reopen` + Prosa zurück nach `teilprojekte.md`, oder als eigenes kleines TP? (Christoph-Entscheid.) +- Fix jetzt ausführen oder nur dokumentieren? + +## Aufwandsschätzung (Core-Ping-Fix) +Klein. 2 Zeilen Template-Edit + ein GC-Deploy-Zyklus (~30–60 min inkl. Backup/Intended/Compliance) ++ Ping-Verifikation. Kein Hardware-/FM-Eingriff bei Option 1. diff --git a/docs/teilprojekte-archiv.md b/docs/teilprojekte-archiv.md index 73d2612..078a086 100644 --- a/docs/teilprojekte-archiv.md +++ b/docs/teilprojekte-archiv.md @@ -77,7 +77,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) --- -### TP1 — ALLNET Core-Switch finalisieren ▸ ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt) +### TP1 · Gitea #34 — ALLNET Core-Switch finalisieren — → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt) - ✅ GE8 Trunk (native vlan1, tagged 10/20/30/40/50/90/99/100) — war bereits aktiv. - ✅ VLAN 40 umbenannt: UNTRUSTED → **MEDIA** (`switch/cli/04-vlan50-media-untrusted.cli`). - ✅ VLAN 50 angelegt + benannt: **UNTRUSTED**. @@ -86,7 +86,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP2 — Omada Access-Switch + Controller ▸ ✅ FERTIG (2026-06-15) +### TP2 · Gitea #35 — Omada Access-Switch + Controller - ✅ Omada Controller 6.2.10.17 auf DS923 (macvlan `.175`): `omada/docker-compose.yml`, Stack via Portainer endpointId=14. **Wizard abgeschlossen** (configured=true), Creds in `mcp/omada/.env`. - ✅ pve-mcp (samik081) in Claude Code eingetragen (`~/.claude/settings.json`, SSE `.173:3000`). - ✅ omada-mcp **live** auf **pve-LXC (`.126`, endpointId=2), ipvlan-IP `.176`**, Stack Id=200, URL in `~/.claude/settings.json`. @@ -122,7 +122,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP2.1 — Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) ▸ ✅ FERTIG (2026-07-16, User solo) +### TP2.1 · Gitea #36 — Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) — User solo) - **Root Cause:** beim VLAN20-Umzug des Omada-Controllers (TP14.1, 2026-07-10) ist die Controller↔sw2-Verbindung zerbrochen (sw2 seither „Disconnected", ursprünglich als reiner Mgmt-Loss vertagt). Beim Rettungsversuch hat der User versehentlich einen **Werks-Reset** am Switch ausgelöst. Ein anschließender Wiederherstellungsversuch (durch Claude) hat Teile der Config **unbemerkt halluziniert** — der damalige „wiederhergestellte" Stand war **nicht vertrauenswürdig**. - **Entscheidung (User 2026-07-15): Controller-Zustand als invalid betrachtet, sw2 komplett neu — diesmal OHNE Managementsoftware.** Kein Omada-Controller-Re-Adopt, kein Omada-Ecosystem-Anschluss (s. TP15-Korrektur — Omada bekommt generell keine Rolle mehr, auch nicht WLAN). sw2 direkt über eigenes Web-UI konfiguriert, wie ein eigenständiger Switch. - **✅ 2026-07-16 (User-Aussage): „voll durch, Funktionsweise erfolgreich getestet."** Nach dem gescheiterten Claude-Versuch hat der User sw2 **komplett allein** neu aufgesetzt (Runbook `docs/tp2.1-omada-switch-standalone-runbook.md` als Playbook-Referenz, nicht Schritt-für-Schritt-Log). **Keine formale Einzelschritt-Verifikation dieser Session** (kein Port-für-Port-Protokoll, keine Live-Prüfung durch Claude) — Status beruht auf User-Bestätigung „es geht einfach", nicht auf eigener Nachkontrolle. @@ -132,7 +132,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP3 — WLAN ▸ 📜 **VOLLSTÄNDIG HISTORISCH — der Ist-Stand steht in TP28, nicht hier** +### TP3 · Gitea #37 — WLAN > 🔴 **Lesehinweis (überarbeitet 2026-08-19): dieser gesamte TP ist Geschichte, von der ersten bis zur > letzten Zeile.** Er beschreibt zwei überholte Zustände nacheinander — die **CAPsMAN-Ära (Juni/Juli > 2026)** und danach den **Standalone-Rebuild vom 2026-08-08**. Beides gilt nicht mehr: @@ -170,7 +170,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP3.1 — ap2 VLAN-Trunk-Fix ▸ ✅ Config live (2026-07-01) — `routeros/tp3.1-ap2-vlan.rsc` +### TP3.1 · Gitea #38 — ap2 VLAN-Trunk-Fix — `routeros/tp3.1-ap2-vlan.rsc` - **Root Cause:** ap2 (CAP, local forwarding) hatte `bridge vlan-filtering=no` → `datapath.vlan-id` wurde nie auf die Leitung geschrieben → **ALLE** WLAN-Clients auf ap2 (main/media/iot/…) egressten **ungetaggt in VLAN1** (defconf/`10.40.41.x`) statt ins Ziel-VLAN. Symptom: iPhone (VLAN10) im OG „verbunden, aber sporadisch kein @@ -208,7 +208,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP3.2 — WLAN Coverage / P750W-Discovery ▸ ✅ **ABGESCHLOSSEN (2026-08-17, User-Test erfolgreich)** — `docs/wifi-p750w-coverage-and-map.md` +### TP3.2 · Gitea #39 — WLAN Coverage / P750W-Discovery — `docs/wifi-p750w-coverage-and-map.md` - ✅ **Brother P750W (VLAN30) gelöst:** DHCP-`client-id`-Fix (matcht über MAC → feste `10.40.30.5`); **an ap1 gepinnt** (geordnete wifi-access-list `accept w2-iot` → blanket `reject`, überlebt ap2-Re-Provision) — dozende Legacy-Clients nur am co-located-GW ap1 per ARP erreichbar, CAP ap2 nicht; mDNS `vlan30` in `mdns-repeat-ifaces`+5353 → in MAIN @@ -221,7 +221,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang) -### TP3.3 — WLAN Coverage-Kur ▸ 📜 **IN TP25 AUFGEGANGEN (2026-08-17)** — offene Arbeit steht dort, nicht hier +### TP3.3 · Gitea #40 — WLAN Coverage-Kur — offene Arbeit steht dort, nicht hier > ⚠️ Der Header sagte bis 2026-08-19 „🔜 TODO (Fortsetzung, neue Session)" — dieselbe Falle wie bei TP3: > ein Status, der nicht mitgezogen wurde. Der Re-Walk und die Dead-Spot-Kartierung sind **Schritt 5 von > TP25** und zwar bewusst **nach** den Funkparameter-Fixes; die 802.11k/v-Reparatur ist am 2026-08-13 @@ -260,7 +260,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP4 — Router-Cutover (hAP#1 = Router + WAN-Edge) ▸ ✅ FERTIG (2026-07-01) — ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1); alles unten beschreibt den damaligen ap1-Aufbau und gilt als **Historie/Referenz**, nicht als Ist +### TP4 · Gitea #41 — Router-Cutover (hAP#1 = Router + WAN-Edge) — ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1); alles unten beschreibt den damaligen ap1-Aufbau und gilt als **Historie/Referenz**, nicht als Ist - ✅ **Bridge VLAN-Filtering aktiviert** (`vlan-filtering=yes`); VLAN-Tabelle: VLAN1 untagged bridge+ether2, VLANs 10/20/30/40/50/90/99/100 tagged bridge+ether2. - ✅ **VLAN-Subinterfaces:** vlan10/20/30/40/50/90/99 auf bridge; je GW `.1` (10.40.10.1 … 10.40.99.1). - ✅ **DHCP:** dhcp-v10…dhcp-v99 je auf eigenem VLAN-Interface, pools .100-.200, GW+DNS = VLAN-GW. @@ -275,7 +275,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP5 — Firewall & Inter-VLAN-Policy ▸ ✅ **DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf** · **+ E5 (2026-08-19)** — 📋 `docs/tp5-deny-by-default-runbook.md` +### TP5 · Gitea #42 — Firewall & Inter-VLAN-Policy — + E5 (2026-08-19)** — 📋 `docs/tp5-deny-by-default-runbook.md` > 📌 **„DURCH" heißt nicht „abgeschlossen".** E5 (MAIN→IOT) kam einen Tag nach dem Drop — unter deny-by-default > braucht **jeder** neue Dienst über eine VLAN-Grenze seine Regel. Weitere Etappen sind normaler Betrieb, > kein Rückschritt. Der Weg dahin ist immer derselbe: **erst den Drop-Log lesen**, dann die Regel schreiben. @@ -411,7 +411,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ ✅ **GESCHLOSSEN 2026-08-23 — CAKE gebaut, gemessen, wieder ausgebaut.** ⚠️ **Die Erklaerung darin ist von TP6.1 ueberholt: nicht DOCSIS, sondern fehlende Klassentrennung.** +### TP6 · Gitea #43 — QoS/CAKE + Call-Quality-A/B-Test — ⚠️ **Die Erklaerung darin ist von TP6.1 ueberholt: nicht DOCSIS, sondern fehlende Klassentrennung.** > 🔴 **ZUERST LESEN — diese Analyse ist teilweise widerlegt (2026-08-23, durch TP6.1).** > Unten steht, die ~30 ms entstuenden im DOCSIS-Uplink und seien unabaenderlich. **Sie sind es > nicht.** TP6.1 holt dieselbe Strecke unter derselben Saettigung auf **11,0 ms** — allein durch @@ -550,7 +550,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP6.1 — Saettigung vermeiden statt Bufferbloat bekaempfen ▸ ✅ **LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last** +### TP6.1 · Gitea #44 — Saettigung vermeiden statt Bufferbloat bekaempfen > **Der Gedanke kam von Christoph** beim TP6-Abschluss („dann koennten wir zumindest den Sync > von der NAS zurueckhalten“) — und er hat mein TP6-Urteil zu Recht angezweifelt. > @@ -601,7 +601,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP7 — Media-Discovery (mDNS) für VLAN 40 ▸ ✅ FERTIG (2026-07-02) — `docs/tp7-mdns.md` +### TP7 · Gitea #45 — Media-Discovery (mDNS) für VLAN 40 — `docs/tp7-mdns.md` - ✅ **Built-in mDNS-Repeater** aktiv — **Live-Stand `crs326-core` (verifiziert 2026-08-14):** `mdns-repeat-ifaces=vlan10,vlan20,vlan30,vlan40,vlan90` → MAIN/INFRA/IOT/GUEST ↔ MEDIA. Reversibel (Feld leeren). 📌 **`bridge` (=VLAN1) ist nicht mehr dabei** — mit dem VLAN1-Retirement erwartbar und korrekt, war aber nie nachgezogen. @@ -618,7 +618,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP7.1 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) ▸ ✅ **LÄUFT (2026-08-18)** — User-bestätigt: „sonos ist da!" +### TP7.1 · Gitea #46 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) — User-bestätigt: „sonos ist da!" - ✅ **Umsetzung:** eigener LXC **108 `mcast-relay`** auf pve (Ubuntu 24.04, 1 Kern, 256 MB, `onboot`), zweibeinig: `eth0` = **10.40.10.21** (VLAN10), `eth1` = **10.40.40.21** (VLAN40), Default-Route nur über VLAN10, **kein IP-Forwarding**. Dazu `core ether13` (pve-Port) um `tagged vlan10, vlan40` @@ -659,7 +659,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP7.2 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery ▸ ✅ FERTIG (2026-07-03) — `docs/sonos-inventar.md` +### TP7.2 · Gitea #47 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery — `docs/sonos-inventar.md` - **Root Cause (widerlegt frühere Doku):** HA-`media_player.*`-Sonos alle `unavailable`. HA sitzt **nicht** auf VLAN 20, sondern noch auf `10.40.41.105` (VLAN-1-Transfernetz, IP-Migration steht aus). Der mDNS-Repeater deckte VLAN 1 **nicht** ab, und die HA-Sonos-Integration ist **SSDP-abhängig** (`manifest dependencies:[ssdp]`, @@ -682,7 +682,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP9 — Gateway-/Router-HA ▸ ❌ OBSOLET (User-Entscheid 2026-08-08) +### TP9 · Gitea #48 — Gateway-/Router-HA - ❌ **Verworfen (User-Begründung 2026-08-08, „ganz klar nein"):** am `crs326-core` führt ohnehin **nie ein Weg vorbei** — er ist der unumgängliche L3-Durchleitungspunkt. Ein zweites Gerät / VRRP **beseitigt diesen SPOF nicht**, sondern baut nur **zusätzliche Sollbruchstellen** ein (2× Config-Sync für DHCP/DNS/Firewall/NAT, Split-Brain-Risiko, VRRP-Eigenfehler) → netto ein Zuverlässigkeits-**Verlust**, kein -Gewinn. Die Frage ist nicht „Aufwand vs. Nutzen", sondern „mehr Fehlerquellen einbauen?" → nein. DR-Weg = Config-Backup-Restore (+ ggf. AP-Reprovisionierung). VRRP-Skizze unten = nur noch historische Recherche. - **„Was, wenn ap1 ausfällt?"** → **VRRP** löst Gateway-Failover je VLAN ~gratis (kreuzweise Master/Backup). ⚠️ **Haken (nicht Last):** DHCP/Firewall/NAT/DNS müssen auf **BEIDEN** APs **identisch** gepflegt sein, sonst „Gateway up, Internet tot". @@ -691,7 +691,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP10 — Multi-Site VPN ▸ ✅ KERN DURCH (2026-06-27) — beide WG live auf ap1-Hub (`docs/tp10-vpn-migration.md`) +### TP10 · Gitea #49 — Multi-Site VPN — beide WG live auf ap1-Hub (`docs/tp10-vpn-migration.md`) > **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).** Research: `docs/research/vpn-comparison.md`. - **✅ KERN DURCH (2026-06-27) + EDGE-CUTOVER (2026-07-01):** beide AVM-Tunnel → **2× WireGuard mit Hub auf ap1** migriert. Live: `peer fm` (`10.101.0.0/24`, bidirektional, myfritz-Endpoint) **und** `peer dw` (`10.95.10.0/24`, CGNAT-Dial-in) mit frischem @@ -711,7 +711,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP14 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) ▸ ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/` +### TP14 · Gitea #50 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) — live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/` - ✅ **ABGESCHLOSSEN — Migration komplett** (Live-Check 2026-08-08 vom core: NAS `.20.10`, pve `.20.100`, Docker-LXC `.20.126`, platform-pg `.20.20`, Nautobot `.20.30` alle 0% Loss). Alle Hosts + Container auf VLAN20. **TP14.4** (CF-Access-Token-Rotation) = kleiner, nicht-blockierender Standalone-Follow-up (CF/TP11-Umfeld); **TP14.5** war nie ein eigener Change (nur ein Reihenfolge-Gate) → erfüllt, TP5 damit entblockt. - ✅ **NAS (DS923, ep14) migriert (2026-07-09/10):** Docker von `default_external` (macvlan .41) auf **ein** `vlan20`-Macvlan (10.40.20.0/24, gw .1, parent `ovs_eth0`); DSM-Cutover `.20.10`; Portainer-Endpoint `.20.10:9001`. **Root-Cause der Dauerfehler** = Portainer **Config-Only-Macvlan-Falle** (`Driver:null`, kein Container-Attach) + Macvlan-Parent-Exklusivität (`default_external` muss weg für `vlan20`) + `default_internal` (172.18) weg. Container laufen auf `.20.x`. - ✅ **CF-Ingress v116 (2026-07-10):** 6 NAS-Origins .41→.20 (SSoT `docs/cf-tunnel-mapping.md` §8); `vw` 200 via Tunnel = pve→VLAN20-Routing steht (502 direkt nach Ingress-PUT = transient). @@ -723,7 +723,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP14.1 — Session A: NAS fertigstellen ▸ ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT +### TP14.1 · Gitea #51 — Session A: NAS fertigstellen — Omada-Inform/sw2 VERTAGT - ✅ **Alle ep14-Container auf vlan20 redeployed + verifiziert running/.20.x** (via Portainer-Stack-Redeploy, Env preserved, Volumes intakt): oc-stack(112 → prometheus `.151`/node-exporter `.154`/grafana `.109`), default(72 → hms-mqtt `.124`), nginx(101 → dashboard-web `.148`), oc-central(160 → qdrant `.159`), oc-charlie(158 → `.162`+cf), oc-johnny2(150 → `.161`+cf), gitea(111 → `.157`, DB `.20.23:3306` erreichbar ✓), drawio(107 → `.139`, intern; CF-CNAME war eh gelöscht). - ✅ **pve-mcp NEU** als Stack (id 202, vlan20-only `.20.173`), verbunden mit Proxmox `.41.100` (v9.0.11) — ⚠️ **StreamableHTTP am Root-Pfad `/`, NICHT `/sse`** (mcp-pve v0.4.8, `MCP_TRANSPORT=http`). `~/.claude.json` `mcpServers.pve-mcp` NEU angelegt `{type:http, url:http://10.40.20.173:3000/}` (war vorher gar nicht drin) → wirkt nach Claude-Code-Restart. - ✅ **Tunnel-Smoke** (CF-Ingress v116 zeigte schon auf `.20`): gitea 200, grafana 302, vw 200 — Origins existieren jetzt real. @@ -734,7 +734,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP14.2 — Session B: pve-Docker-LXC (.126) → VLAN20 ▸ ✅ DURCH (2026-07-10) (ipvlan) +### TP14.2 · Gitea #52 — Session B: pve-Docker-LXC (.126) → VLAN20 - ✅ ep2-Container auf `vlan20`: **omada-mcp `.20.176`** (OMADA_BASE_URL→`.20.175`), **adguard_bf1 `.20.16`**, **adguard_fm2 `.20.17`** — alle running/reachable (health 200, adguard 302). Transf. Files `docs/nas-vlan20/stacks/{195-adguard,200-omada-mcp}.yml`. - ✅ LXC 400 (`docker`) net0 → `tag=20, ip=10.40.20.126/24, gw=10.40.20.1` (⚠️ **Retag brauchte Christoph** — Token `claude@pam!claude` hat kein `VM.Config.Network`; UI-Edit, dann Reboot via API mit `VM.PowerMgmt`). Portainer wandert auf `https://10.40.20.126:9443`. - 🐞 **Gotcha: in-Container-Iface heißt jetzt `veth20`, NICHT `eth0`** (UI setzte Name analog net1=veth16) → Docker `vlan20`=ipvlan **`parent=veth20`** (nicht eth0 wie im Runbook). cloudflared(host)/portainer(bridge) unberührt. @@ -744,13 +744,13 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP14.2a — pve-mcp Single-Session-Fix + VLAN20-Host-DNS ▸ ✅ (2026-07-10) +### TP14.2a · Gitea #53 — pve-mcp Single-Session-Fix + VLAN20-Host-DNS - **pve-mcp „Failed to connect":** Ursache = **mcp-pve v0.4.8** kann im HTTP-Transport nur **eine** MCP-Session (ein globaler `StreamableHTTPServerTransport`, `sessionIdGenerator` gesetzt) → erster Client nach Boot belegt den Slot, jeder weitere Init (neue Claude-Code-Session, `claude mcp list`-Healthcheck, Reconnect) = `400 "Server already initialized"`. **omada-mcp NICHT betroffen** (frische Session-ID pro Init → Per-Session-Transport). **Fix = Upgrade auf 0.6.1** (Per-Session-Map, `serverFactory()` je Session, gleiche Env-Vars = Drop-in); Stack 202 neu deployed, Container `.20.173` = 0.6.1, `Connected to Proxmox VE 9.0.11`, `tools/list` liefert `pve_*`, **Claude Code ✔ Connected**. 3× init hintereinander = 3× 200 mit verschiedenen session-ids (verifiziert). - **Kollateral-Fund — VLAN20-Host-DNS kaputt (beide Docker-Hosts):** nach dem VLAN20-Umzug zeigten **NAS** *und* **pve-LXC** `/etc/resolv.conf` noch auf totes `.41.254` → `docker pull` ghcr.io scheitert (`EAI_AGAIN` / 5s-Resolver-Timeout; Egress zu ghcr-IP `140.82.x:443` = OK 31 ms, also **nur Resolver tot**, nicht Egress). Fixes: **LXC** `.20.126` → `pct set 400 --nameserver 10.40.20.1` (Christoph). **NAS** → DSM-„Allgemein"-DNS war `.20.1`, ABER die **Netzwerkschnittstelle zog DNS per DHCP** und überschrieb `resolv.conf`; Fix = Schnittstelle „DNS manuell" `10.40.20.1` (Christoph) → NAS pullt wieder direkt. **Merkregel Synology:** GUI-DNS ≠ was `dockerd` liest (Quelle `/etc/resolv.conf`, per DHCP überschreibbar). **Workaround solange NAS-DNS tot:** `docker save`(LXC, hat DNS) → **gzip** (191-MB-tar reißt am Portainer-Agent-Socket-Proxy „context canceled"; ~70 MB gz geht) → Portainer `POST /endpoints/14/docker/images/load` → Stack-Redeploy `pullImage:false`. Gotcha: laufende Container frieren den `127.0.0.11`-Upstream zum Erstellzeitpunkt ein (Recreate nötig; für pve-mcp egal — Proxmox per IP `.41.100`). Memory `ref_pve_mcp_singlesession_and_vlan20_dns`. -### TP14.3 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep ▸ ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt +### TP14.3 · Gitea #54 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep — dormante Stacks weiter bewusst nicht deployt - ✅ **Dormante Stacks bereitgestellt (2026-07-11):** `transform.py` auf core(43)/openclaw(92)/openclaw2(97)/oc20260307(119)/openspeedtest(74)/speedtest_tracker(80)/technikscheiss(42) → **`.new.yml` + `.orig.yml`** in `docs/nas-vlan20/stacks/` (gitignored). **Nicht deployt** (User: „im Bedarfsfall später", Container-Zukunft ungewiss). 42/43 waren `default_internal` → jetzt vlan20-only (DHCP-.20.x). ⚠️ **Deploy-Caveats:** externe Refs bewusst NICHT umgeschrieben (`DB_HOST=.41.122` [pre-existing tote openclaw-DB, in 80+119], `OLLAMA_BASE_URL=.41.129`, `REDIS_URL=.41.111` in 119); **Kollision** `43-core` will `.20.23` = gitea-DB-IP (nicht gleichzeitig deploybar). - ✅ **Referenz-Sweep `.41→.20` (2026-07-16, im Rahmen der TP19-Backup-Vorbereitung live gefunden + gefixt):** ap1 `/ip dns static` hatte noch `nas.bf.lan→.41.10` und `docker.bf.lan→.41.126` **stale** — korrigiert auf `.20.10`/`.20.126` (MikroMCP `manage_dns_entry`, dry-run+apply). Zusätzlich per User-Entscheid **Omada-Controller + omada-mcp komplett gelöscht** (overkill für 1 Switch auf der Abschussliste) → DNS-Einträge `omada.bf.lan`/`omada-mcp.bf.lan` entfernt statt migriert. `fritzbox.bf.lan` (totes Gerät, physisch weg seit 2026-07-01) ebenfalls entfernt. `docs/ip-plan.md` + CLAUDE.md nachgezogen. - ✅ **ERLEDIGT/GEGENSTANDSLOS (live geprüft 2026-08-16):** ~~`~/.claude.json` (omada-mcp-Eintrag verwaist), CF-Ingress-Rest (`omada`/`omada-mcp`-Hostnames, ungeprüft)~~ @@ -760,7 +760,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ ✅ ERFÜLLT (2026-08-08) — kein eigener Change +### TP14.5 · Gitea #55 — Session E: Freigabe-Gate → TP5-Firewall — kein eigener Change - Gate „TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20)" ist erfüllt → **TP5 (Deny-by-default) ist entblockt.** Reines Reihenfolge-Gate, kein eigener Change. @@ -771,7 +771,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". > auf tote Adressen — `backup-ap1.py` allerdings auf eine **lebende falsche** (`10.40.10.1` ist die > VLAN10-SVI des Core, nicht ap1) und hätte still das falsche Gerät gesichert. > -### TP15 — HW-Refresh: Switching + Router ▸ ✅ **ABGESCHLOSSEN (2026-08-08)** — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) **und** Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc` +### TP15 · Gitea #56 — HW-Refresh: Switching + Router — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) **und** Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc` > 🔌 **Scope-Änderung 2026-08-16 (User-Entscheid): die DC-USV ist aus diesem TP herausgelöst und heißt jetzt **TP24**.** Sie war der letzte offene Rest hier, hat mit Switching/Routing sachlich nichts zu tun und trägt eigene Entscheidungen (Elektrik, Batterie, Standort). TP15 ist damit inhaltlich zu. > *Vorherige Header-Fassung (Stand 2026-08-05, überholt): „🟢 CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23); Core-Tausch/L3-Umzug **vorbereitet, Cutover-Config materialisiert (2026-08-05)**, Cutover = nächstes Wartungsfenster."* - ✅ **BRING-UP BEIDER CRS326 + BRANCH-TAUSCH gegen sw2 — DURCH (2026-07-23, Claude via REST unter Governance-Ausnahme #2).** Ablauf/As-built: @@ -825,7 +825,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` ▸ ✅ **ABGESCHLOSSEN (2026-08-17)** — `docs/tp16.1-warp-mesh-native-crs326.md` +### TP16.1 · Gitea #57 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` — `docs/tp16.1-warp-mesh-native-crs326.md` - ✅ **Kernergebnis: ICMP ist repariert.** Privater Zugang unterwegs läuft über **natives WireGuard auf `crs326-core`** statt cloudflared-QUIC — **0 % Ping-Loss auf alle BF-VLANs, vorher 100 %**. Genau das Symptom aus dem Incident 2026-07-16, das dieses TP ausgelöst hat. Öffentliche FQDNs unberührt auf dem 3-Tunnel-HA (TP16). Compliance **68/68**. - ✅ **§4.0 erledigt 2026-08-17: Mesh-Node angelegt.** `bf-core-mesh` = **`d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env` (`CF_MESH_REGISTER_TOKEN`, gitignored). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.** - 🔑 **Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe).** Der alte User-Token hatte **zwei** fehlende Permissions (§4.0 *und* §4.1 — `devices/settings` war schon lesend `403`). Ersetzt durch einen **account-owned** Token (`9984078812bc…`, ⏰ **gültig bis 2027-01-09**). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an `/accounts/{id}/tokens/verify` — am alten `/user/tokens/verify` melden sie `1000 "Invalid API Token"`, **während alle echten Endpoints 200 liefern**; (2) Dashboard-Fehler `"Account tag in access policy must match tag in request uri"` = eine **Zone**-Permission (`DNS`) steckt in der Policy-Zeile mit **Account**-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in `docs/tp16.1-…md` §4.0. **Merke: Dashboard sagt „Edit", API sagt „Write".** @@ -865,7 +865,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` ▸ ✅ **KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30** — Geräte-PW *(war TP17.2, seit 2026-08-24 **TP30** Posten #3)* — `mcp/shelly/`, Runbook `docs/tp17-shelly-go-runbook.md` +### TP17 · Gitea #58 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` — Geräte-PW *(war TP17.2, seit 2026-08-24 **TP30** Posten #3)* — `mcp/shelly/`, Runbook `docs/tp17-shelly-go-runbook.md` - ✅ **Abschluss 2026-08-13 (User-Bestätigt):** auch die zuletzt offenen saisonalen Shellys (s1/s2/s3/s12) sind inzwischen umgeklemmt — **alle Shellys auf `dirtyvader-iot`/VLAN30**, kein VLAN1-Shelly-Rest mehr. Migration-Teil von TP17 damit vollständig abgeschlossen. - ✅ **GO durchgezogen 2026-07-13 (16 Shellys VLAN1→VLAN30, 0 Fehler):** Pilot s26 + Welle A (s4–s9) + Welle B (s10/11/13/15/17/19/20/21/25) → alle auf `dirtyvader-iot`/`10.40.30.` verifiziert (SSID+IP+rssi). Mit den 6 Vorab-Migrierten (s14/16/18/22/23/24) = **22/26 auf VLAN30**; VLAN1-Sweep `.180-250` = **0 Shellys**. Restliche 4 saisonal ausgesteckte (s1/s2/s3/s12) inzwischen ebenfalls migriert (s.o.). - 🔧 **Anker-Verfahren (User-Idee, statt sta1-Backup):** je Gerät **vor** dem sta-Write die geräteeigene **AP-Funktion** aktiviert (`WiFi.SetConfig ap.enable` — Firmware verlangt WPA2, kein offenes AP → PSK=IOT-PSK, live/kein Reboot) = Notanker, falls VLAN30-Join scheitert (Gerät bleibt über `Shelly*-`-AP erreichbar, **kein physischer Reset**). Nach Verify alle 22 AP-Anker wieder `deanchor`t. Runner: `mcp/shelly/migrate.py` (`migrate`/`deanchor`/`auth`/`table`, Dry-Run default, stoppt bei erstem Fehler, reused `discover.py`). @@ -912,7 +912,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP19 — cudy-Teardown + VLAN1-Client-Retirement ▸ ✅ **GESCHLOSSEN (2026-08-18)** — `docs/tp19-vlan1-cudy-teardown-runbook.md` +### TP19 · Gitea #59 — cudy-Teardown + VLAN1-Client-Retirement — `docs/tp19-vlan1-cudy-teardown-runbook.md` - ✅ **Abschluss-Sweep 2026-08-18, alles live gegengeprueft (reine Reads):** - **VLAN1 ist leer:** `/ip arp` auf `bridge` = **0 Eintraege, 0 reachable**. Nicht Restverkehr, sondern gar nichts. Der Dual-DHCP-Race aus `project_vlan1_dhcp_incident` hat damit endgueltig @@ -1009,7 +1009,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". - **Teardown-Sequenz (angepasst an den Live-Stand):** VLAN1-Client-Sweep ist **schon fast leer** (nur sw1-Mgmt + pve-Interim reachable) → `defconf`/pool-v1 ist **bereits disabled** → cudy-Abbau **entfällt** (cudy1 umgenutzt statt abgebaut, cudy2 tot) → ✅ **SwitchBot + Twinkly erledigt (2026-07-16)** → **verbleibt:** Media-Check (B1.3), pve-`.41.100` abräumen (B1.5), Interim-Krücken bewerten (ap2 `frame-types=admit-only-vlan-tagged`, `st-main rrm/wnm`), Inventar/`ip-plan`/DNS `.41→Ziel` final. - Abh.: **TP14 ✅** (Hosts raus aus VLAN1), **TP17 ✅** (Shellys raus). Verwandt: `project_vlan1_dhcp_incident` (cudy-Dual-DHCP-Leak), TP5 (Deny-by-default danach sauberer). SSoT hier + `docs/tp19-vlan1-cudy-teardown-runbook.md`. Folge: **TP19.1**. -#### TP19.1 — VLAN99-MGMT-Cutover (sw1/sw2/ap2-Mgmt VLAN1 → VLAN99) ▸ ✅ ERLEDIGT (2026-08-14 nachgezogen) — alle lebenden Geräte auf VLAN99, die beiden Rest-Punkte gegenstandslos +#### TP19.1 · Gitea #60 — VLAN99-MGMT-Cutover (sw1/sw2/ap2-Mgmt VLAN1 → VLAN99) — alle lebenden Geräte auf VLAN99, die beiden Rest-Punkte gegenstandslos - ✅ **Abschluss-Nachtrag 2026-08-14 (Status-Korrektur, kein neuer Change):** der Header stand noch auf „ap2 vertagt, sw1 offen" (Stand 2026-07-16) — beides ist durch spätere TPs eingelöst bzw. gegenstandslos geworden, ohne dass es hier nachgetragen wurde. Ist-Stand: **core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`** — jedes lebende Netzgerät hat sein Mgmt auf VLAN99. **sw1/sw2 existieren nicht mehr** (physisch ausgebaut, in Nautobot per TP22 Schritt 5 auf *Decommissioned*). Damit ist TP19.1 vollständig; offen bleibt nur der VLAN1-**Client**-Teil, und der gehört zu TP19 (Hauptteil), nicht hierher. - **Scope:** die einzigen Dinge, die TP19s Hauptteil bewusst in VLAN1 belässt — sw1 (`.41.11`), sw2, ap2-Mgmt (`.41.2`) — auf VLAN 99 (MGMT, `10.40.99.0/24`) umziehen. Erst danach ist VLAN1 wirklich **komplett** weg, nicht nur als Client-Netz. - **User-Entscheid (2026-07-16):** kein eigenständiges TP-Nummer — bewusst als **Unterpunkt von TP19** geführt („VLAN1 killen"), nicht als TP22 o.ä. @@ -1024,7 +1024,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP20 — Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) ▸ ✅ **Plattform live (2026-07-16)** — Nautobot-DB angelegt, vw-Migration (TP20.1) offen — Memory `project_mikrotik_commit_nautobot_gc` +### TP20 · Gitea #61 — Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) — Nautobot-DB angelegt, vw-Migration (TP20.1) offen — Memory `project_mikrotik_commit_nautobot_gc` - ✅ **Platform-Postgres live (2026-07-16):** LXC 105 auf pve, `platform-pg.bf.lan` / `10.40.20.20`, Ubuntu 24.04 (native PG16-Paket, kein PGDG-Repo nötig — bewusste Abweichung von der sonstigen Debian/community-scripts-Konvention der übrigen pve-LXCs, aus PG16-Verfügbarkeitsgründen). Cluster mit `--locale en_US.UTF-8 -e UTF8` neu initialisiert (Erstinstallation lief in `C`/`SQL_ASCII`, für Nautobot ungeeignet, korrigiert). `wal_level=replica` gesetzt; **`archive_mode` bewusst noch NICHT an** — ohne pgBackRest/Garage-S3 (TP18) zum Prunen würde archivierte WAL unbegrenzt wachsen und die kleine 8GB-Disk füllen; wird nachgezogen sobald TP18 steht. Rolle+DB `nautobot` angelegt, `pg_hba.conf` erlaubt `scram-sha-256` aus `10.40.20.0/24`. End-to-End-Verbindungstest (Netzwerk, nicht nur localhost) erfolgreich. Creds in `postgres/.secrets` (gitignored, `chmod 600`). - **Auslöser:** Mehrere Dienste brauchen zentrale DB (Nautobot-SoT [TP21], Vaultwarden, HA-Recorder). Grundsatzentscheid statt Nautobot-Insellösung — „wir skalieren schneller als wir schauen können" (User). - ⚠️ **Abh.-Korrektur (User 2026-07-16): TP18 (Garage-S3) ist KEINE Vorbedingung mehr — „hat mit dem Netz nichts zu tun", transitiv aus der TP21-Kette entfernt.** pgBackRest→Garage-S3 (s.u.) wird **nachgezogen sobald Garage existiert**, blockiert aber nicht mehr den Start. `wal_level=replica`/`archive_mode` können mit einem Platzhalter/lokalen Archive-Ziel hochgezogen werden, produktives Off-Site-PITR kommt später. @@ -1048,7 +1048,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ 🟢 **Plattform + Datenmodell + Templates live** +### TP21 · Gitea #62 — Nautobot Golden Config (config-as-code Control-Plane) - 🔴 **BETRIEBSBEFUND 2026-08-18: der Stack hat sich zweimal in einer Nacht selbst umgebracht — Ursache war der Image-Healthcheck, nicht Celery.** `nautobot-worker` und `-scheduler` erben aus dem Image einen `HEALTHCHECK`, der alle **10 s** `nautobot-server health_check` startet — einen kompletten @@ -1112,7 +1112,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP21.1 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) ▸ ✅ NETZ WIEDER LIVE (2026-08-08); ~~Aufräum-Todos offen~~ → **alle fünf erledigt, letzter live gegengeprüft 2026-08-23** (s. Nachtrag am Ende) +### TP21.1 · Gitea #63 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) — ~~Aufräum-Todos offen~~ → **alle fünf erledigt, letzter live gegengeprüft 2026-08-23** (s. Nachtrag am Ende) > **Kern-Erkenntnis (wichtig gegen Doku-Verwirrung):** **`crs326-core` IST jetzt der produktive L3-Router.** Der Core-Cutover (TP15) wurde faktisch vollzogen — ap1 wurde plattgemacht/getrennt, core hat Routing/DHCP/DNS/NAT/WG-Hub übernommen. core-Mgmt = **`10.40.99.1`** (vlan99-SVI) bzw. same-subnet-Fallback **`10.40.10.1`** (RouterOS-Version 7.19.5 identifiziert das Gerät zweifelsfrei als CRS326, nicht als hAP/ap1). Die CLAUDE.md-Zeile „core grundkonfiguriert, NICHT produktiv" ist damit **überholt**. - **Verlauf:** Ausgangssymptom war ein L3-Ausfall-Eindruck (kein DHCP, „kein Routing nach .20.x"). Ursache = **unvollständiger Cutover**: core hatte L3-SVIs + Firewall, aber es fehlten DHCP-Server, WAN-Egress, WG-Hub und der DNS-Resolver-Upstream. ap1 (bisher alleiniges L3-Gerät) war bereits Werksreset + getrennt. - ✅ **DHCP wiederhergestellt** aus letztem ap1-Backup (`routeros/ap1-pre-core-cutover-20260806-073616.rsc`): 7 Server (`dhcp-v10…v99`) + 8 Networks + **54 statische Reservierungen** + Option `dns 1.1.1.1`. Reproduzierbar in `routeros/tp21-core-dhcp-from-ap1.rsc`. Legacy VLAN1 (pool-v1/defconf) + tote omada-inform-Option bewusst weggelassen. @@ -1153,7 +1153,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP22 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) ▸ ✅ **DURCH (2026-08-13)**, Compliance-Backlog **abgearbeitet 2026-08-16 → 68/68, 0 Drifts** — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08 +### TP22 · Gitea #64 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) — Compliance-Backlog **abgearbeitet 2026-08-16 → 68/68, 0 Drifts** — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08 - **Ziel:** die 4 Live-Geräte (crs326-core, crs326-branch, ap1, ap2) real unter **Nautobot Golden Config** nehmen — Backup → Compliance/Drift → intended → **Push (`enable_deploy`)**. Damit greift der Governance-Entscheid „Config exklusiv über Nautobot" operativ. Macht die aktuellen `routeros/.secrets/*.rsc`-Handoffs mittelfristig überflüssig. - **Stand aus TP21:** Plattform + Datenmodell + Git-Template + intended-config für core stehen; ap1/ap2 als reine APs zurückgeschrieben (`nautobot/ingest-ap-rebuild.py`, 2026-08-08). `enable_plan`/`enable_deploy` in `nautobot_config.py` noch **bewusst aus** (pull/read-only). - ✅ **Schritt 0 DURCH (2026-08-13) — Backup-Scrubbing + Repo-Neuanlage.** Vorgezogen aus Schritt 4, weil ohne Scrubbing jedes weitere Backup Secrets geleakt hätte. @@ -1234,7 +1234,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP26 — `/ip firewall filter` unter Drift-Kontrolle ▸ ✅ **DURCH (2026-08-17)** +### TP26 · Gitea #65 — `/ip firewall filter` unter Drift-Kontrolle > **Nachgetragen 2026-08-18.** TP26 existierte bis dahin nur als zwei Commits und eine `.rsc` — ohne > Abschnitt in dieser Datei. Genau die Lücke, die eine frische Session nicht finden kann. @@ -1255,7 +1255,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP27 — RouterBOOT-Firmware auf 7.24 nachziehen ▸ ✅ **DURCH (2026-08-17/18)** +### TP27 · Gitea #66 — RouterBOOT-Firmware auf 7.24 nachziehen > **Nachgetragen 2026-08-18.** Auch TP27 hatte keinen Abschnitt — die Arbeit steckt in Christophs Commit > `b93514a` plus `routeros/tp27-routerboot-firmware-724.rsc` und > `docs/routerboot-firmware-klickanleitung.md`. @@ -1274,7 +1274,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ↩️ **WIEDER GEÖFFNET 2026-08-30** +### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core` > Der Volltext steht seit dem 2026-08-30 wieder in > [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter @@ -1284,7 +1284,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". -### TP-R — Router-Capabilities-Sweep ▸ FERTIG (`docs/research/routeros-capabilities.md`) +### TP-R · Gitea #67 — Router-Capabilities-Sweep — `docs/research/routeros-capabilities.md`) - **Leitsatz: nativ vor Container, NAS vor Router** (128MB-Flash-Limit). - **Sofort nativ (~0 RAM):** RoMON · Netwatch · Scheduler + Auto-Backup/Mail · SNMP→externes Monitoring · **mDNS-Repeater** · spectral-scan · **802.11r/k/v (Fast-Roaming statt Mesh)** *(die Sweep-Fassung nannte hier CAPsMAN — das ist seit 2026-08-08 abgeschafft; 11r/k/v läuft standalone weiter, s. TP3/TP3.3)* · Back-to-Home/WireGuard. - **Auf NAS:** FreeRADIUS(+LDAP) · NPM · icinga/Grafana · CF-Tunnel. @@ -1305,7 +1305,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". > 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 +### TP25 · Gitea #17 — 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), @@ -1827,7 +1827,7 @@ Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-ver > Erklärungen — zweimal stand hier eine plausible Ursache, die die nächste Messung gekippt hat > (ETSI-Band C bei Fund B; „ap1 ans Sideboard" bei (F)/(G)). Wer TP25 fortsetzt, muss sehen können, > welche Wege schon abgelaufen sind. -### TP25 — WLAN-Stabilisierung ▸ 🟢 **KERN ERLEDIGT (2026-08-22): Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). Offen nur noch **Fund F** (5:05-Takt IoT) und die Entscheidung **5 GHz auf 20 MHz**. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6 +### TP25 · Gitea #17 — WLAN-Stabilisierung — 43 % → 9 % Verlust, 20,4 s → 1,0 s). Offen nur noch **Fund F** (5:05-Takt IoT) und die Entscheidung **5 GHz auf 20 MHz**. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6 > 🆕 **Messung 2026-08-20/21: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md)** > (Nebenprodukt der TP6-Messreihen, Core-Sampler im 2-s-Takt über 2,5 h, teils mit laufendem Videocall). > Drei Befunde: @@ -2223,7 +2223,7 @@ Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-ver -### TP39 — LXC-Hygiene auf pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup ▸ ✅ **ABGESCHLOSSEN (2026-09-01)** +### TP39 · Gitea #68 — LXC-Hygiene auf pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup **Auslöser:** ein 10-Sekunden-Hänger auf `gitea`, gefunden am Rand des TP16.3-§5-Flips. Er hatte mit dem Flip nichts zu tun, war aber nur deshalb sichtbar, weil zum ersten Mal jemand systematisch @@ -2302,7 +2302,7 @@ 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)** +### TP34 · Gitea #24 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) > ➡️ **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). @@ -2324,7 +2324,7 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein. --- -### TP44 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot ▸ ✅ **DURCH (2026-09-10): Etappe 1 + Etappe 2 (Teil A IPAM-Prefixe, Teil B Live-Sync als täglicher Nautobot-Job)** +### TP44 · Gitea #33 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot > **Entscheid (User, 2026-09-02):** die volle CF-Estate (Tunnel + WARP-Connector + VPC-Service + > Hyperdrive + Workers + alle Access-Apps) als strukturierte, abfragbare SSoT in Nautobot abbilden — > **etappenweise: manuell jetzt, Sync später.** Nautobot kann CF nicht steuern → „SSoT" heißt @@ -2368,7 +2368,7 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein. - **Beziehung zu TP37:** TP44 hat **TP37 Punkt 3** (CF-Connectoren) vorweggenommen — dieselbe native-Modell- Linie, die TP37 P1+2 (Substrat/Dienste) fortsetzt. -### TP41 — CF-Access-Reparatur + Zero-Trust-Konsolidierung ▸ ✅ **ABGESCHLOSSEN (2026-09-10)** — Auth-Kern seit 2026-09-01 komplett; **Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet** (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7. +### TP41 · Gitea #30 — CF-Access-Reparatur + Zero-Trust-Konsolidierung — Auth-Kern seit 2026-09-01 komplett; **Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet** (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7. **Auslöser:** CF-Access zwang Christoph **und Geli** täglich zum OTP-Login (soll: 30 Tage); GitHub/Google „funktionieren nicht". Root-Causes: Team-Domain-Rename `bf4`→`emsenhuber` (Provider-Callback veraltet), @@ -2426,14 +2426,14 @@ der Egress ohnehin Cloudflare (nicht diese IP) → der IP-Include wäre dann üb **Gitea-Least-Privilege** = eigener künftiger TP (jedes Projekt repo-scoped Login, Admin nur Claude/Christoph). - Bezug **TP11** (Monitoring): eine Plattenfüllstand-Warnung hätte das Wochen früher gezeigt. -### TP43 — dw-Standort-DNS-Uplink (site-spezifische Overrides) ▸ ♻️ **REOPENED 2026-09-11 → Volltext zurück in [`teilprojekte.md`](teilprojekte.md) (Abschnitt TP43)** +### TP43 · Gitea #32 — dw-Standort-DNS-Uplink (site-spezifische Overrides) Kurz zwischen 2026-09-11 verworfen und am selben Tag reopened: die 420-Prämisse war gegenstandslos (public CF-Worker, via WARP/TP41 gelöst), aber die **interne** BF-Namensauflösung über WG am dw blieb dabei offen (`portainer.bf.emsenhuber.net` am dw nicht erreichbar). Korrigierter Scope + verworfene 420-Historie stehen jetzt aktiv in `teilprojekte.md`. Gitea-Issue #32 = `status:todo` · `thema:firewall-dns`. -### TP42 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net ▸ ✅ **DURCH (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`, `.bf.lan` raus, `.16` (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an **TP37** gekoppelte Nautobot-Doku-Teil. +### TP42 · Gitea #31 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net — ein interner Suffix `bf.emsenhuber.net`, `.bf.lan` raus, `.16` (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an **TP37** gekoppelte Nautobot-Doku-Teil. **Auslöser (2026-09-01, TP41-Randbefund):** live gemessen liefern die realen Client-Resolver `10.40.20.36`/`.37` `*.emsenhuber.net → 10.40.20.40` (Caddy) — korrekt. Daneben existiert aber ein diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 55e1699..af8324a 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -69,28 +69,28 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · > > 📊 **Index/Statustabelle → [Gitea-Board](https://gitea.emsenhuber.net/admin/bf-infra/issues)** (siehe Kopf dieser Datei). Hier unten folgt nur noch die per-TP-Prosa. -### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt +### TP5.1 · Gitea #1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) - **Kontext (2026-07-08/09):** Airfryer „airschreier" (Philips/Versuni, ESP, `10.40.30.180`) bekam per DHCP-Option 6 `dns 1.1.1.1` als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — **kein** Content-Block, via Gateway-Logs bewiesen, s. `memory/ref_esp_ota_doh_not_block`). Nebenwirkung: public **1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH** (`px35ynlol3.cloudflare-gateway.com`) → **DNS-Sicht auf das Gerät weg**, Gerät umgeht zudem die Filterung. - **(a) DNS-Redirect statt Geräte-Override** (der dauerhafte Fix): dst-nat „UDP/53 (+TCP/53) von Low-Trust-Src → auf einen *plain* Resolver umbiegen" gibt **Logging zurück UND** funktionierendes OTA (kein langsamer DoH-Pfad fürs Gerät). Redirect-Ziel-Optionen: (i) ap1-Resolver mit *nicht*-DoH-Upstream für dieses Src-Set, (ii) direkt public 1.1.1.1 (dann nur Flow-Logging), (iii) eigener plain-Resolver. Sauberer als „Gerät verschwindet auf 1.1.1.1". Verortung: Teil des UNTRUSTED-Internet-only-Pinnings (TP5-„Offen"); Entscheidung v50-DNS-Policy (gefiltert vs. plain) hängt dran. - **(b) Live-Torch als Ad-hoc-Observability:** DNS ist bei direktem 1.1.1.1 blind, **Flows nicht** (NATen über ap1). `torch` auf `vlan30`/`vlan50` gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS `eu-west-1` IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln. - **Abh.:** UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker. -### TP8 — Identity & Access-Control ▸ research-gated +### TP8 · Gitea #2 — Identity & Access-Control - **RADIUS = FreeRADIUS auf der NAS** (✏️ User-Manager v5 kann **KEIN LDAP** → openldap nur via FreeRADIUS anbindbar). - **EAP-TLS (Zert) je Gerät + dynamische VLAN-Zuweisung** (RADIUS-Attribut `Tunnel-Private-Group-ID`). **Einfachste Heim-Alternative:** pro VLAN eine SSID mit **WPA3-PSK** (kein RADIUS) — Abwägung Komfort vs. Admin-Aufwand. - Abh.: TP3. (Research erledigt, s. TP-R.) -### TP11 — Monitoring & Mgmt-Control-Plane ▸ research-gated +### TP11 · Gitea #3 — Monitoring & Mgmt-Control-Plane - MikroMCP + omada-mcp + **SNMP/icinga-mikrotik**, RoMON, NTP, **E-Mail-Alerts**, Netwatch. → Teil von `docs/research/routeros-capabilities.md`. - **CF-Tunnel #1033 Track A ✅ live (2026-06-30):** Round-Robin-1033er über 3 asymmetrische Connectors behoben durch Reduktion auf pve-only Connector (NAS + macmini scheiterten an pve-lokalen/stalen Origins). SSoT: `docs/cf-tunnel-mapping.md`. **Track B/C läuft über TP14** (Ingress bereits v116 .41→.20 für NAS; pve-Connector→VLAN20 via TP14 Session B = #1033-Zielbild). -### TP12 — IPv6 (intern + extern, 3 Standorte) ▸ research-gated +### TP12 · Gitea #4 — IPv6 (intern + extern, 3 Standorte) - Konzept für FM/BF/DW. → TP-R. -#### TP12.1 — IPv6 im LAN (ULA) + v6-Firewall ▸ 🟠 **KONKRET GEWORDEN (2026-08-22), Treiber: Matter/Thread** +#### TP12.1 · Gitea #5 — IPv6 im LAN (ULA) + v6-Firewall > **Anlass:** der runde IKEA-Fühler **TIMMERFLOTTE** (Matter over Thread) ließ sich nicht anlernen — > „pairing failed" in der Companion-App. Ursache ist **nicht** die Firewall, sondern dass zwischen > VLAN10 und VLAN20 **überhaupt kein IPv6 geroutet wird**. Damit ist TP12 vom Konzept-TP zu einem @@ -168,7 +168,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · **v6-Firewall nachbauen → dann erst Adressen vergeben.** Gehört unter TP12 (Gesamtkonzept 3 Standorte), ist aber allein für BF ausführbar. -### TP13 — „Botschafts-WLANs" ▸ 🔜 **AUSFÜHRBAR (Runbook 2026-08-19)** — `docs/tp13-botschafts-wlan-runbook.md` · fun/low-prio +### TP13 · Gitea #6 — „Botschafts-WLANs" — `docs/tp13-botschafts-wlan-runbook.md` · fun/low-prio > 📌 Am 2026-08-19 überarbeitet: der Technikteil beschrieb noch Standalone-APs (SSIDs entstehen seit > TP28 am **Manager**, also ein Deploy statt zwei), und der Entwurf „nur `ssid=`, kein Security" war ein > **Denkfehler** — eine SSID ohne Security *ist* ein offenes WLAN, also genau der Fall, den der @@ -185,7 +185,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · - ⚠️ **Recht vor Scharfschalten prüfen (User privat, AT):** offenes WLAN + Portal berührt Impressumspflicht/Störerhaftung — **reine Namens-SSIDs ohne offenen Zugang sind unkritisch** (kein Fremd-Datenverkehr); im Zweifel nur die Beacon-Variante. - **Abh.:** ~~TP3-Rebuild ✅ (Standalone-APs — SSID-Änderung = 1 Deploy/GC-Job)~~ → **TP28 ✅** (CAPsMAN auf dem Core) + **TP22 ✅** (GC-Deploy-Pipeline). SSID-Änderung = **ein** Deploy auf den Core; Direkt-Edit am Gerät ist durch den Governance-Entscheid ausgeschlossen. -### TP14.4 — Session D: CF-Access-Profil für Claude ▸ ✅ **IN TP41 T4 AUFGEGANGEN (2026-09-01)** — `docs/tp14.4-cf-access-token-runbook.md` (historisch) +### TP14.4 · Gitea #7 — Session D: CF-Access-Profil für Claude — `docs/tp14.4-cf-access-token-runbook.md` (historisch) > ✅ **Erledigt über TP41 T4 (2026-09-01), nicht als eigenständiger `claude-mcp`-Token:** der > `claude-mcp`-Service-Token war die Basis (`t4a.py`), wurde **→ `zt-svc` umbenannt + rotiert** und über > Gruppe `zt-automation` konsolidiert. `mcp/cloudflare/.env` trägt jetzt **beide** Keys @@ -201,7 +201,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · erneut rotieren. Abnahme = ein Aufruf **mit** den Headern (antwortet) **und** einer **ohne** (403) — der Erfolgsfall allein beweist nichts, der Hostname könnte auf `bypass` stehen. -### TP16 — CF-Tunnel: Round-Robin killen + reziprokes HA ▸ ✅ **HA WIEDER VOLL (2026-08-24)** — Kern 2026-07-10 — `docs/cf-tunnel-mapping.md §10 + §15`, #1033 +### TP16 · Gitea #8 — CF-Tunnel: Round-Robin killen + reziprokes HA — Kern 2026-07-10 — `docs/cf-tunnel-mapping.md §10 + §15`, #1033 - ✅ **GEFIXT 2026-08-24 — Ursache war ein `docker stop`, kein QUIC-Flap.** Die beiden macmini-Connectoren (`cloudflared-bfnas`, `cloudflared-bfpve`) standen auf `Exited (0)`, **beide `finishedAt` auf derselben Sekunde `2026-07-21T13:00:53`**, bei korrekt gesetztem `restart=unless-stopped` und `http2`. Fix = @@ -249,7 +249,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · - Abh.: TP14.1/14.2 ✅. Verwandt: **TP11** (CF-Tunnel-Track-B/C + Access), #1033. SSoT: `docs/cf-tunnel-mapping.md`. Memory `project_cf_tunnel_1033`. - ✅ **Incident 2026-07-16 (User unterwegs, WARP „spinnt", keine BF-IPs mehr) ▸ Public-FQDN-Teil gefixt:** macmini-Connectoren (`cloudflared-bfnas`/`-bfpve`) flappten live unter QUIC/UDP (Docker-Desktop-Bridge-NAT) → `bf-nas`/`bf-pve` von 2 auf 1 Connector degradiert (HA faktisch weg, nicht down). Fix: beide auf `TUNNEL_TRANSPORT_PROTOCOL=http2` umgestellt (rollend, 0 Downtime), wieder voll HA. Konvention: macmini-Connectoren künftig immer `http2`. **ICMP-Befund (getrennt, strukturell):** TCP über die WARP-Route ging überall, ICMP nicht (fm 36 %/bf 100 %/dw 100 % Loss) — cloudflared ist QUIC-Proxy, kein WireGuard. **Kurz als „bf-pve-Route wegen USV" notiert (Commit `1c1a676`) → REVIDIERT** (Begründung invertiert: bf-pve-Connectoren laufen auf NAS+macmini, nicht pve). Details: `docs/cf-tunnel-mapping.md §13`. **→ Richtungswechsel für Private Access = TP16.1.** -### TP16.2 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) ▸ 📋 KONZEPT (2026-08-14), Umsetzung offen +### TP16.2 · Gitea #9 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) — Umsetzung offen - 🔴 **HARTER CONSTRAINT (User 2026-08-14): `vw` wird NICHT WARP-/Access-geschützt.** Gilt dauerhaft, nicht „vorerst". **Begründung (technisch belastbar, nicht Bequemlichkeit):** die Bitwarden-Clients (Browser-Extension, Mobile, Desktop) können den **interaktiven Access-Login nicht durchführen** — dasselbe Muster, das die HA-Companion-App auf `400` laufen lässt. Ein Passwortmanager hinter einem Client, den man im Notfall (fremdes Gerät, Neuinstallation, Handyverlust) nicht installieren kann, ist ein **Aussperr-Risiko gegen den eigenen Schlüsselbund** — und damit gefährlicher als das, wovor die Maßnahme schützen soll. **Die frühere TP16-Schritt-5-Absicht „vw von bypass auf geschützt heben" ist damit gestrichen.** - **Ziel stattdessen: Schutz erhöhen, ohne Clients zu brechen** — gilt für **alle** bypass-Dienste (`vw`, `hass`, `gitea`). - ✅ **Schicht 0 — bereits vorhanden, nicht unterschätzen:** keine offenen Ports, **alles ausschließlich über CF-Tunnel** (3-Tunnel-HA, TP16), TLS terminiert an der Edge, DDoS-Schutz. Der Origin ist nicht direkt erreichbar. Das ist bereits mehr als bei den meisten Selfhostern. @@ -263,7 +263,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · - **Reihenfolge:** (1) Client-IP-Weitergabe fixen (inkl. HA `trusted_proxies`) → (2) `/admin`-Pfade path-scoped schützen → (3) App-Limits + 2FA → (4) Edge-Rules → (5) CrowdSec/fail2ban mit CF-Bouncer. - **Abh.:** TP16 ✅ (Tunnel-HA). Verwandt: **TP20.1** (vw wandert auf Platform-Postgres — DB-HA ≠ Access, getrennt halten), **TP23** (dessen WARP-Managed-Network-Idee ist durch diesen Constraint **entlastet**: `vw` verlangt keine Device-Identity, also kollidiert „WARP zuhause aus" nicht mehr mit dem Access-Strang). -### TP16.3 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) ▸ ✅ **ABGESCHLOSSEN 2026-09-01** — Flip (§5) + Abbau (§6) durch; öffentlich nur noch `bf-public` (+`fm`) — `docs/tp16.3-bf-public-ap-connector-runbook.md` +### TP16.3 · Gitea #10 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) — Flip (§5) + Abbau (§6) durch; öffentlich nur noch `bf-public` (+`fm`) — `docs/tp16.3-bf-public-ap-connector-runbook.md` #### ✅ §6 Abbau — AUSGEFÜHRT 2026-09-01 (vom User vorgezogen, „es wird nichts mehr sein") @@ -664,7 +664,7 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik TP — siehe Quick-Index Zeile TP16.3. Bezug zu **TP35** Schicht 2: Reihenfolge ist TP16.3 → TP35. TP35 wartet mit Schicht 2, bis das Fronting steht. -### TP17.1 — Shelly Gen4 raus aus dem WLAN ▸ ❌ **VERWORFEN (2026-08-23) — Weg bewiesen, Ziel damit nicht erreichbar** — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md` +### TP17.1 · Gitea #11 — Shelly Gen4 raus aus dem WLAN — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md` > ❌ **User-Entscheid 2026-08-23 nach dem Pilot: stoppen und zurückbauen.** Auslöser war Christophs > strukturelle Frage: *„zigbee sollte die antwort auf die vielen radios auf -iot geben. nutzt aber > wenig wenn man das wifi nicht abdrehen kann bzw. es nicht mehr aktivieren kann ohne reset."* @@ -816,7 +816,7 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik -### TP17.3 — Shelly-Regression nach der VLAN30-Migration ▸ ✅ **ERLEDIGT (2026-08-23, Folge-Session): beide Geräte repariert, Ursache `eco_mode` bewiesen** — offen nur noch der Flotten-Entscheid (gehört TP17.1) — aus TP17 herausgelöst, User-Meldung +### TP17.3 · Gitea #12 — Shelly-Regression nach der VLAN30-Migration — offen nur noch der Flotten-Entscheid (gehört TP17.1) — aus TP17 herausgelöst, User-Meldung > **User-Meldung 2026-08-23:** „vor einiger zeit hast du an den shellys änderungen vorgenommen. das > hatte bei einigen einen bösen seiteneffekt: **lüfter-shelly musste ich doppelt drücken**. der übelste > ist jedoch der von **diele ug: seit deinen änderungen spinnt er herum und blinkt wie ein wilder. @@ -953,7 +953,7 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au 4. Reboots der verbleibenden 13 `restart_required`-Geräte — **gefahrlos** (empirisch an `s26` belegt), deshalb kein eigener Termin, sondern Mitnahme bei TP17.1. -### TP18 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier) ▸ 🔜 KONZEPT (2026-07-12) +### TP18 · Gitea #13 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier) - **Auslöser:** 9-TB-Syno-Backup-Volume **zu 2/3 voll**; reine Daten ~3 TB, Tendenz steigend, „schlagartiger" Bedarf nicht ausgeschlossen. „Wenn skalieren, dann jetzt Syno-Nachfolger mitdenken." - **Kriterien (User-priorisiert):** (1) **keine Fremd-Abhängigkeit / kein Cloud-Lock-in** (Cloud-Opex abschreckend); (2) **Multi-Site BF/FM/DW**; (3) **HA** (Standort-Verlust überlebbar); (4) **billig/teuer trennen** — Variante **„kalt = Objektspeicher, heiß = lokal"** genügt (kein Hot/Cold *innerhalb* des Stores nötig); (5) **horizontal + vertikal skalierbar**; (6) **S3-API als Branchenstandard**, den jedes Backup-Tool spricht. - **Entscheid Engine = Garage** (Deuxfleurs, Rust): einziger Kandidat, der **geo-verteilt über offenes Internet/WAN latenztolerant** ist (CRDT-Metadaten, eventually consistent) → passt aufs 3-Keller-Modell BF/FM/DW. **Ceph verworfen** (will Sub-ms-LAN, Single-Cluster-über-WAN = Anti-Pattern; zu schwer, ~1 GB RAM/TB). **MinIO** (AGPL + Console/Feature-Abbau 2025) und **SeaweedFS** (kann Hot/Cold + Erasure-Coding, aber komplexer, Multi-Site async) = **Fallback**, falls später feingranulares Tiering / EC-Effizienz bei zweistelligen TB dominant wird. @@ -970,14 +970,14 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au - **Offen (Modell scharfstellen):** realer Strompreis; ob **FM/DW eigene Nodes stellen** oder Christoph HW hinstellt; Backup-Tool-Wahl (restic vs Kopia); ob heißes Zeug künftig auf ZFS/pve statt Syno. - Abh.: **TP10** (WG-Multi-Site = Transport, Upload-BW-Constraint), **TP14** (Container-Hosts), **TP15** (Node-HW) / **TP24** (Stromversorgung), Muster von **TP16** (reziprokes Per-Host-HA). Kein Blocker für laufende TPs. SSoT dieser Session. -### TP20.1 — Vaultwarden → Platform-Postgres + Cross-Site-HA ▸ 🔜 KONZEPT (2026-07-14, ausgelagert aus TP20) +### TP20.1 · Gitea #14 — Vaultwarden → Platform-Postgres + Cross-Site-HA — ausgelagert aus TP20) - **Ziel:** `vw` (`.20.127`, heute MariaDB) auf den Precious-Tier holen **und** das eigentliche User-Ziel erfüllen: **DB-Replikation über 2 Sites** (Standort-Verlust überlebbar). - **Phase 1 — Migration (single-site):** vw **MariaDB→Postgres** (Export → Import in Tier-1-DB `vaultwarden` → `DATABASE_URL` umstellen → verifizieren). Danach ist vw precious + im pgBackRest/PITR-Schirm von TP20. *(Alternative verworfen: vw auf MariaDB lassen + MariaDB-Repl — zugunsten EINER Replikations-/Backup-Mechanik im Precious-Tier.)* - **Phase 2 — Cross-Site-Standby:** `db-platform-standby`-LXC am 2. Standort → `pg_basebackup`/Streaming-Replication über **WG (TP10)** → **manuelles Promote-Runbook**. Repliziert den ganzen Tier-1-Cluster (vw = HA-Ziel, Nautobot bekommt Gratis-DR-Kopie). **Patroni + 3. Witness** nur, falls später Auto-Failover statt manuellem Promote gewünscht. - **Verwandt/Abgrenzung:** vw ist auch in **TP16** offen (CF-Access: `bypass`→WARP-geschützt) — **DB-HA ≠ Access**, getrennt behandeln. Reziprokes 2-Site-Muster analog **TP16**/**TP18**. - Abh.: **TP20** (Tier-1 Postgres muss stehen), **TP10** (WG-Transport für Phase-2), **TP18** (Backup-Schirm). Kein Blocker für TP21. -### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 5 WIEDER OFFEN (Redesign 2026-09-12): Self-Service deckt die DAU-Requirements nicht ab → Stufe 6 bleibt dahinter gated** +### TP23 · Gitea #15 — DNS-Policy & Self-Service-Ausnahmen > 🔴 **Redesign 2026-09-12 — Stufe 5 reopened.** Bei „Stufe 6 angehen" fiel auf: die 09-04-Stufe-5 > (single-domain, temp-only 15m/1h/4h, **eine** Ausnahme gleichzeitig, **HA-Neustart bei neuer > Domain**, `allow-domain` global statt pro Person) deckt die Nutzer-Requirements **nicht** ab @@ -1074,7 +1074,7 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au - ⚠️ **802.1X löst die IoT-Identität NICHT:** Consumer-IoT (Shelly, Sonos) hat keinen 802.1X-Supplicant; der Standardweg ist **MAB** = wieder MAC-basiert → **kein Identitätsgewinn gegenüber der DHCP-Reservierung**. **TP8 bleibt richtig, aber als Sicherheitsthema** (dynamische VLAN-Zuweisung, kein MAC-Spoofing bei Managed-Geräten), nicht als DNS-Identitätsquelle. - **Abh.:** **TP21.1 TODO 1** (DoH auf core), **TP5** (Firewall/Bypass-Sperre — Reihenfolge s.o.), **TP16 Schritt 5** (Access-Kollision), **TP16.1** (falls WARP zuhause bleiben soll), **TP8** (nur Sicherheit). **Kein Blocker für TP5 → TP6**, aber TP5 darf die Bypass-Sperre nicht vorziehen. -### TP24 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) ▸ 🔜 KONZEPT — **aus TP15 herausgelöst (User-Entscheid 2026-08-16)** — `docs/hw-refresh-requirements.md §9` +### TP24 · Gitea #16 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) — aus TP15 herausgelöst (User-Entscheid 2026-08-16)** — `docs/hw-refresh-requirements.md §9` - **Warum ein eigener TP:** Die USV hing bisher als Unterpunkt in TP15 (HW-Refresh Switching/Router) mit. TP15s Kernauftrag — CRS326-Beschaffung, Bring-up, Core-Tausch — ist seit 2026-08-08 **durch**; die Stromversorgung ist als einziger Rest übrig geblieben und hat mit Switching sachlich nichts zu tun. Sie ist ein **eigenständiges Infrastruktur-Thema mit eigener Einkaufs-, Verkabelungs- und Entscheidungslogik** (Elektrik, Batterie, Standort) und blockiert nichts auf dem kritischen Pfad. Darum eigener TP. - **Was es ist (Begriffsklärung):** eine **Pufferbatterie für die Netz-/Infrastruktur-Hardware** — **keine** klassische AC-USV mit 230-V-Steckdosen. Ein 48-V-Gleichspannungsbus versorgt die Geräte **direkt an ihrem DC-Eingang**; die Batterie hängt am selben Bus und übernimmt bei Netzausfall ohne Umschaltlücke. Der Umweg AC→DC→AC→DC (und damit dessen Dauerverlust) entfällt. - **Zielbild (Aufbau, unverändert gültig aus der TP15-Recherche):** @@ -1102,7 +1102,7 @@ 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 ▸ ✅ **FERTIG (2026-09-15)** → [Archiv](teilprojekte-archiv.md#tp25) +### TP25 · Gitea #17 — WLAN-Stabilisierung — → [Archiv](teilprojekte-archiv.md#tp25) > **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, @@ -1110,7 +1110,7 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au -### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **ap3 LIVE (2026-08-30)** — wieder geöffnet und durchgezogen +### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core` — wieder geöffnet und durchgezogen > **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist > unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30, @@ -1325,7 +1325,7 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört der Port ist untagged-Member von VLAN40 bei `pvid=1`. -### TP29 — Anwesenheitserkennung fürs Energiesparen ▸ 💡 **IDEE, noch nicht auf dem kritischen Pfad** (angelegt 2026-08-21) +### TP29 · Gitea #19 — Anwesenheitserkennung fürs Energiesparen > **User-Formulierung (2026-08-21):** „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So > kommen wir dem Ziel der Energieersparnis ohne Komfortverlust groß näher." @@ -1351,7 +1351,7 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört dahin kommt. **Kein Blocker für irgendetwas.** -### TP30 — Credentials: neu setzen & rotieren (Sammelbecken) ▸ 🔜 **ANGELEGT 2026-08-24, bewusst GEBÜNDELT gegen Projektende** +### TP30 · Gitea #20 — Credentials: neu setzen & rotieren (Sammelbecken) > **User-Entscheid 2026-08-24:** „ich würde gerne daraus einen eigenen punkt machen. ich bin mir > sicher, dass es noch öfters passwörter zu rotieren geben wird. das machen wir dann alles gegen > projektende." — nachgeschärft am selben Tag: **„tp30 wird unser sammelbecken für credentials, @@ -1637,7 +1637,7 @@ unabhängig vom gebündelten Durchgang.** -### TP31 — Reverse Proxy für innen und außen; cloudflared von sechs Knoten auf einen ▸ ✅ **ABGESCHLOSSEN 2026-09-01** +### TP31 · Gitea #21 — Reverse Proxy für innen und außen; cloudflared von sechs Knoten auf einen > ✅ **Durch — Schritt 5+6 sind über den Piloten [TP16.3](#tp163) miterledigt (2026-09-01):** `bf-public` > **healthy, 12 Connectoren** auf ap1/ap2/ap3; `vw`/`hass`/`n8n`/`gitea` laufen über CF → **Caddy** > (`https://10.40.20.40:443`), die drei Alttunnel `bf-nas`/`bf-pve`/`bf-mm` **gelöscht**. Damit steht das @@ -1887,7 +1887,7 @@ diesmal mit Datenverlust statt 530ern. **Automatisches Ingress-Failover erst, we - 📋 **Vollständiger Plan mit allen Abwägungen:** `~/.claude/plans/expressive-pondering-clarke.md` -### TP32 — Nummern- und Adress-Systematik ▸ 🟢 **KONZEPT STEHT (2026-08-25), Startreihenfolge bereits repariert** +### TP32 · Gitea #22 — Nummern- und Adress-Systematik > **User-Auftrag 2026-08-24:** „du/ich sollst in der lage sein, selbstständig nach einem vorgegebenen > system freie ips vergeben zu können … und diese systematik findet sich in anderen netzen künftig auch > wieder" · „ich meine 100 sollte künftig für die core services stehen." @@ -1998,7 +1998,7 @@ gewachsen, nicht geplant. Merkregel: **„Bestand bleibt, wo er ist" gilt nur, s wo er ist, ohne nachzusehen.** -### TP33 — Sonos aus der Ferne bedienen (VPN/WARP) ▸ 💡 **IDEE, bewusst zurückgestellt** (angelegt 2026-08-25, User-Entscheid „später entscheiden") +### TP33 · Gitea #23 — Sonos aus der Ferne bedienen (VPN/WARP) — angelegt 2026-08-25, User-Entscheid „später entscheiden") - **Auslöser:** beim Sonos-Fall vom 08-25 kam die Frage auf, ob die Player auch **von unterwegs** auffindbar sein sollen. Antwort für jetzt: **nein, später entscheiden** — hier nur festgehalten, @@ -2017,7 +2017,7 @@ wo er ist, ohne nachzusehen.** --- -### TP35 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard ▸ ✅ **ABGESCHLOSSEN 2026-09-12** — 5/6 Schichten durch (MCP-Schichtung FQDN→IP, Credentials-`{env:}`-Flip angewandt+verifiziert, globale CLAUDE.md-Schicht, `/news`, Backup-Guard, `/projektstatus`-Skill). Schicht-3-**pro-Projekt**-Rollout laut Abgrenzung eigene Session je Projekt (kein Blocker). Einziger Rest = On-Network-mikromcp-Smoketest (off-site nicht machbar, VLAN99:80 timeout, non-blocking) → als Merkposten nach TP30. Gitea #25 closed/`status:done`. +### TP35 · Gitea #25 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard — 5/6 Schichten durch (MCP-Schichtung FQDN→IP, Credentials-`{env:}`-Flip angewandt+verifiziert, globale CLAUDE.md-Schicht, `/news`, Backup-Guard, `/projektstatus`-Skill). Schicht-3-**pro-Projekt**-Rollout laut Abgrenzung eigene Session je Projekt (kein Blocker). Einziger Rest = On-Network-mikromcp-Smoketest (off-site nicht machbar, VLAN99:80 timeout, non-blocking) → als Merkposten nach TP30. Gitea #25 closed/`status:done`. - **Auslöser:** Bestandsaufnahme Session 2026-08-28 (Handoff `docs/handoff-20260828-pre-opencode-migration.md`). Erkenntnis: MCP-Configs sind heute über `~/.config/opencode/opencode.jsonc` UND `~/.claude.json` verstreut, ohne klares Schema. - **Befund (geprüft):** 11/12 Backups im Verzeichnis `~/dev/.pre-opencode-migration-backups/20260828-150335/` gzip-valid; `uwe-job-bewerbung.tar.gz` korrupt (Source unverändert da). Repos und globale Configs md5-/HEAD-identisch zum Vor-Session-Stand. Synology-Drive-Sync aktiv (Anwender, manuell). - **Scope:** @@ -2034,7 +2034,7 @@ wo er ist, ohne nachzusehen.** --- -### TP36 — Index/Status als SSoT nach Gitea (Steuerung aus der branchbaren Datei herauslösen) ▸ ⏳ **CUTOVER 2026-09-07** — Index/Status = Gitea-Issues, Prosa bleibt Datei +### TP36 · Gitea #26 — Index/Status als SSoT nach Gitea (Steuerung aus der branchbaren Datei herauslösen) — Index/Status = Gitea-Issues, Prosa bleibt Datei - **Auslöser:** Skill-Run 2026-08-28 (`docs/handoff-20260828-session-a-projektstatus.md`) lieferte einen überholten „Nächster Schritt"-Output; **manuell gepflegte Doku mit Live-Daten verliert gegen die Realität, sobald sie lang wird.** Verschärft 2026-09-06: `docs/teilprojekte.md` wurde in **40/40** letzten Commits angefasst — jeder TP editierte Quick-Index **und** Ist-Stand-Block → **Merge-Konflikt-Magnet**, der Branch-pro-TP verhinderte (ein Parallel-Branch hatte so die TP37-Doppelvergabe verursacht). - 🔄 **Reframe 2026-09-06 (Christoph):** von „SSoT via **Nautobot**" auf „SSoT via **Gitea**". Grund: Index+Status sind **kuratierte Steuerung** (Reihenfolge, Marker, Abhängigkeiten), kein Geräte-Live-Zustand — die gehören auf ein **Issue-Board**, nicht in Custom Fields. **Nautobot bleibt nur für Live-Gerätedaten** relevant (Compliance-Zahl, RouterOS-Version …) → als optionaler Rest unten, entkoppelt von diesem TP. - **Modell (umgesetzt):** TP = **Gitea-Issue** in [`admin/bf-infra`](https://gitea.emsenhuber.net/admin/bf-infra/issues) · Status = Label `status:*` (todo/wip/paused/idea/concept/done/dropped) · Gruppierung = Label `thema:*`. Seeder `bin/gitea-tp-sync.py` (idempotent, Dry-Run default, `--commit` schreibt; Token aus `~/.hermes/.env`, **nie ausgeben**). Erstlauf: **68 Issues (25 open/43 closed) + 15 Labels**. @@ -2047,7 +2047,7 @@ wo er ist, ohne nachzusehen.** - **Verzahnt mit:** TP35 (SSoT + Konsolidierung), TP44 (native Nautobot-Modelle), `bf-infra/CLAUDE.md`-Lessons. Plan: `~/.claude/plans/geht-das-los-auch-reflective-lark.md`. -### TP38 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch ▸ 🔜 **ANGELEGT 2026-08-31** +### TP38 · Gitea #28 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch *(Nummer 2026-08-31 von 37 auf 38 korrigiert und von Christoph bestätigt: TP37 war zeitgleich in einem zweiten Branch für „Nautobot als Netzwerkdoku" vergeben worden.)* @@ -2123,7 +2123,7 @@ folgenden Absicherungen dazu, und die Entscheidung darüber in die Umsetzungs-Se --- -### TP40 — Home-Assistant-Recorder-Retention: die 41-GB-`states`-Tabelle beschneiden ▸ 🔜 **ANGELEGT 2026-09-01 (User-Okay erteilt)** +### TP40 · Gitea #29 — Home-Assistant-Recorder-Retention: die 41-GB-`states`-Tabelle beschneiden **Auslöser:** LXC 302 (MariaDB) lief am 2026-09-01 auf **100 %** voll. Sofortmassnahme war `+30G` (TP39 Punkt 3), aber 59 der 65 GB sind die **`homeassistant`-Datenbank**, und die wächst weiter — @@ -2164,7 +2164,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz - 🔴 **Kein eigenmächtiger Datenlöschvorgang** — `purge` wirft echte Historie weg; die Aufbewahrungsdauer ist eine Nutzerentscheidung. Diese Session hat **nur** vergrössert und gemessen. -### TP37 — Nautobot als durchgehende Netzwerkdoku: Hosts/Container/Dienste, erster Anwendungsfall CF-Connectoren ▸ 🔜 ANGELEGT 2026-08-30 (User-Okay erteilt) +### TP37 · Gitea #27 — Nautobot als durchgehende Netzwerkdoku: Hosts/Container/Dienste, erster Anwendungsfall CF-Connectoren > **Christophs Formulierung (2026-08-30):** *„können wir das setup mit den cf-connectoren dann auch in > nautobot abbilden. quasi eine durchgehende netzwerkdoku stück für stück. muss eh fast sein, da du auf > den mikrotiks ja nur via gc/nautobot deployst."* @@ -2226,7 +2226,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz --- -### TP34.2 — Interne DNS-Namen als Daten (FQDN aus Nautobot) ▸ 🔜 **ANGELEGT 2026-09-22** (Gitea #73) — **gemeinsame Arbeit** +### TP34.2 · Gitea #73 — Interne DNS-Namen als Daten (FQDN aus Nautobot) — **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: @@ -2254,7 +2254,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz --- -### TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot ▸ 🔜 **ANGELEGT 2026-09-21** (Gitea #71) +### TP45 · Gitea #71 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot > **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: > *„wenn wir schon reverse proxy & co korrigieren müssen, wäre es vl jetzt ein guter zeitpunkt, @@ -2479,7 +2479,7 @@ NAT/mangle/WG sind Template-Literale, der Firewall-Filter kommt als ConfigContex 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. -### TP47 — Core egress via cf-mesh und WARP-Exit-Validierung ▸ **ANGELEGT 2026-09-27** +### TP47 · Gitea #74 — Core egress via cf-mesh und WARP-Exit-Validierung > **Christoph:** Validierung des Core-Egress-Weges via Cloudflare Mesh. Ausgangsprüfung gegen neutralen Host > und Ziel `164.90.191.7` (DigitalOcean). Korrektur früherer Behauptung: **WARP war am Mac gar nicht > verbunden** — `warp=off`, `ip=90.146.33.177` (Heim-IP). Die zuvor gemessene `104.28.207.39` stammte aus @@ -2711,7 +2711,7 @@ TP22 · TP-R · **TP3.2** (08-17) · **TP5** (08-18) · **TP7.1** (08-18) · **T -### TP43 — dw-Standort: interne BF-DNS-Namen über WG auflösbar machen ▸ ✅ **UMGESETZT 2026-09-11** — dw-FritzBox: Upstream `.36`+`.37`, DNS-Rebind-Ausnahme `bf.emsenhuber.net`, Fallback-Haken „öffentliche NS bei Unreachbarkeit" **gesetzt** (Ideal-Variante: `.37`-Redundanz bleibt, BF-down → public). (verworfene 420-Historie am Ende dieses Abschnitts) +### TP43 · Gitea #32 — dw-Standort: interne BF-DNS-Namen über WG auflösbar machen — dw-FritzBox: Upstream `.36`+`.37`, DNS-Rebind-Ausnahme `bf.emsenhuber.net`, Fallback-Haken „öffentliche NS bei Unreachbarkeit" **gesetzt** (Ideal-Variante: `.37`-Redundanz bleibt, BF-down → public). (verworfene 420-Historie am Ende dieses Abschnitts) **Live-Auslöser (2026-09-11):** Christoph am dw-Standort, WG-Tunnel zu BF up, kann `portainer.bf.emsenhuber.net` nicht erreichen. Direkte IP `https://10.40.20.126:9443` geht ⇒ **reines DNS-, kein Routing-Problem.** Der