From b697ffc6fa191579ac5de411d0c8c1531387aa6a Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Sun, 30 Aug 2026 21:07:17 +0200 Subject: [PATCH 1/5] TP16.3/TP31 Schritt 5: Vor-Ort-Runbook fuer bf-public-Connector (ap1/ap2/ap3) Runbook docs/tp16.3-bf-public-ap-connector-runbook.md fuer die Vor-Ort-Session geschrieben; docs/tp31-schritt5-runbook.md abgeloest (read-only-Header). Live-Befunde 2026-08-30, die den Plan aendern: - ap3 ist neu und NICHT baugleich: cAP ax (cAPGi-5HaxD2HaxD, 10.40.99.13), arm64/96 MB frei, aber ROS 7.24.1 gegen 7.24 auf ap1/ap2 => zwei verschiedene container.npk. device-mode=basic statt home (fetch bereits an). - Blocker A: bf-public-Ingress zeigt auf :80, Caddy antwortet dort mit 308 auf https => Redirect-Schleife ueber CF fuer alle vier Namen. Faellt heute nicht auf, weil die Alttunnel an Caddy vorbei direkt aufs Backend gehen. Fix: https://10.40.20.40:443 + originServerName je Regel (ueber 443 4x 200). - Blocker B: es gibt KEINE Freigabe vlan99 -> vlan20:443. Die forward-Kette wurde vollstaendig gelesen (36 Regeln); die einzige MGMT->INFRA-Regel ist ein src-port=8728-Rueckweg. Der Vorgaenger-Runbook behauptet das Gegenteil ("Regel 27 deckt vlan99->vlan20:80 ab") - Regel 27 ist IOT->INFRA. - Blocker C: ROUTER_AP3_PASS fehlt in ~/.secrets/credentials.env (nur in ~/.claude.json). Nebenbefund: credentials.env laesst sich in zsh nicht sourcen (rohe WireGuard-Zeilen ab ~Z.69) - Variablen bleiben still leer. - Kein PoE an beiden CRS326 => kein Remote-Power-Cycle, Bestaetigung physisch. - Die sechs abzubauenden Container auf allen drei Hosts verifiziert; terpene-cloudflared und fm.emsenhuber.net bleiben. CF-Rollback-Snapshots unter docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json neu angelegt (vier Ingress-Configs + CNAMEs inkl. Record-IDs) - es gab sie vorher nicht. Quick-Index, TP16.3-Abschnitt und Ist-Stand-Block mitgezogen. Co-Authored-By: Claude Opus 5 --- ...m-ingress-20260830-vor-tp16.3-schnitt.json | 20 + ...s-ingress-20260830-vor-tp16.3-schnitt.json | 24 + ...c-ingress-20260830-vor-tp16.3-schnitt.json | 44 ++ ...e-ingress-20260830-vor-tp16.3-schnitt.json | 20 + ...es-tunnel-20260830-vor-tp16.3-schnitt.json | 44 ++ docs/teilprojekte.md | 74 ++- docs/tp16.3-bf-public-ap-connector-runbook.md | 574 ++++++++++++++++++ docs/tp31-schritt5-runbook.md | 16 + 8 files changed, 810 insertions(+), 6 deletions(-) create mode 100644 docs/cf-backup/bf-mm-ingress-20260830-vor-tp16.3-schnitt.json create mode 100644 docs/cf-backup/bf-nas-ingress-20260830-vor-tp16.3-schnitt.json create mode 100644 docs/cf-backup/bf-public-ingress-20260830-vor-tp16.3-schnitt.json create mode 100644 docs/cf-backup/bf-pve-ingress-20260830-vor-tp16.3-schnitt.json create mode 100644 docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json create mode 100644 docs/tp16.3-bf-public-ap-connector-runbook.md diff --git a/docs/cf-backup/bf-mm-ingress-20260830-vor-tp16.3-schnitt.json b/docs/cf-backup/bf-mm-ingress-20260830-vor-tp16.3-schnitt.json new file mode 100644 index 0000000..54b9b9e --- /dev/null +++ b/docs/cf-backup/bf-mm-ingress-20260830-vor-tp16.3-schnitt.json @@ -0,0 +1,20 @@ +{ + "tunnel_id": "7a322261-380a-447b-97d2-8ebce99f5ed1", + "version": 6, + "config": { + "ingress": [ + { + "service": "http://10.40.20.167:5678", + "hostname": "n8n.emsenhuber.net" + }, + { + "service": "http_status:404" + } + ], + "warp-routing": { + "enabled": false + } + }, + "source": "cloudflare", + "created_at": "2026-08-30T10:19:30.297725Z" +} diff --git a/docs/cf-backup/bf-nas-ingress-20260830-vor-tp16.3-schnitt.json b/docs/cf-backup/bf-nas-ingress-20260830-vor-tp16.3-schnitt.json new file mode 100644 index 0000000..f339d01 --- /dev/null +++ b/docs/cf-backup/bf-nas-ingress-20260830-vor-tp16.3-schnitt.json @@ -0,0 +1,24 @@ +{ + "tunnel_id": "220c9958-401b-4111-9451-f3db09bba2d5", + "version": 7, + "config": { + "ingress": [ + { + "service": "http://10.40.20.157:3000", + "hostname": "gitea.emsenhuber.net" + }, + { + "service": "http://10.40.20.127", + "hostname": "vw.emsenhuber.net" + }, + { + "service": "http_status:404" + } + ], + "warp-routing": { + "enabled": true + } + }, + "source": "cloudflare", + "created_at": "2026-08-30T08:50:21.241084Z" +} diff --git a/docs/cf-backup/bf-public-ingress-20260830-vor-tp16.3-schnitt.json b/docs/cf-backup/bf-public-ingress-20260830-vor-tp16.3-schnitt.json new file mode 100644 index 0000000..872c457 --- /dev/null +++ b/docs/cf-backup/bf-public-ingress-20260830-vor-tp16.3-schnitt.json @@ -0,0 +1,44 @@ +{ + "tunnel_id": "48d3b0b6-7233-4781-a76a-1de4a5e957ce", + "version": 2, + "config": { + "ingress": [ + { + "service": "http://10.40.20.40:80", + "hostname": "vw.emsenhuber.net", + "originRequest": { + "noTLSVerify": true + } + }, + { + "service": "http://10.40.20.40:80", + "hostname": "hass.emsenhuber.net", + "originRequest": { + "noTLSVerify": true + } + }, + { + "service": "http://10.40.20.40:80", + "hostname": "n8n.emsenhuber.net", + "originRequest": { + "noTLSVerify": true + } + }, + { + "service": "http://10.40.20.40:80", + "hostname": "gitea.emsenhuber.net", + "originRequest": { + "noTLSVerify": true + } + }, + { + "service": "http_status:404" + } + ], + "warp-routing": { + "enabled": false + } + }, + "source": "cloudflare", + "created_at": "2026-08-27T05:45:54.534390Z" +} diff --git a/docs/cf-backup/bf-pve-ingress-20260830-vor-tp16.3-schnitt.json b/docs/cf-backup/bf-pve-ingress-20260830-vor-tp16.3-schnitt.json new file mode 100644 index 0000000..f4a0232 --- /dev/null +++ b/docs/cf-backup/bf-pve-ingress-20260830-vor-tp16.3-schnitt.json @@ -0,0 +1,20 @@ +{ + "tunnel_id": "f7fdf3a6-b67d-4567-93b1-b0f273794d09", + "version": 4, + "config": { + "ingress": [ + { + "service": "http://10.40.20.105:8123", + "hostname": "hass.emsenhuber.net" + }, + { + "service": "http_status:404" + } + ], + "warp-routing": { + "enabled": true + } + }, + "source": "cloudflare", + "created_at": "2026-08-30T08:50:25.151983Z" +} diff --git a/docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json b/docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json new file mode 100644 index 0000000..d512b66 --- /dev/null +++ b/docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json @@ -0,0 +1,44 @@ +[ + { + "name": "ds-fm.emsenhuber.net", + "type": "CNAME", + "content": "b697d124-53bf-4a33-8fcf-384cced4db1a.cfargotunnel.com", + "proxied": true, + "id": "0da61c6143bfd0d5f9c47ca4b4147b06" + }, + { + "name": "gateway-fm.emsenhuber.net", + "type": "CNAME", + "content": "b697d124-53bf-4a33-8fcf-384cced4db1a.cfargotunnel.com", + "proxied": true, + "id": "54ac8b816765b45191469678891389bd" + }, + { + "name": "gitea.emsenhuber.net", + "type": "CNAME", + "content": "220c9958-401b-4111-9451-f3db09bba2d5.cfargotunnel.com", + "proxied": true, + "id": "3d6053529f99fc5832d9956ee6e8834c" + }, + { + "name": "hass.emsenhuber.net", + "type": "CNAME", + "content": "f7fdf3a6-b67d-4567-93b1-b0f273794d09.cfargotunnel.com", + "proxied": true, + "id": "0841148f401b5db40fa02f69d0f0e848" + }, + { + "name": "n8n.emsenhuber.net", + "type": "CNAME", + "content": "7a322261-380a-447b-97d2-8ebce99f5ed1.cfargotunnel.com", + "proxied": true, + "id": "40b311a40e41fbeae85ca097a4036e6c" + }, + { + "name": "vw.emsenhuber.net", + "type": "CNAME", + "content": "220c9958-401b-4111-9451-f3db09bba2d5.cfargotunnel.com", + "proxied": true, + "id": "2971d3ba9410e841b35ec58ffda59be4" + } +] diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index dca4975..5d650e1 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -33,10 +33,20 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` · liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)* - 🆕 **MCP-Bestand 2026-08-28 korrigiert:** **9 MCPs identisch** in `~/.claude.json` und `~/.config/opencode/opencode.jsonc` (vorher Asymmetrie 7 vs. 11 — `portainer-mcp`/`shelly-mcp` raus, `gitea-mcp`/`n8n-mcp` rein). **Mac mini = n8n-Host, NICHT zentraler MCP-Host** (Performance-Restriktion); zentraler MCP-Host = **DS923** (`nas-bf`, Portainer Endpoint 14). Inventur-Stand `mcp/INVENTORY.md` + Policy `docs/mcp-host-policy.md`. - 🆕 **MCP-Migration FQDN→IP (2026-08-29, TP35 Schicht 1):** `gitea-mcp` und `n8n-mcp` in `~/.config/opencode/opencode.jsonc` von CF-FQDN auf LAN-IP umgestellt — `gitea-mcp` → `http://10.40.20.167:3000/mcp` (macmini, **ohne** Bearer, intern), `n8n-mcp` → `http://10.40.20.167:3001/mcp` (mit `Bearer {env:N8N_MCP_BEARER}`). **Befund dabei:** der Handoff 1.2 nannte für gitea-mcp die falsche IP `.157:3000` (das ist die Gitea-Web-UI, `400 Invalid CSRF`); der echte gitea-mcp läuft auf `.167:3000` (`serverInfo: gitea-mcp 0.0.10`). `cloudflare`-MCP bleibt auf dem offiziellen FQDN (User-Direktive). +- 🆕 **TP16.3/TP31 Schritt 5 vorbereitet (2026-08-30):** Runbook für die Vor-Ort-Session geschrieben + ([`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)), CF-Rollback-Snapshots + unter `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json` **neu angelegt** (es gab sie vorher nicht). + `docs/tp31-schritt5-runbook.md` ist damit **abgelöst** (read-only-Header): es kannte ap3 nicht, + unterstellte einen Registry-Pull (`fetch: no` auf ap1/ap2) und enthielt eine **falsche** + Firewall-Freigabe-Behauptung. **CF-Stand:** `bf-public` `inactive` / 0 Connectoren; die vier + öffentlichen Namen laufen noch über bf-mm/bf-nas/bf-pve **an Caddy vorbei** direkt aufs Backend. - 🆕 **Globale Verhaltensregeln konsolidiert (2026-08-29, TP35 Schicht 3):** `~/.config/opencode/AGENTS.md` ist jetzt die **SSoT** für globale Verhaltensregeln (Secrets, VLAN-Schema, 5 Lessons, SoC, Übergabe-Mechanik, Mid-Action-Checkpoint, Commit-Hinweis, Backup-Guard). `~/.claude/CLAUDE.md` hält denselben Block als Claude-Code-Spiegelung mit Verweis auf die SSoT. opencode liest AGENTS.md mit Vorrang vor `~/.claude/CLAUDE.md`. -- **Alle vier MikroTik laufen auf RouterOS 7.24** (stable, 2026-08-17 von Christoph selbst gehoben, TP27), - **RouterBOOT ebenfalls 7.24** auf allen vieren (`current-firmware` = `upgrade-firmware`, geprüft 08-18). +- **Fünf MikroTik seit 2026-08-30** — neu dazu **`ap3`** (cAP ax `cAPGi-5HaxD2HaxD`, **`10.40.99.13`**, + Uplink core `ether21`, in der MikroMCP-Registry). ⚠️ **ap3 läuft `7.24.1`, die anderen vier `7.24`** + (live gelesen 08-30) — für versionsgebundene Pakete (`container.npk`) sind das **zwei** Dateien, + nicht eine. RouterBOOT jeweils = `upgrade-firmware`. + *(Hier stand bis 08-29 „Alle vier MikroTik laufen auf RouterOS 7.24" — mit ap3 überholt.)* - **L3 + WAN-Edge = `crs326-core`** (CRS326-24G-2S+, Technik UG): Routing, DHCP (7 Server, 54 Reservierungen), DNS, NAT, **NTP-Server für den Standort** und der WG-Hub (TP10, Peers `fm`/`dw`) laufen hier. Mgmt-SVI **`10.40.99.1`** (vlan99), Gateway `.1` je VLAN. **WAN = `vlan100`** — DHCP-Client @@ -67,7 +77,13 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d **16 Sites** (4 public, 12 intern mit `.bf.`-Suffix), Zertifikate ausschliesslich per **DNS-01** mit einem eng geschnittenen `Zone:DNS:Edit`-Token (`CF_DNS_API_TOKEN`, **nicht** das account-weite). Abnahme über den Host-Header von einem zweiten Host: 11× 200, `nautobot` 302. - Offen: Schritt 5–6 (Tunnel `bf-public`, Abbau). + Offen: **Schritt 5–6** (Connector für `bf-public`, CNAME-Flip, Abbau der sechs Container) — + **Runbook steht: [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)**, + Vor-Ort-Session an ap1/ap2/ap3. 🔴 **Zwei Blocker davor, beide remote lösbar** (08-30 gemessen): + **(a)** der `bf-public`-Ingress zeigt auf `:80`, wo Caddy mit **308 → https** antwortet ⇒ Redirect-Schleife + über Cloudflare; Fix = `https://10.40.20.40:443` + `originServerName` je Regel (über 443 mit korrekter + SNI liefern alle vier Namen 200). **(b)** es gibt **keine** Freigabe `vlan99 → vlan20:443` — der + Connector liefe in `TP5-WOULD-DROP`. - **DNS/Resolver = AdGuard-Paar `agh-bf1`/`agh-bf2`** (LXC 110/111 auf pve, **`10.40.20.36`/`.37`**, TP23 Stufe 2 + TP31 Schritt 4 ✅ seit 2026-08-26): Upstream DoH zum CF-Gateway, conditional `[/bf.emsenhuber.net/] → 10.40.20.1`, 47 Rewrites (16 Service → Caddy `.20.40` + 10 Host-Namen @@ -101,6 +117,11 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d > - 🔜 **Session B** — shelly-mcp-Container auf DS923 (`10.40.20.10`) deployen (live, ssh). Repo-Materialien committed (`mcp/shelly/{Dockerfile,BUILD.md,requirements.txt,.gitignore}`); `.env` und Tunnel-Secret lokal — kein MCP-Client (User-Direktive). > - 🔜 **Session C** — `caddy/cf-sync.py`-Drift-Check + Backup-Guard (`~/dev/.bin/backup-guard.sh`, TP35 Schicht 5). > +> 🆕 **Dazu seit 2026-08-30: TP16.3/TP31 Schritt 5 = Vor-Ort-Session** an ap1/ap2/**ap3** +> ([Runbook](tp16.3-bf-public-ap-connector-runbook.md)). **Reihenfolge beachten:** die drei Blocker in +> §1 sind **remote** zu erledigen, *bevor* jemand in den Keller geht — sonst steht man vor einem +> Connector, der `healthy` meldet, während die Seite eine Redirect-Schleife dreht. +> > 🆕 **Strukturelle Folge = TP36:** SSoT für `teilprojekte.md`-Live-Daten via Nautobot (eigene Session, nicht heute). Befund 2026-08-28: manuell gepflegte Datei verliert gegen die Realität, sobald sie lang wird. Quickref-Index, TP-Abschnitt und Ist-Stand-Block müssen bei jedem Edit mitgezogen werden — keine schlägt die andere. **Merkregel im Lessons-Block.** > 🆕 **Neu am 2026-08-24/25:** [TP31](#tp31) löst **TP16** und **TP16.2** ab und dampft cloudflared @@ -200,7 +221,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP14.4 | 🔜 | CF-Access-Token | — | > | TP16 | ✅ | CF-Tunnel HA wieder voll | 2026-08-24 | > | TP16.2 | 🔜 | Härtung bypass-Dienste | — | -> | TP16.3 | ⏳ | cloudflared-Reduktion (Probe läuft) | — | +> | TP16.3 | 🔜 | `bf-public`-Connector auf ap1/ap2/**ap3** (Runbook steht) | — | > | TP17.1 | ❌ | Shelly Gen4 → Zigbee | 2026-08-23 | > | TP17.3 | ✅ | Shelly-Regression behoben | 2026-08-23 | > | TP18 | 🔜 | Garage-Objektspeicher | — | @@ -210,7 +231,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP25 | ✅ | WLAN-Stabilisierung (Kern erledigt) | 2026-08-24 | > | TP29 | 💡 | Anwesenheitserkennung (Idee) | — | > | TP30 | 🔜 | Credentials (Etappe 1 ✅, Rest gebündelt) | — | -> | TP31 | ✅ | Reverse Proxy Caddy (Schritt 1–4 ✅) | — | +> | TP31 | ⏳ | Reverse Proxy Caddy (1–4 ✅, 5–6 = Vor-Ort-Session) | — | > | TP32 | 🟢 | Nummern-Systematik (Konzept) | — | > | TP33 | 💡 | Sonos fern (Idee, zurückgestellt) | — | > | TP34 | 🔜 | Caddyfile via Nautobot/GC (Stub, Inventur-Handoff 2026-08-27) | — | @@ -408,7 +429,48 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na - **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) ▸ ⏳ **PROBE LÄUFT SEIT 2026-08-24** — `docs/tp16.3-nautobot-mesh-only-runbook.md §8` +### TP16.3 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) ▸ 🔜 **UMSETZUNG VORBEREITET 2026-08-30** (Probe seit 2026-08-24 ✅) — `docs/tp16.3-nautobot-mesh-only-runbook.md §8` + +#### 🔜 Follow-up F2 — `bf-public`-Connector auf die APs (Runbook 2026-08-30) +**Runbook: [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)** +— Vor-Ort-Session an **ap1 + ap3 (Technik UG)** und **ap2 (Technik OG)**, weil der +`device-mode`-Wechsel eine **physische** Bestätigung am Gerät verlangt. + +- 🆕 **ap3 ist neu und nicht baugleich** (live 08-30): **cAP ax** (`cAPGi-5HaxD2HaxD`, `10.40.99.13`), + arm64, 96 MB Flash frei, 689 MB RAM frei ⇒ Container passt. **Aber: ROS `7.24.1` gegen `7.24`** auf + ap1/ap2 ⇒ **zwei verschiedene `container.npk`**. Und ap3 steht auf `device-mode=basic` (nicht `home`), + dort ist `fetch` bereits erlaubt — wir nutzen trotzdem auf allen dreien den `.tar`-Import, damit alle + dasselbe Image fahren (gepinnt: `cloudflared 2026.6.1`, Digest `sha256:6d91c121…`, 27,5 MB). +- 🔴 **Blocker A — Redirect-Schleife.** `bf-public` zeigt auf `http://10.40.20.40:80`; Caddy antwortet + dort mit **308 → `https:///`**, also zurück durch Cloudflare in denselben Tunnel ⇒ + `ERR_TOO_MANY_REDIRECTS` für alle vier Namen. **Fällt heute nicht auf**, weil die drei Alttunnel + **an Caddy vorbei** direkt aufs Backend gehen — die Schleife entsteht erst mit dem Umstieg. + Fix = `https://10.40.20.40:443` + `originServerName` je Regel (über 443 mit korrekter SNI: 4× 200). +- 🔴 **Blocker B — die Freigabe existiert nicht.** Die `forward`-Kette des Core wurde vollständig + gelesen (36 Regeln, Nr. 35 = `TP5: deny-by-default`): **keine** Regel `vlan99 → vlan20` für 80/443. + Die einzige MGMT→INFRA-Regel (Nr. 26) ist ein `src-port=8728`-**Rückweg** der API. + ⚠️ **Der Vorgänger-Runbook behauptet das Gegenteil** („Regel 27 deckt `vlan99→vlan20:80` ab ✅") — + Regel 27 ist `IOT->INFRA http-rueckweg`, `in-interface=vlan30`. Ohne neue Regel läuft der Connector + in `TP5-WOULD-DROP` + Drop. Egress zu CF (Regel 13 `LAN->WAN`) und DNS (Regeln 30–33) sind dagegen ✅. + ⇒ Das `masquerade` des `veth`-Netzes ist deshalb **Pflicht**, nicht Kosmetik: sonst kommen die Pakete + mit `172.17.0.2` am Core an, wo weder Route noch Regel sie kennt. +- 🟡 **Blocker C — `ROUTER_AP3_PASS` fehlt in `~/.secrets/credentials.env`** (steht nur in + `~/.claude.json`), gegen die Credential-Store-Konvention. Der `sshpass -e`-Reboot-Weg braucht es dort. + ⚠️ Nebenbefund: **`credentials.env` lässt sich in zsh nicht sourcen** — ab ~Zeile 69 stehen rohe + WireGuard-Zeilen, zsh bricht ab und lässt die Variablen **leer**; das sieht aus wie ein ungültiger + Token. Alle Snippets im Runbook laufen deshalb in `bash -c`. +- 🔩 **Kein PoE als Abkürzung:** beide CRS326 haben nachweislich gar kein PoE-Menü + (`bad command name poe`) ⇒ es gibt **keinen** Remote-Power-Cycle, die Bestätigung ist wirklich physisch. +- 🧹 **Abbau (frühestens 7 Tage nach dem Flip):** Tunnel `bf-mm`/`bf-nas`/`bf-pve` löschen und die + **sechs** Container entfernen — LXC 400 (`cloudflared-bfmm`, `-bfnas`), macmini (`-bfpve`, `-bfnas`), + NAS ep14 (`-bfpve`, `-bfmm`); alle drei Hosts 08-30 verifiziert. **Erst Container, dann Tunnel** — + umgekehrt erzeugt man 530er. 🚫 **`terpene-cloudflared` (LXC 400) und `fm.emsenhuber.net` bleiben.** +- 🔴 **Ehrliche Einschränkung (aus TP31, gilt unverändert):** solange Caddy nur auf `caddy-bf1` liegt, + ist das Origin aller drei Connectoren **derselbe Rechner** — drei Connectoren kaufen + **Hardware-Diversität am Eingang, aber keine Verfügbarkeit**. Der echte Gewinn kommt mit `caddy-bf2` + (LXC 131); für `vw` gilt dabei die TP20.1-Bremse (Schreibzugriffe auf einen read-only Standby). +- **Rollback:** `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json` (vier Ingress-Configs + CNAMEs + inkl. Record-IDs), am 2026-08-30 **neu angelegt**. - ▶️ **Schnitt ausgeführt 2026-08-24:** `nautobot.emsenhuber.net` ist aus dem Ingress von `bf-nas` entfernt (**8 → 7 Regeln**, Catch-all weiterhin letzte). Rollback-Sicherung liegt unter `docs/cf-backup/bf-nas-ingress-20260824-vor-tp16.3.json` — Rückweg ist ein `PUT`, eine Minute. diff --git a/docs/tp16.3-bf-public-ap-connector-runbook.md b/docs/tp16.3-bf-public-ap-connector-runbook.md new file mode 100644 index 0000000..6672271 --- /dev/null +++ b/docs/tp16.3-bf-public-ap-connector-runbook.md @@ -0,0 +1,574 @@ +# TP16.3 / TP31 Schritt 5 — Runbook: `bf-public`-Connector auf ap1 / ap2 / **ap3**, CNAME-Flip, Abbau + +> **Stand 2026-08-30, live gelesen. Vorbereitet, NICHT ausgeführt.** +> **Löst [`tp31-schritt5-runbook.md`](tp31-schritt5-runbook.md) ab** (Stand 08-27). Jenes Dokument +> kennt ap3 nicht, unterstellt einen Registry-Pull (geht bei `fetch: no` nicht) und enthält **eine +> nachweislich falsche Freigabe-Behauptung** (§8 Falle 2). Es bleibt als Historie liegen, ist aber +> **nicht mehr auszuführen**. + +**Charakter:** Vor-Ort-Session. Der `device-mode`-Wechsel verlangt an **jedem** der drei APs eine +**physische** Bestätigung — ap1 + ap3 in **Technik UG**, ap2 in **Technik OG**. + +--- + +## §0 Ist-Stand (live gelesen 2026-08-30, ~20:45) + +### Die drei APs — **sie sind NICHT gleich** + +| AP | IP | Modell | Arch | ROS **live** | Flash frei | RAM frei | `device-mode` | `container` | `fetch` | +|---|---|---|---|---|---|---|---|---|---| +| **ap1** | 10.40.99.11 | hAP ax² (`C52iG`) | arm64 | **7.24** | 95 MB / 128 | 686 MB | `home` | ❌ `no` | ❌ `no` | +| **ap2** | 10.40.99.12 | hAP ax² (`C52iG`) | arm64 | **7.24** | 95 MB / 128 | 694 MB | `home` | ❌ `no` | ❌ `no` | +| **ap3** | 10.40.99.13 | **cAP ax** (`cAPGi`) | arm64 | **7.24.1** | 96 MB / 128 | 689 MB | **`basic`** | ❌ `no` | ✅ `yes` | + +🔴 **Zwei Unterschiede, die je einen eigenen Arbeitsschritt kosten:** +1. **ap3 läuft 7.24.1, ap1/ap2 laufen 7.24.** Ein `container.npk` **muss exakt zur RouterOS-Version + passen** — sonst bleibt es nach dem Reboot schlicht deaktiviert. ⇒ **zwei verschiedene Downloads**, + nicht einer. Nicht aus Bequemlichkeit dieselbe Datei auf alle drei schieben. +2. **ap3 steht auf `device-mode=basic`**, nicht `home` — dort sind `fetch`/`scheduler` bereits erlaubt. + Das ändert am `container`-Flag nichts (auch dort `no`, auch dort physische Bestätigung), macht ap3 + aber zum einzigen AP, der theoretisch aus der Registry ziehen könnte. **Wir nutzen das bewusst + nicht** — gleicher `.tar`-Weg auf allen dreien, damit alle drei dasselbe Image fahren. + +*(Auf ap3 lief zum Lesezeitpunkt 4 h Uptime — frisch in Betrieb. Uplink laut Registry `core ether21`.)* + +### Cloudflare + +| Tunnel | ID | Status | Connectoren | +|---|---|---|---| +| **bf-public** | `48d3b0b6-7233-4781-a76a-1de4a5e957ce` | **inactive** | **0** ← Ziel dieser Session | +| bf-mm | `7a322261-380a-447b-97d2-8ebce99f5ed1` | healthy | 8 | +| bf-nas | `220c9958-401b-4111-9451-f3db09bba2d5` | healthy | 8 | +| bf-pve | `f7fdf3a6-b67d-4567-93b1-b0f273794d09` | healthy | 8 | +| `fm.emsenhuber.net` | `b697d124-…` | healthy | 4 | 🚫 **Standort FM — nicht anfassen** | +| `terpene-db` | `b83b68f0-…` | healthy | 4 | 🚫 **anderes Projekt — nicht anfassen** | + +**Heutige Wege der vier öffentlichen Namen** (alle noch **an Caddy vorbei**, direkt aufs Backend): + +| Name | CNAME → Tunnel | Ingress-Ziel | +|---|---|---| +| `n8n` | bf-mm | `http://10.40.20.167:5678` | +| `gitea` | bf-nas | `http://10.40.20.157:3000` | +| `vw` | bf-nas | `http://10.40.20.127` | +| `hass` | bf-pve | `http://10.40.20.105:8123` | + +**`bf-public` hat bereits alle vier Ingress-Regeln** → `http://10.40.20.40:80` (Caddy) + Catch-all 404. +⚠️ Genau dieses `:80` ist Blocker A, siehe §1.1. + +### Caddy (`caddy-bf1`, LXC 130, `10.40.20.40`) — gemessen + +| Name | `:80` | `:443` (SNI korrekt) | +|---|---|---| +| `vw` / `hass` / `n8n` / `gitea` | **308 → `https://…`** | **200** | + +### Die sechs Container, die am Ende weg müssen — alle drei Hosts verifiziert + +| Host | Zugang | Container | +|---|---|---| +| LXC 400 (pve) | `ssh root@10.40.20.100` → `pct exec 400 -- docker` | `cloudflared-bfmm`, `cloudflared-bfnas` | +| macmini | `ssh macmini` | `cloudflared-bfpve`, `cloudflared-bfnas` | +| NAS DS923 | Portainer `endpointId=14` | `cloudflared-bfpve`, `cloudflared-bfmm` | + +🚫 **`terpene-cloudflared` auf LXC 400 bleibt** (Tunnel `terpene-db`, anderes Projekt). Steht im +`docker ps` **direkt neben** den beiden zu löschenden — beim Löschen den Namen lesen, nicht die Zeile zählen. + +### Rollback-Material (2026-08-30 **neu angelegt**, es gab es vorher nicht) + +``` +docs/cf-backup/bf-public-ingress-20260830-vor-tp16.3-schnitt.json +docs/cf-backup/bf-mm-ingress-20260830-vor-tp16.3-schnitt.json +docs/cf-backup/bf-nas-ingress-20260830-vor-tp16.3-schnitt.json +docs/cf-backup/bf-pve-ingress-20260830-vor-tp16.3-schnitt.json +docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json # inkl. Record-IDs für den Flip-Rollback +``` + +--- + +## §1 Drei Blocker — **remote zu erledigen, BEVOR jemand in den Keller geht** + +> Alle drei sind vom Schreibtisch aus lösbar. Wer sie offen lässt, steht vor Ort vor einem +> Connector, der `healthy` meldet, während die Seite eine Redirect-Schleife dreht. + +### 1.1 🔴 Blocker A — `bf-public` zeigt auf `:80`, und Caddy antwortet dort mit 308 + +Gemessen: `curl -H "Host: vw.emsenhuber.net" http://10.40.20.40/` → **`308` → `Location: https://vw.emsenhuber.net/`**. + +Der Weg wäre also: Browser → CF → Tunnel → Caddy `:80` → **308 zurück auf denselben öffentlichen +Namen** → CF → Tunnel → Caddy `:80` → … ⇒ **`ERR_TOO_MANY_REDIRECTS`**, für alle vier Namen. + +Das fällt heute niemandem auf, weil die drei Alttunnel **an Caddy vorbei** direkt aufs Backend gehen +(§0) — die Schleife entsteht **erst** mit dem Umstieg auf Caddy als Origin. + +**Fix:** Origin auf `https://10.40.20.40:443` mit **`originServerName` je Regel** (Caddy hält für alle +vier Namen gültige LE-Zertifikate ⇒ **kein** `noTLSVerify` nötig, die Kette bleibt geprüft): + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +TID=48d3b0b6-7233-4781-a76a-1de4a5e957ce +curl -s -X PUT -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \ + "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel/$TID/configurations" \ + -d "{\"config\":{\"ingress\":[ + {\"hostname\":\"vw.emsenhuber.net\",\"service\":\"https://10.40.20.40:443\",\"originRequest\":{\"originServerName\":\"vw.emsenhuber.net\"}}, + {\"hostname\":\"hass.emsenhuber.net\",\"service\":\"https://10.40.20.40:443\",\"originRequest\":{\"originServerName\":\"hass.emsenhuber.net\"}}, + {\"hostname\":\"n8n.emsenhuber.net\",\"service\":\"https://10.40.20.40:443\",\"originRequest\":{\"originServerName\":\"n8n.emsenhuber.net\"}}, + {\"hostname\":\"gitea.emsenhuber.net\",\"service\":\"https://10.40.20.40:443\",\"originRequest\":{\"originServerName\":\"gitea.emsenhuber.net\"}}, + {\"service\":\"http_status:404\"}]}}" | jq -r ".success"' +``` + +**Abnahme:** `.success == true`, danach Ingress zurücklesen und die vier `https://…:443` sehen. + +### 1.2 🔴 Blocker B — die Firewall lässt MGMT → Caddy **nicht** durch + +Die `forward`-Kette des Core wurde vollständig gelesen (36 Regeln, Nr. 35 = `TP5: deny-by-default`). +**Es gibt keine Regel `vlan99 → vlan20` für 80/443.** Die einzige Regel in dieser Richtung ist + +``` +26 TP5-E2: MGMT->INFRA api-rueckweg + chain=forward accept protocol=tcp in-interface=vlan99 out-interface=vlan20 src-port=8728 +``` + +— ein **Rückweg** der RouterOS-API, `src-port`, nicht `dst-port`. Sie trägt den Connector nicht. + +🔴 **Der Vorgänger-Runbook behauptet in §0 wörtlich, „Regel 27 `MGMT→INFRA TCP` deckt `vlan99→vlan20:80` +bereits ab ✅". Das ist falsch** — Regel 27 ist `IOT->INFRA http-rueckweg` (`in-interface=vlan30`). +Ohne die Regel unten läuft der Connector in Regel 34 (`TP5-WOULD-DROP`, Log) und Regel 35 (Drop). + +**Fix** (vor die Log-Regel setzen, nicht ans Ende): + +``` +/ip firewall filter add chain=forward action=accept protocol=tcp \ + in-interface=vlan99 out-interface=vlan20 dst-address=10.40.20.40 dst-port=443 \ + comment="TP31: AP-cloudflared -> caddy-bf1" \ + place-before=[find comment~"TP5: log-only"] +``` + +Über MikroMCP: `plan_changes` mit `dryRun:true`, dann `apply_plan`. + +**Was NICHT gebraucht wird:** +- **Egress zu Cloudflare** (QUIC udp/7844): Regel 13 `TP5-E2: LAN->WAN` (`out-interface-list=WAN`, + ohne `in-interface`-Einschränkung) deckt das ab. ✅ +- **DNS für den Container**: Regeln 30–33 erlauben `LAN → 10.40.20.36/.37:53` aus **jedem** VLAN. ✅ + ⇒ im Container `dns=10.40.20.36` setzen. + +⚠️ **Damit die Regel greift, muss der Container-Verkehr auch wirklich als `vlan99` ankommen** — das +`veth`-Netz `172.17.0.0/24` ist dem Core unbekannt und hätte keinen Rückweg. Deshalb ist das +`masquerade` in §3.4 **nicht optional**, sondern die Voraussetzung dieser Regel. + +### 1.3 🟡 Blocker C — `ROUTER_AP3_PASS` fehlt in `~/.secrets/credentials.env` + +Vorhanden sind `ROUTER_AP1_PASS`, `ROUTER_AP2_PASS`, `CRS326_CORE_PASS`, `CRS326_BRANCH_PASS`. +Das **ap3-Passwort steht nur in `~/.claude.json`** (MCP-Env) — gegen die globale Konvention +(`~/.secrets` ist der Credential-Store) **und** ein Problem für §3.2: der Reboot-Weg liest +`SSHPASS` aus `~/.secrets`. + +```bash +# Wert aus ~/.claude.json übernehmen und ergänzen — Wert dabei NICHT auf stdout bringen: +printf 'ROUTER_AP3_USER=admin\nROUTER_AP3_PASS=%s\n' "$(python3 -c " +import json;print(json.load(open('$HOME/.claude.json'))['mcpServers']['mikromcp']['env']['ROUTER_AP3_PASS'])")" \ + >> ~/.secrets/credentials.env +chmod 600 ~/.secrets/credentials.env +grep -c '^ROUTER_AP3_PASS=' ~/.secrets/credentials.env # muss 1 sein +``` + +⚠️ **`~/.secrets/credentials.env` ist kein sauberes `KEY=VALUE`-File** — ab Zeile ~69 stehen rohe +WireGuard-Zeilen (`Endpoint:`, `AllowedIPs:`, `PersistentKeepalive:`). In **zsh** bricht +`source` daran ab und lässt die Variablen **leer**, ohne Fehler. Alle Snippets hier laufen deshalb +in `bash -c '…'`. **Wer das übersieht, jagt einen „ungültigen Token", der in Wahrheit ein leerer ist.** + +--- + +## §2 Vorbereitung am Schreibtisch (vor der Fahrt, mit Internet) + +### 2.1 Die **zwei** `container.npk` holen — Versionen nicht mischen + +```bash +mkdir -p ~/tp16.3 && cd ~/tp16.3 +# ap1 + ap2 (7.24) +curl -fLO https://download.mikrotik.com/routeros/7.24/all_packages-arm64-7.24.zip +unzip -o -j all_packages-arm64-7.24.zip 'container-7.24-arm64.npk' +# ap3 (7.24.1) +curl -fLO https://download.mikrotik.com/routeros/7.24.1/all_packages-arm64-7.24.1.zip +unzip -o -j all_packages-arm64-7.24.1.zip 'container-7.24.1-arm64.npk' +ls -la container-*.npk +``` +*(Beide URLs am 2026-08-30 mit `HTTP 200` verifiziert.)* + +### 2.2 cloudflared als `.tar` bauen — **Registry-Pull ist auf ap1/ap2 gesperrt** (`fetch: no`) + +Auf dem macmini (hat Docker, Image liegt schon lokal): + +```bash +ssh macmini 'docker pull --platform linux/arm64 \ + cloudflare/cloudflared@sha256:6d91c121b803126f7a5344005d17a9324788fc09d305b6e2560ec6040a7ae283 \ + && docker tag cloudflare/cloudflared@sha256:6d91c121b803126f7a5344005d17a9324788fc09d305b6e2560ec6040a7ae283 cfd:2026.6.1 \ + && docker save cfd:2026.6.1 -o /tmp/cloudflared-arm64.tar && ls -la /tmp/cloudflared-arm64.tar' +scp macmini:/tmp/cloudflared-arm64.tar ~/tp16.3/ +``` + +**Gepinnt auf `cloudflared 2026.6.1`** (Digest `sha256:6d91c121…`, arm64, Image 27,5 MB) — damit alle +drei APs nachweislich dasselbe fahren und ein späterer `:latest`-Sprung nichts still verschiebt. + +### 2.3 Tunnel-Token bereitlegen + +Der Token liegt bereits als `BF_PUBLIC_TUNNEL_TOKEN` in `~/.secrets/credentials.env`. Frisch ziehen +(z. B. nach Token-Rotation) — **Ausgabe nie in eine Kommandozeile oder ins Transkript**: + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +TID=48d3b0b6-7233-4781-a76a-1de4a5e957ce +T=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \ + "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel/$TID/token" | jq -r .result) +echo "Token-Länge: ${#T}" # nur die Länge, nie der Wert +printf "%s" "$T" > ~/tp16.3/bf-public.token; chmod 600 ~/tp16.3/bf-public.token' +``` + +### 2.4 Backups je AP + +```bash +# je AP, über MikroMCP oder SSH: +/system backup save name=vor-tp16.3-ap1 # bzw. -ap2 / -ap3 +/export file=vor-tp16.3-ap1 +``` + +--- + +## §3 Vor Ort — **je AP einzeln, komplett durchziehen, dann der nächste** + +> **Reihenfolge: ap3 → ap2 → ap1.** +> ap3 zuerst, weil dort am wenigsten hängt (frisch in Betrieb, 4 h Uptime) — wenn der Ablauf +> irgendwo klemmt, klemmt er am unkritischsten Gerät. **ap1 zuletzt**, weil an ihm die +> `crs326-core`-Seite des WLAN hängt. +> +> ⚠️ **Jeder Schritt hier trennt kurzzeitig das WLAN dieses APs** (zwei Reboots). Die CAPs holen +> sich ihre Config nach dem Boot vom Manager (`crs326-core`) per **L2-Discovery auf `vlan99`** — +> das kommt von selbst zurück, braucht aber ~1–2 min. **Nicht ungeduldig nachprovisionieren.** + +### 3.1 `.npk` hochladen — **die richtige Datei zum richtigen AP** + +| AP | Datei | +|---|---| +| ap1 (`.99.11`) | `container-7.24-arm64.npk` | +| ap2 (`.99.12`) | `container-7.24-arm64.npk` | +| **ap3 (`.99.13`)** | **`container-7.24.1-arm64.npk`** | + +```bash +cd ~/tp16.3 +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +SSHPASS="$ROUTER_AP3_PASS" sshpass -e scp -O container-7.24.1-arm64.npk admin@10.40.99.13:' +``` + +🔴 **Niemals `sshpass -p`** — das schreibt das Passwort in die Kommandozeile und damit in `ps` und +jeden argv-Trace. **Genau so ist am 2026-08-22 das Core-Passwort in ein Transkript geraten.** +`-e` hält es in der Umgebung des Kindprozesses. + +Gegenprobe vor dem Reboot — **die Datei muss wirklich liegen**, sonst rebootet man umsonst: +``` +/file print where name~"container" +``` + +### 3.2 Reboot (installiert das Paket) + +🔴 **Der Reboot geht NICHT über MikroMCP.** Das `reboot`-Tool sendet einen `delay`-Parameter, den die +REST-API auf ROS 7.24 nicht kennt (`unknown parameter delay`); `run_command` ist für `/system reboot*` +per Deny-Policy gesperrt. Der Weg ist SSH: + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +SSHPASS="$ROUTER_AP3_PASS" sshpass -e ssh -o StrictHostKeyChecking=accept-new \ + admin@10.40.99.13 "/system reboot"' +``` + +**Abnahme nach ~90 s:** +``` +/system package print +# muss jetzt DREI Pakete zeigen: routeros, wifi-qcom, container — alle gleiche Version +``` +⚠️ **Steht `container` da mit abweichender Version oder `disabled=yes`, war es die falsche `.npk`.** +Dann: Datei löschen, richtige hochladen, erneut rebooten. Nicht weitermachen. + +### 3.3 `device-mode` — **der physische Schritt** + +``` +/system device-mode update container=yes +``` + +Die Session **bricht danach ab** und RouterOS wartet auf eine Bestätigung **am Gerät** mit einer +Frist (typisch **5 Minuten**, die Konsole nennt die genaue Deadline). Bestätigt wird durch: + +**Weg A — Kaltstart (empfohlen, eindeutig):** +> **Stecker ziehen, ~5 Sekunden warten, wieder einstecken.** + +🔴 **Wichtig: die APs hängen NICHT an PoE aus den Switches** — beide CRS326 haben nachweislich gar +kein PoE-Menü (`bad command name poe`). Es gibt also **keinen** Weg, das aus der Ferne zu tun; der +Strom kommt vom jeweiligen Netzteil bzw. Injektor am AP. + +**Weg B — Taster:** +> **Reset-Knopf am laufenden Gerät kurz drücken und loslassen.** Am hAP ax² sitzt er an der +> Unterseite, am cAP ax unter der Abdeckung an der Rückseite. + +⚠️ **Weg B nur am LAUFENDEN Gerät und nur kurz.** Den Reset-Knopf **während des Bootens** gedrückt zu +halten ist etwas völlig anderes — das setzt die Konfiguration zurück bzw. geht nach Netinstall. +Wer unsicher ist, nimmt **Weg A**; ein Kaltstart kann nicht versehentlich zum Werksreset werden. + +Verstreicht die Frist ohne Bestätigung, passiert **nichts** — die Änderung verfällt, das Gerät bleibt +wie es war. Dann einfach neu absetzen. + +**Abnahme:** +``` +/system device-mode print +# container: yes +``` + +### 3.4 Container-Netz anlegen + +``` +/interface veth add name=veth-cf address=172.17.0.2/24 gateway=172.17.0.1 +/ip address add address=172.17.0.1/24 interface=veth-cf +/ip firewall nat add chain=srcnat action=masquerade src-address=172.17.0.0/24 \ + comment="TP31: cloudflared egress" +``` + +Das `masquerade` ist **Pflicht**, nicht Kosmetik — siehe §1.2: ohne es kommen die Pakete mit +`172.17.0.2` am Core an, wo weder Route noch Firewall-Regel sie kennt. + +### 3.5 Image importieren und Container bauen + +```bash +# .tar hochladen (~28 MB) +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +SSHPASS="$ROUTER_AP3_PASS" sshpass -e scp -O ~/tp16.3/cloudflared-arm64.tar admin@10.40.99.13:' +``` + +Auf dem AP: +``` +/container config set tmpdir=tmp +/container add file=cloudflared-arm64.tar interface=veth-cf root-dir=cf \ + dns=10.40.20.36 logging=yes start-on-boot=yes \ + cmd="tunnel --no-autoupdate run --token=" +``` + +🔑 **Token-Handling:** den Wert aus `~/tp16.3/bf-public.token` einsetzen. Sauberer als ihn in die +`cmd` zu schreiben ist eine Env-Variable — dann steht er nicht im `/container print`: +``` +/container envs add name=cfd key=TUNNEL_TOKEN value="" +/container envs add name=cfd key=TUNNEL_TRANSPORT_PROTOCOL value=quic +# und im add: envlist=cfd cmd="tunnel --no-autoupdate run" +``` + +Starten und aufräumen: +``` +/container start [find root-dir=cf] +/container print # status: running +/file remove [find name~"cloudflared-arm64.tar"] # ~28 MB Flash zurückholen +``` + +⚠️ **Platzrechnung:** 95 MB frei − 28 MB `.tar` − ~28 MB entpackte Layer ≈ **39 MB Rest**, und das +nur bis der `.tar` gelöscht ist. Es geht aus, aber **ohne Reserve für einen zweiten Container** — +das ist die harte Grenze aus TP31, kein Richtwert. + +### 3.6 Abnahme **auf diesem AP** + +``` +/log print where message~"cloudflared" or message~"tunnel" +# erwartet: "Registered tunnel connection" (mehrfach, je CF-Edge) +``` + +Und **die Strecke zu Caddy separat prüfen** — sie ist der Teil, den Blocker B betraf: +``` +/tool fetch url="https://10.40.20.40/" check-certificate=no output=none +``` +*(Auf ap1/ap2 ist `fetch` per `device-mode=home` gesperrt — dort stattdessen aus dem Container heraus +oder über den Drop-Log gegenprüfen, siehe §8.)* + +**Der Drop-Log ist das erste Diagnosewerkzeug, nicht das letzte:** +`10.40.20.18:514`, Dateien auf dem Docker-LXC unter `/opt/tp5-syslog/log/`, Präfix `TP5-WOULD-DROP`. +Taucht dort `10.40.99.13 → 10.40.20.40:443` auf, fehlt die Regel aus §1.2. + +### 3.7 Erst wenn dieser AP sauber ist: nächster AP + +--- + +## §4 Abnahme des Tunnels (nach allen drei APs) + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +curl -s -H "Authorization: Bearer $CF_API_TOKEN" \ + "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel/48d3b0b6-7233-4781-a76a-1de4a5e957ce" \ + | jq "{status:.result.status, conns:(.result.connections|length)}"' +``` + +**Kriterium: `status: "healthy"`, `conns` > 0** (erfahrungsgemäß 4 CF-Edge-Verbindungen je Connector, +bei drei APs also Richtung 12). + +🔴 **`healthy` heißt „der Connector redet mit Cloudflare" — nicht „das Origin antwortet".** Der Beweis +für die Strecke bis Caddy ist §5.1, nicht diese Zahl. Genau diese Verwechslung hat beim +`grafana`-Fall monatelang einen toten Dienst gesund aussehen lassen. + +--- + +## §5 CNAME-Flip — einzeln, mit Abnahme dazwischen + +**Reihenfolge: `n8n` → `gitea` → `hass` → `vw`** (steigende Kritikalität; `vw` zuletzt). +Zone-ID `1b5bc02fdf61b6b989afde543f3f4134`. + +### 5.1 Vorab: Testname statt Produktivname + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +Z=1b5bc02fdf61b6b989afde543f3f4134 +curl -s -X POST -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \ + "https://api.cloudflare.com/client/v4/zones/$Z/dns_records" \ + -d "{\"type\":\"CNAME\",\"name\":\"test-tp163\",\"content\":\"48d3b0b6-7233-4781-a76a-1de4a5e957ce.cfargotunnel.com\",\"proxied\":true}" | jq -r .success' +``` +Dazu **eine Ingress-Regel** für `test-tp163.emsenhuber.net` → `https://10.40.20.40:443` mit +`originServerName: vw.emsenhuber.net` in `bf-public` ergänzen. Dann **von außerhalb des LAN, ohne +WARP** aufrufen. Erwartet: Access-Login, **keine Redirect-Schleife**. +⇒ Erst wenn das steht, wird ein Produktivname angefasst. Danach Testname + Testregel wieder entfernen. + +### 5.2 Flip je Name + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +Z=1b5bc02fdf61b6b989afde543f3f4134 +N=n8n # danach: gitea, hass, vw +ID=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \ + "https://api.cloudflare.com/client/v4/zones/$Z/dns_records?name=$N.emsenhuber.net" | jq -r ".result[0].id") +curl -s -X PATCH -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \ + "https://api.cloudflare.com/client/v4/zones/$Z/dns_records/$ID" \ + -d "{\"content\":\"48d3b0b6-7233-4781-a76a-1de4a5e957ce.cfargotunnel.com\",\"proxied\":true}" | jq -r .success' +``` + +### 5.3 Abnahme je Name — **von außerhalb des LAN, WARP AUS** + +- [ ] Aufruf über Mobilfunk (oder anderer Standort) → Access-Login → **200** +- [ ] **Kein** `ERR_TOO_MANY_REDIRECTS` (das wäre Blocker A) +- [ ] Caddy sieht den Request: `ssh root@10.40.20.100 'pct exec 130 -- journalctl -u caddy -n 20'` +- [ ] 15 min beobachten, kein 530/1033 + +🔴 **WARP muss aus sein.** Mit aktivem WARP läuft der Test über den Mesh und beweist den öffentlichen +Weg **nicht** — dieselbe Klasse Fehldiagnose wie beim ICMP-Fall 2026-08-13. + +**Namensspezifisch:** +- **`gitea`** — Access gilt **nur der Web-UI**, Git-/API-Pfade laufen daran vorbei. **Vor** dem Flip + einen Push von außen testen, **nach** dem Flip denselben Push wiederholen. +- **`hass`** — die Companion-App kann kein Access-Login. Nach dem Flip App auf dem Handy **über + Mobilfunk** prüfen, nicht nur den Browser. +- **`vw`** — Bitwarden läuft auf **Access-Bypass**. **Vor** dem Flip prüfen, dass die Bypass-Policy + für `vw.emsenhuber.net` steht; sonst sperrt der Flip alle Bitwarden-Clients aus. + +--- + +## §6 Abbau — **frühestens nach 7 Tagen störungsfreiem Betrieb** + +> Die drei Alttunnel sind die Rückfallebene. Solange sie stehen, ist jeder Flip eine +> Ein-Feld-Änderung zurück. **Nicht am selben Abend abbauen.** + +### 6.1 Erst die Container stoppen, dann die Tunnel löschen + +Umgekehrt genommen läuft man in 530er von Connectoren, die auf einen gelöschten Tunnel zeigen. + +```bash +# LXC 400 auf pve — terpene-cloudflared NICHT anfassen +ssh root@10.40.20.100 'pct exec 400 -- docker rm -f cloudflared-bfmm cloudflared-bfnas' +ssh root@10.40.20.100 'pct exec 400 -- docker ps --format "{{.Names}}"' # terpene-cloudflared muss bleiben + +# macmini +ssh macmini 'docker rm -f cloudflared-bfpve cloudflared-bfnas && docker ps --format "{{.Names}}"' +``` + +NAS (DS923, Portainer `endpointId=14`, Key aus `mcp/portainer/.env`): +```bash +bash -c 'set -a; . ~/dev/bf-infra/mcp/portainer/.env; set +a +K="${PORTAINER_API_KEY:-$PORTAINER_TOKEN}"; U="${PORTAINER_URL:-https://10.40.20.126:9443}" +for c in cloudflared-bfpve cloudflared-bfmm; do + ID=$(curl -sk -H "X-API-Key: $K" "$U/api/endpoints/14/docker/containers/json?all=true" \ + | jq -r ".[]|select(.Names[0]==\"/$c\")|.Id") + curl -sk -X DELETE -H "X-API-Key: $K" "$U/api/endpoints/14/docker/containers/$ID?force=true" + echo "removed $c" +done' +``` +⚠️ Sind es **Stacks** statt einzelner Container, kommen sie sonst beim nächsten Stack-Deploy zurück — +dann den Stack-Eintrag entfernen, nicht nur den Container. + +### 6.2 Dann die drei Tunnel löschen + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +for T in 7a322261-380a-447b-97d2-8ebce99f5ed1 220c9958-401b-4111-9451-f3db09bba2d5 f7fdf3a6-b67d-4567-93b1-b0f273794d09; do + curl -s -X DELETE -H "Authorization: Bearer $CF_API_TOKEN" \ + "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel/$T" | jq -r "{id:\"$T\",ok:.success}" +done' +``` + +🚫 **Nicht in dieser Schleife und nirgends sonst:** `b697d124-…` (`fm.emsenhuber.net`, Standort FM) +und `b83b68f0-…` (`terpene-db`). **IDs vor dem Absetzen einzeln gegen §0 prüfen.** + +### 6.3 Schlusskontrolle + +```bash +bash -c 'set -a; . ~/.secrets/credentials.env; set +a +curl -s -H "Authorization: Bearer $CF_API_TOKEN" \ + "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel?is_deleted=false" \ + | jq -r ".result[]|\"\(.name)\t\(.status)\tconns=\(.connections|length)\""' +``` +**Soll:** `bf-public healthy`, `fm.emsenhuber.net healthy`, `terpene-db healthy` — sonst nichts. + +--- + +## §7 Rollback + +| Lage | Rückweg | +|---|---| +| Ein Name kaputt nach Flip | CNAME zurück auf die alte Tunnel-ID — Werte in `cnames-tunnel-20260830-…json` (inkl. Record-ID) | +| `bf-public`-Ingress kaputt | `PUT …/configurations` mit `bf-public-ingress-20260830-…json` | +| Container auf einem AP stört | `/container stop` + `/container remove`, WLAN läuft davon unabhängig | +| AP-Config verkorkst | `/system backup load name=vor-tp16.3-apX` | +| Firewall-Regel unerwünscht | `/ip firewall filter remove [find comment~"TP31: AP-cloudflared"]` | + +**`device-mode=container:yes` ist nicht Teil des Rollbacks.** Zurückdrehen ginge nur mit erneutem +physischem Gang. Es schadet nicht — ohne laufenden Container ist es ein gesetztes Flag, sonst nichts. + +--- + +## §8 Fallen + +1. **Zwei ROS-Versionen, zwei `.npk`.** ap1/ap2 = 7.24, ap3 = **7.24.1**. Falsche Datei ⇒ Paket bleibt + nach dem Reboot inaktiv. **Nach jedem Reboot `/system package print` lesen**, nicht annehmen. +2. **Die Freigabe `vlan99 → vlan20:443` existiert nicht** — entgegen der Behauptung im + Vorgänger-Runbook. Ohne §1.2 landet der Connector im `TP5-WOULD-DROP`-Log. **Erst dort nachsehen.** +3. **Caddy antwortet auf `:80` mit 308.** Ein Ingress auf `:80` erzeugt eine Redirect-Schleife — + und zwar **erst** beim Umstieg auf Caddy, weil die Altwege am Proxy vorbeigehen. +4. **`healthy` ≠ „Dienst erreichbar".** Der Tunnelstatus sagt nichts über das Origin; die + Wildcard-Access-App `*.emsenhuber.net` liefert für **jeden** Namen ein sauberes 302. Ein toter + Dienst sieht so monatelang gesund aus (der `grafana`-Fall). +5. **Kein `sshpass -p`.** Nur `-e` mit `SSHPASS` aus der Umgebung. +6. **`~/.secrets/credentials.env` in zsh sourcen schlägt still fehl** (rohe WireGuard-Zeilen ab ~Z. 69) + — Variablen bleiben **leer**, die API meldet einen „ungültigen Token". Immer `bash -c`. +7. **`terpene-cloudflared` steht direkt neben den zu löschenden Containern.** Namen lesen. +8. **Ein Container pro AP, keiner mehr.** Nach dem Import bleiben ~39 MB. Caddy oder AGH gehören + nicht dazu (die eine Config ändert sich ständig, die andere schreibt dauernd auf NAND). +9. **Reboot nie über MikroMCP** (`unknown parameter delay`), sondern per SSH. +10. **WLAN-Abnahme ist eine frische DHCP-Lease je SSID, nicht die Registration-Table.** Nach zwei + Reboots je AP lohnt der Blick — die Tabelle zeigte beim CAPsMAN-Ausfall 14 h lang zufriedene + Clients ohne Adresse. + +--- + +## §9 Ehrliche Einschränkung — was diese Session **nicht** kauft + +Solange Caddy **nur** auf `caddy-bf1` (LXC 130, pve) läuft, ist das Origin aller drei Connectoren +**derselbe Rechner**. Drei Connectoren auf drei APs kaufen damit **Hardware-Diversität am Eingang, +aber keine Verfügbarkeit**: fällt pve aus, sind alle vier öffentlichen Dienste weg — mit drei +Connectoren genauso vollständig wie mit einem. + +**Der Gewinn ist real, aber begrenzt:** kein Docker-Host mehr im öffentlichen Pfad (die APs sind +eigene Maschinen ⇒ die macvlan/ipvlan-Blindheit kann dort prinzipiell nicht auftreten), sechs +Container werden drei, und die Host→Origin-Zuordnung lebt nur noch im `caddy/Caddyfile`. + +**Der echte Verfügbarkeitsgewinn kommt erst mit `caddy-bf2` (LXC 131)** und einer zweiten +Ingress-Regel. ⚠️ Für `vw` gilt dabei die TP20.1-Bremse: zwei Caddys an verschiedenen Standorten +würden die Hälfte der **Schreib**zugriffe auf einen read-only Standby schicken — das #1033-Muster, +diesmal mit Datenverlust. **Automatisches Ingress-Failover erst, wenn der DB-Teil es trägt.** + +Bis dahin ist die Reserve die manuelle: derselbe Connector kann über eine zusätzliche Ingress-Regel +**direkt** auf ein kritisches Backend zeigen, an Caddy vorbei. diff --git a/docs/tp31-schritt5-runbook.md b/docs/tp31-schritt5-runbook.md index 440830e..9f88b62 100644 --- a/docs/tp31-schritt5-runbook.md +++ b/docs/tp31-schritt5-runbook.md @@ -5,6 +5,22 @@ # Mode-Button-Druck an jedem AP, danach Reboot. Dieser Runbook-Teil muss # vor Ort ausgeführt werden. +> 🔴 **ABGELÖST 2026-08-30 — NICHT MEHR AUSFÜHREN.** +> Nachfolger: [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md). +> Dieses Dokument bleibt als Historie stehen. Drei Gründe, warum es nicht mehr trägt: +> 1. **Es kennt `ap3` nicht** (cAP ax, `10.40.99.13`, seit 2026-08-30 in Betrieb) — und ap3 läuft +> **ROS 7.24.1** gegen 7.24 auf ap1/ap2, braucht also ein **anderes `container.npk`**. +> 2. **§2b unterstellt einen Registry-Pull.** Auf ap1/ap2 ist `fetch: no` (`device-mode=home`) — +> das Image muss als `.tar` hoch und per `/container add file=` importiert werden. +> 3. 🔴 **§0 behauptet, „Regel 27 `MGMT→INFRA TCP` deckt `vlan99→vlan20:80` bereits ab ✅".** +> **Das ist falsch.** Die `forward`-Kette des Core wurde am 2026-08-30 vollständig gelesen: +> Regel 27 ist `IOT->INFRA http-rueckweg` (`in-interface=vlan30`), und die einzige MGMT→INFRA-Regel +> (Nr. 26) ist ein `src-port=8728`-Rückweg der API. **Eine Freigabe für den Connector existiert +> nicht** — ohne sie landet er in `TP5-WOULD-DROP` + Drop. +> +> Ebenfalls im Nachfolger, hier noch nicht enthalten: der **Redirect-Loop** über `:80` +> (Caddy antwortet dort mit 308) und der Abbau der sechs Container. + ## 0. IST-STAND (live gelesen 2026-08-27) - Tunnel **bf-public** `48d3b0b6` ✅: Ingress mit 4 öffentlichen Hostnames → `10.40.20.40:80` - Token: `~/.secrets/credentials.env` → `BF_PUBLIC_TUNNEL_TOKEN` From 810126c01ba3419586316a6ed14f8a1acb33ce4b Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Mon, 31 Aug 2026 07:52:33 +0200 Subject: [PATCH 2/5] Doku: bf-infra hat seit 2026-08-30 ein Gitea-Remote (Backup-Luecke geschlossen) CLAUDE.md und README.md behaupteten beide noch den Inventur-Stand vom 2026-08-27: "lokal, kein Gitea-Remote (Working-Copy). Verloren bei Festplattencrash." bzw. "kein Gitea-Remote (Sicherheitsluecke, offen)". Live geprueft: origin = https://gitea.emsenhuber.net/admin/bf-infra.git, main lokal und remote identisch. Die Luecke ist zu. Dazu der Hinweis, der die Aussage traegt: ein Remote ist nur so viel wert wie der letzte Push - der Backup-Guard (TP35 Schicht 5) ist genau diese Erinnerung und wird nicht mit --no-verify umgangen. docs/handoff-20260827-inventur-und-architektur.md bleibt unveraendert - der Handoff ist ein historischer Snapshot und war an dem Tag korrekt. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 8 +++++++- README.md | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 518f517..17eac75 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,13 @@ 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, **lokal, kein Gitea-Remote** (Working-Copy). Verloren bei Festplattencrash. +- **`bf-infra`** ist aktiver Master und hat **seit 2026-08-30 ein Gitea-Remote**: + `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git`, `main` lokal und remote synchron. + ⚠️ **Die frühere Zeile „lokal, kein Gitea-Remote (Working-Copy). Verloren bei Festplattencrash." + ist damit überholt** — die Backup-Lücke aus der Inventur 2026-08-27 ist geschlossen. + 🔴 **Aber: 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. - **`admin/bf-network-r6`** (Gitea) ist Archiv, letzte aktive Commits 2026-08-25 → read-only-Header. - **`admin/bf-golden-config`** (Gitea) ist Bot-Master (Nautobot 2×/Tag), nicht manuell. - **`admin/blaueblueten-n8n`** (Gitea) ist Master für terpene-Repo. diff --git a/README.md b/README.md index d2a5c7e..2f641ef 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ unter OpenCode und anderem LLM weitergepflegt. | Repo | Rolle | Stand | |---|---|---| -| `~/dev/bf-infra/` (lokal) | Aktiver Master, Working-Copy | kein Gitea-Remote (Sicherheitslücke, offen) | +| `~/dev/bf-infra/` + `admin/bf-infra` (Gitea) | Aktiver Master | **Remote seit 2026-08-30**, `main` synchron — Backup-Lücke geschlossen | | `admin/bf-network-r6` (Gitea) | Archiv, historischer Master | read-only-Header, letzte aktive Commits 2026-08-25 | | `admin/bf-golden-config` (Gitea) | Bot-Master (Nautobot 2×/Tag) | intended/ + backups/, nicht manuell | | `admin/blaueblueten-n8n` (Gitea) | Master für terpene-Repo | aktiv, lokal synchron | From badf715f5ae8f3e7dcdb68bb8b55f8466457dd51 Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Mon, 31 Aug 2026 08:06:38 +0200 Subject: [PATCH 3/5] =?UTF-8?q?TP16.3=20=C2=A71:=20die=20drei=20Blocker=20?= =?UTF-8?q?remote=20behoben=20(Ingress,=20Firewall=20via=20GC,=20ap3-Cred)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A) bf-public-Ingress von http://10.40.20.40:80 auf https://10.40.20.40:443 umgestellt, originServerName je Regel. Die :80-Variante haette ueber Cloudflare eine Redirect-Schleife gebaut (Caddy antwortet dort 308 auf https, das Ziel laeuft zurueck in denselben Tunnel). B) Freigabe vlan99 -> vlan20:443 auf crs326-core angelegt - ueber Nautobot Golden Config, NICHT per Direkt-Write: ip-firewall-filter steht seit TP26 unter Drift-Kontrolle, und die zwei genehmigten Ausnahmen (#2 Bring-up, #4 WireGuard-Keys) decken diesen Fall nicht. - Backup vorher: core-pre-tp16.3-blockerB-20260831.backup (TP22-Regel 3) - gc-manual-plan.py, Change-ID TP16.3-BlockerB, Plan Completed - Position verifiziert: direkt vor TP5: log-only / TP5: deny-by-default - Re-Baseline ingest-core-firewall.py --apply, dann --refresh-compliance => 135/135 compliant, 0 Drifts - funktional: /tool fetch von ap3 erreicht Caddy und bekommt einen TLS-Alert VON Caddy - ein Alert kann nur vom Server kommen, das Paket ist also da. Der Alert ist die SNI-Abweisung (ohne SNI 000, mit korrektem SNI 200) und damit genau der Grund fuer originServerName. C) ROUTER_AP3_USER/_PASS in ~/.secrets/credentials.env nachgetragen (war nur in ~/.claude.json), chmod 600, Sicherung .bak-20260831. REST-Auth gegen ap3 = HTTP 200. Vor Ort startet die Arbeit damit bei §2/§3 (container.npk, device-mode, Container). Quick-Index, TP16.3-Abschnitt und Ist-Stand mitgezogen. Co-Authored-By: Claude Opus 5 --- docs/teilprojekte.md | 25 +++++++++----- docs/tp16.3-bf-public-ap-connector-runbook.md | 34 +++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 5d650e1..b331f1e 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -40,6 +40,11 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d unterstellte einen Registry-Pull (`fetch: no` auf ap1/ap2) und enthielt eine **falsche** Firewall-Freigabe-Behauptung. **CF-Stand:** `bf-public` `inactive` / 0 Connectoren; die vier öffentlichen Namen laufen noch über bf-mm/bf-nas/bf-pve **an Caddy vorbei** direkt aufs Backend. +- 🆕 **TP16.3 §1-Blocker remote erledigt (2026-08-31):** Ingress von `bf-public` auf + `https://10.40.20.40:443` + `originServerName`; Firewall-Freigabe `vlan99 → vlan20:443` **über + Nautobot GC** (Plan `Completed`, Re-Baseline `ingest-core-firewall.py --apply`, danach + **135/135 compliant / 0 Drifts**); `ROUTER_AP3_PASS` in `~/.secrets`. **Offen bleibt nur die + Vor-Ort-Session** (§2/§3: `container.npk`, `device-mode`, Container) und danach Flip + Abbau. - 🆕 **Globale Verhaltensregeln konsolidiert (2026-08-29, TP35 Schicht 3):** `~/.config/opencode/AGENTS.md` ist jetzt die **SSoT** für globale Verhaltensregeln (Secrets, VLAN-Schema, 5 Lessons, SoC, Übergabe-Mechanik, Mid-Action-Checkpoint, Commit-Hinweis, Backup-Guard). `~/.claude/CLAUDE.md` hält denselben Block als Claude-Code-Spiegelung mit Verweis auf die SSoT. opencode liest AGENTS.md mit Vorrang vor `~/.claude/CLAUDE.md`. - **Fünf MikroTik seit 2026-08-30** — neu dazu **`ap3`** (cAP ax `cAPGi-5HaxD2HaxD`, **`10.40.99.13`**, @@ -79,11 +84,13 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d account-weite). Abnahme über den Host-Header von einem zweiten Host: 11× 200, `nautobot` 302. Offen: **Schritt 5–6** (Connector für `bf-public`, CNAME-Flip, Abbau der sechs Container) — **Runbook steht: [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md)**, - Vor-Ort-Session an ap1/ap2/ap3. 🔴 **Zwei Blocker davor, beide remote lösbar** (08-30 gemessen): - **(a)** der `bf-public`-Ingress zeigt auf `:80`, wo Caddy mit **308 → https** antwortet ⇒ Redirect-Schleife - über Cloudflare; Fix = `https://10.40.20.40:443` + `originServerName` je Regel (über 443 mit korrekter - SNI liefern alle vier Namen 200). **(b)** es gibt **keine** Freigabe `vlan99 → vlan20:443` — der - Connector liefe in `TP5-WOULD-DROP`. + Vor-Ort-Session an ap1/ap2/ap3. ✅ **Die drei §1-Blocker sind am 2026-08-31 remote erledigt** — + **(a)** Ingress auf `https://10.40.20.40:443` + `originServerName` je Regel umgestellt (die alte + `:80`-Zeile hätte über CF eine Redirect-Schleife gebaut, weil Caddy dort 308 auf https antwortet); + **(b)** Freigabe `vlan99 → vlan20:443` **über Nautobot GC** deployt (`ip-firewall-filter` steht unter + Drift-Kontrolle, TP26 ⇒ kein Direkt-Write), Position vor `TP5: log-only` verifiziert, **135/135 + compliant, 0 Drifts**; **(c)** `ROUTER_AP3_PASS` in `~/.secrets` nachgetragen (REST-Auth 200). + ⇒ **Vor Ort startet die Arbeit bei §2/§3** (Pakete, `device-mode`, Container). - **DNS/Resolver = AdGuard-Paar `agh-bf1`/`agh-bf2`** (LXC 110/111 auf pve, **`10.40.20.36`/`.37`**, TP23 Stufe 2 + TP31 Schritt 4 ✅ seit 2026-08-26): Upstream DoH zum CF-Gateway, conditional `[/bf.emsenhuber.net/] → 10.40.20.1`, 47 Rewrites (16 Service → Caddy `.20.40` + 10 Host-Namen @@ -221,7 +228,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP14.4 | 🔜 | CF-Access-Token | — | > | TP16 | ✅ | CF-Tunnel HA wieder voll | 2026-08-24 | > | TP16.2 | 🔜 | Härtung bypass-Dienste | — | -> | TP16.3 | 🔜 | `bf-public`-Connector auf ap1/ap2/**ap3** (Runbook steht) | — | +> | TP16.3 | 🔜 | `bf-public`-Connector ap1/ap2/**ap3** (§1-Blocker ✅, Rest vor Ort) | — | > | TP17.1 | ❌ | Shelly Gen4 → Zigbee | 2026-08-23 | > | TP17.3 | ✅ | Shelly-Regression behoben | 2026-08-23 | > | TP18 | 🔜 | Garage-Objektspeicher | — | @@ -441,12 +448,12 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na ap1/ap2 ⇒ **zwei verschiedene `container.npk`**. Und ap3 steht auf `device-mode=basic` (nicht `home`), dort ist `fetch` bereits erlaubt — wir nutzen trotzdem auf allen dreien den `.tar`-Import, damit alle dasselbe Image fahren (gepinnt: `cloudflared 2026.6.1`, Digest `sha256:6d91c121…`, 27,5 MB). -- 🔴 **Blocker A — Redirect-Schleife.** `bf-public` zeigt auf `http://10.40.20.40:80`; Caddy antwortet +- ✅ **Blocker A — Redirect-Schleife, behoben 2026-08-31.** `bf-public` zeigt auf `http://10.40.20.40:80`; Caddy antwortet dort mit **308 → `https:///`**, also zurück durch Cloudflare in denselben Tunnel ⇒ `ERR_TOO_MANY_REDIRECTS` für alle vier Namen. **Fällt heute nicht auf**, weil die drei Alttunnel **an Caddy vorbei** direkt aufs Backend gehen — die Schleife entsteht erst mit dem Umstieg. Fix = `https://10.40.20.40:443` + `originServerName` je Regel (über 443 mit korrekter SNI: 4× 200). -- 🔴 **Blocker B — die Freigabe existiert nicht.** Die `forward`-Kette des Core wurde vollständig +- ✅ **Blocker B — die Freigabe fehlte, angelegt 2026-08-31 über Nautobot GC.** Die `forward`-Kette des Core wurde vollständig gelesen (36 Regeln, Nr. 35 = `TP5: deny-by-default`): **keine** Regel `vlan99 → vlan20` für 80/443. Die einzige MGMT→INFRA-Regel (Nr. 26) ist ein `src-port=8728`-**Rückweg** der API. ⚠️ **Der Vorgänger-Runbook behauptet das Gegenteil** („Regel 27 deckt `vlan99→vlan20:80` ab ✅") — @@ -454,7 +461,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na in `TP5-WOULD-DROP` + Drop. Egress zu CF (Regel 13 `LAN->WAN`) und DNS (Regeln 30–33) sind dagegen ✅. ⇒ Das `masquerade` des `veth`-Netzes ist deshalb **Pflicht**, nicht Kosmetik: sonst kommen die Pakete mit `172.17.0.2` am Core an, wo weder Route noch Regel sie kennt. -- 🟡 **Blocker C — `ROUTER_AP3_PASS` fehlt in `~/.secrets/credentials.env`** (steht nur in +- ✅ **Blocker C — `ROUTER_AP3_PASS` fehlte in `~/.secrets/credentials.env`, nachgetragen 2026-08-31** (steht nur in `~/.claude.json`), gegen die Credential-Store-Konvention. Der `sshpass -e`-Reboot-Weg braucht es dort. ⚠️ Nebenbefund: **`credentials.env` lässt sich in zsh nicht sourcen** — ab ~Zeile 69 stehen rohe WireGuard-Zeilen, zsh bricht ab und lässt die Variablen **leer**; das sieht aus wie ein ungültiger diff --git a/docs/tp16.3-bf-public-ap-connector-runbook.md b/docs/tp16.3-bf-public-ap-connector-runbook.md index 6672271..eb1c408 100644 --- a/docs/tp16.3-bf-public-ap-connector-runbook.md +++ b/docs/tp16.3-bf-public-ap-connector-runbook.md @@ -84,12 +84,21 @@ docs/cf-backup/cnames-tunnel-20260830-vor-tp16.3-schnitt.json # inkl. Record-I --- -## §1 Drei Blocker — **remote zu erledigen, BEVOR jemand in den Keller geht** +## §1 Drei Blocker — ✅ **ALLE ERLEDIGT 2026-08-31 (remote)** -> Alle drei sind vom Schreibtisch aus lösbar. Wer sie offen lässt, steht vor Ort vor einem -> Connector, der `healthy` meldet, während die Seite eine Redirect-Schleife dreht. +> ✅ **Nichts davon ist vor Ort noch zu tun.** Abschnitt bleibt als Nachweis stehen; die Fixes sind +> gemessen, nicht nur abgesetzt. **Vor Ort startet die Arbeit bei §2/§3.** +> +> | Blocker | Stand | Nachweis | +> |---|---|---| +> | **A** Redirect-Schleife (`:80`) | ✅ behoben | Ingress zeigt auf `https://10.40.20.40:443`, `originServerName` je Regel gesetzt | +> | **B** Freigabe `vlan99 → vlan20:443` | ✅ behoben | Regel via Nautobot GC deployt, Plan `Completed`, am Gerät verifiziert, **135/135 compliant, 0 Drifts** | +> | **C** `ROUTER_AP3_PASS` | ✅ behoben | in `~/.secrets/credentials.env` (`chmod 600`), REST-Auth gegen ap3 = **HTTP 200** | -### 1.1 🔴 Blocker A — `bf-public` zeigt auf `:80`, und Caddy antwortet dort mit 308 +### 1.1 ✅ Blocker A — `bf-public` zeigte auf `:80`, und Caddy antwortet dort mit 308 + +> **Erledigt 2026-08-31.** Ingress zurückgelesen: alle vier Namen → `https://10.40.20.40:443`, +> `SNI` = der jeweilige Hostname. Der `PUT` unten ist der ausgeführte Stand, nicht mehr eine Aufgabe. Gemessen: `curl -H "Host: vw.emsenhuber.net" http://10.40.20.40/` → **`308` → `Location: https://vw.emsenhuber.net/`**. @@ -117,7 +126,17 @@ curl -s -X PUT -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: applic **Abnahme:** `.success == true`, danach Ingress zurücklesen und die vier `https://…:443` sehen. -### 1.2 🔴 Blocker B — die Firewall lässt MGMT → Caddy **nicht** durch +### 1.2 ✅ Blocker B — die Firewall liess MGMT → Caddy **nicht** durch + +> **Erledigt 2026-08-31 über Nautobot Golden Config** (`gc-manual-plan.py --device crs326-core --deploy`, +> Change-ID `TP16.3-BlockerB`, Plan `23e5b667-…` **Completed**), **nicht** per Direkt-Write — `ip-firewall-filter` +> steht unter Drift-Kontrolle (TP26). Backup vorher: `core-pre-tp16.3-blockerB-20260831.backup` (TP22-Regel 3). +> Danach `ingest-core-firewall.py --apply` (Re-Baseline) + `--refresh-compliance` ⇒ **135/135, 0 Drifts.** +> **Position verifiziert:** die Regel sitzt direkt **vor** `TP5: log-only` und `TP5: deny-by-default`. +> **Funktional verifiziert:** `/tool fetch` von ap3 auf `https://10.40.20.40` erreicht Caddy und bekommt +> einen **TLS-Alert von Caddy selbst** — ein Alert kann nur vom Server kommen, das Paket ist also da. +> Der Alert ist die **SNI-Abweisung** (Gegenprobe: ohne SNI `000`, mit korrektem SNI `200`) und genau +> der Grund, warum `originServerName` in §1.1 gesetzt ist. **Kein Fehler, sondern der Beweis.** Die `forward`-Kette des Core wurde vollständig gelesen (36 Regeln, Nr. 35 = `TP5: deny-by-default`). **Es gibt keine Regel `vlan99 → vlan20` für 80/443.** Die einzige Regel in dieser Richtung ist @@ -154,7 +173,10 @@ Ohne die Regel unten läuft der Connector in Regel 34 (`TP5-WOULD-DROP`, Log) un `veth`-Netz `172.17.0.0/24` ist dem Core unbekannt und hätte keinen Rückweg. Deshalb ist das `masquerade` in §3.4 **nicht optional**, sondern die Voraussetzung dieser Regel. -### 1.3 🟡 Blocker C — `ROUTER_AP3_PASS` fehlt in `~/.secrets/credentials.env` +### 1.3 ✅ Blocker C — `ROUTER_AP3_PASS` fehlte in `~/.secrets/credentials.env` + +> **Erledigt 2026-08-31.** `ROUTER_AP3_USER`/`_PASS` nachgetragen, Datei `chmod 600`, +> Sicherung `~/.secrets/credentials.env.bak-20260831`. Funktionsbeweis: REST-Auth gegen ap3 = **HTTP 200**. Vorhanden sind `ROUTER_AP1_PASS`, `ROUTER_AP2_PASS`, `CRS326_CORE_PASS`, `CRS326_BRANCH_PASS`. Das **ap3-Passwort steht nur in `~/.claude.json`** (MCP-Env) — gegen die globale Konvention From 452ea53576b926d1fdfe3e6f7f8fc510255068bf Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Mon, 31 Aug 2026 14:57:35 +0200 Subject: [PATCH 4/5] =?UTF-8?q?ROS=207.24.1=20auf=20allen=20fuenf=20nachge?= =?UTF-8?q?zogen=20+=20Handover=20fuer=20TP16.3=20=C2=A72/=C2=A73?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nautobot: ap1 stand als einziges Geraet noch auf SoftwareVersion 7.24 (ap2, ap3, core, branch waren bereits richtig) -> auf 7.24.1 gesetzt. Danach Backup/Intended/Compliance. Folge fuers Runbook: aus zwei container.npk wird EINE. Die Zwei-Dateien-Regel vom 08-30 war rein versionsbedingt, nicht modellbedingt - cAP ax und hAP ax^2 teilen sich die arm64-Pakete. §0, §2.1, §3.1 und Falle 1 umgeschrieben. Die Regel dahinter bleibt: npk muss exakt zur ROS-Version passen, sonst ist das Paket nach dem Reboot inaktiv - ohne Fehlermeldung. Neue Compliance-Drift, NICHT von dieser Session und bewusst offen gelassen: crs326-core / wifi-security - sec-main hat am Geraet ft-over-ds=no, Soll ist yes (ft=yes ist korrekt). Kam mit dem Upgrade. 134/135 compliant. Braucht eine Entscheidung (Geraet korrigieren oder neuen Wert zum Soll erklaeren) plus Re-Provisionierung der CAPs; ein Re-Baseline ohne Entscheid wuerde die Drift-Erkennung wertlos machen. Handover docs/handoff-20260831-tp16.3-vorort-session.md fuer die Folgesession (§2/§3: npk, device-mode, Container). Enthaelt u.a. den Hinweis, dass ein fehlschlagendes /tool fetch vom AP auf Caddy KEIN Fehler ist, sondern die SNI-Abweisung - und dass der TLS-Alert sogar beweist, dass die Freigabe traegt. Offen notiert, nicht angelegt: TP-Vorschlag "dedizierter untagged-VLAN99-Port je Switch" (Out-of-Band-Mgmt) - Auslöser war, dass crs326-branch beim Core-Neustart nicht erreichbar war. Wartet auf Christophs Okay. Co-Authored-By: Claude Opus 5 --- .../handoff-20260831-tp16.3-vorort-session.md | 126 ++++++++++++++++++ docs/teilprojekte.md | 18 ++- docs/tp16.3-bf-public-ap-connector-runbook.md | 48 ++++--- 3 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 docs/handoff-20260831-tp16.3-vorort-session.md diff --git a/docs/handoff-20260831-tp16.3-vorort-session.md b/docs/handoff-20260831-tp16.3-vorort-session.md new file mode 100644 index 0000000..b954b04 --- /dev/null +++ b/docs/handoff-20260831-tp16.3-vorort-session.md @@ -0,0 +1,126 @@ +# Handover — TP16.3 §2/§3: Container-Bootstrap auf ap1 / ap2 / ap3 + +> **Erstellt 2026-08-31.** Zielsession: **§2 + §3** des Runbooks +> [`tp16.3-bf-public-ap-connector-runbook.md`](tp16.3-bf-public-ap-connector-runbook.md) — also +> `container.npk`, `device-mode`, Container. **Vor Ort**, weil jeder AP eine physische Bestätigung braucht. +> **Nicht** Teil dieser Session: der CNAME-Flip (§5) und der Abbau (§6). + +--- + +## 1. Was vor dir schon erledigt ist — nicht nochmal machen + +| Punkt | Stand | Nachweis | +|---|---|---| +| **§1 Blocker A** — Redirect-Schleife | ✅ 08-31 | `bf-public`-Ingress → `https://10.40.20.40:443`, `originServerName` je Regel, zurückgelesen | +| **§1 Blocker B** — Firewall `vlan99→vlan20:443` | ✅ 08-31 | via **Nautobot GC** (Plan `Completed`), Position vor `TP5: log-only` geprüft, funktional gegen Caddy nachgewiesen | +| **§1 Blocker C** — `ROUTER_AP3_PASS` | ✅ 08-31 | in `~/.secrets/credentials.env`, REST-Auth ap3 = **200** | +| Rollback-Snapshots | ✅ 08-30 | `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json` | +| ROS-Upgrade auf **7.24.1** | ✅ 08-31 (User) | alle fünf Geräte, RouterBOOT mitgezogen | + +**⇒ Die Session startet bei §2.** Wer §1 nochmal anfasst, arbeitet doppelt. + +--- + +## 2. Die eine Änderung gegenüber dem Runbook-Stand vom 08-30 + +🎉 **Aus zwei `container.npk` ist eine geworden.** Christoph hat am 08-31 alle fünf MikroTik auf +**7.24.1** gehoben. Damit gilt: + +``` +container-7.24.1-arm64.npk → ap1, ap2 UND ap3 +``` + +Die frühere Zwei-Dateien-Regel war **rein versionsbedingt**, nicht modellbedingt — `cAP ax` und +`hAP ax²` teilen sich die `arm64`-Pakete. Das Runbook ist entsprechend umgeschrieben (§0, §2.1, §3.1, +Falle 1). + +⚠️ **Die Regel dahinter bleibt:** `container.npk` muss **exakt** zur ROS-Version passen. Vor dem Upload +`/system resource print` lesen. Falsche Datei ⇒ Paket ist nach dem Reboot **inaktiv, ohne Fehlermeldung**. + +--- + +## 3. Ablauf in einem Satz + +Je AP, **einzeln und vollständig, Reihenfolge ap3 → ap2 → ap1**: +`.npk` hoch → Reboot (SSH, **nicht** MikroMCP) → `/system package print` prüfen → +`device-mode update container=yes` → **physisch bestätigen** → `veth` + `masquerade` → +`.tar` hoch → `/container add file=` → starten → `.tar` löschen → Log prüfen. + +**Details, Befehle und Abnahmekriterien: Runbook §2 und §3.** Hier nur die Punkte, die man +erfahrungsgemäß falsch macht: + +- 🔴 **Reboot geht NICHT über MikroMCP** (ROS kennt den `delay`-Parameter nicht, `run_command` ist für + `/system reboot*` gesperrt) → `SSHPASS="$PW" sshpass -e ssh admin@ "/system reboot"`. + **Niemals `sshpass -p`** — das schreibt das Passwort in `ps`. +- 🔴 **`~/.secrets/credentials.env` in zsh sourcen schlägt still fehl** (rohe WireGuard-Zeilen ab ~Z. 69). + Variablen bleiben **leer**, und der Fehler sieht aus wie ein ungültiger Token. **Immer `bash -c`.** +- 🔴 **Kein PoE an den CRS326** (beide haben gar kein PoE-Menü) ⇒ kein Remote-Power-Cycle. Die + Bestätigung ist wirklich physisch: **Stecker ziehen/stecken** (Weg A, eindeutig) oder Reset-Knopf am + *laufenden* Gerät kurz drücken (Weg B). Weg B **nie während des Bootens** — das wäre ein Werksreset. +- 🔴 **Ein Container pro AP.** Nach dem Import bleiben ~39 MB. Kein Caddy, kein AGH dazu. +- ⚠️ **Jeder AP rebootet zweimal** → WLAN dieses APs ist je ~1–2 min weg. Die CAPs holen ihre Config + per L2-Discovery vom Core. **Nicht ungeduldig nachprovisionieren.** +- ⚠️ **WLAN-Abnahme ist eine frische DHCP-Lease je SSID, nicht die Registration-Table.** + +**Erwartetes Ergebnis am Ende:** `bf-public` meldet `healthy` mit Connectoren (Richtung 12 = 3 APs × 4 +CF-Edge). 🔴 **`healthy` heißt nur „Connector redet mit Cloudflare", nicht „Origin antwortet"** — der +Beweis für die Strecke bis Caddy ist §5.1, und der gehört in die **Folge**session. + +--- + +## 4. Was du unterwegs sehen wirst und **nicht** als Fehler behandeln darfst + +**`/tool fetch` von einem AP auf `https://10.40.20.40` schlägt mit einem TLS-Alert fehl.** +Das ist **korrekt und erwartet**: Caddy weist Handshakes ohne passenden SNI ab (gemessen: ohne SNI +`000`, mit korrektem SNI `200`). Der Alert **beweist sogar**, dass das Paket ankommt — ein TLS-Alert +kann nur vom Server kommen, die Firewall-Freigabe trägt also. cloudflared schickt den richtigen SNI +über `originServerName`. **Nicht „reparieren".** + +--- + +## 5. Offene Punkte, die NICHT in diese Session gehören + +1. 🔴 **Compliance-Drift `crs326-core` / `wifi-security` (134/135).** `sec-main` hat am Gerät + `ft-over-ds=no`, Soll `yes` (`ft=yes` ist korrekt). **Kam mit dem 7.24.1-Upgrade.** Braucht eine + Entscheidung — Gerät korrigieren *oder* neuen Wert zum Soll erklären — plus Re-Provisionierung der + CAPs. ⚠️ **Kein Re-Baseline ohne Entscheid**, das macht die Drift-Erkennung wertlos. +2. 🆕 **TP-Vorschlag (Christoph 2026-08-31, noch nicht angelegt): dedizierter untagged-VLAN99-Port + auf beiden Switches.** Auslöser ist eine reale Beobachtung: **beim Core-Neustart war `crs326-branch` + nicht mehr erreichbar**, weil das Management über den Core läuft. Ein Out-of-Band-Zugang je Switch + (ein Port, untagged VLAN99, Laptop direkt) macht den Branch unabhängig vom Core. + **Bewusst nicht eigenmächtig angelegt** (TP-Management-Konvention: vorschlagen, abstimmen, dann + anlegen). ⇒ Nummer vergeben und Abschnitt schreiben, sobald Christoph zustimmt. + 💡 Berührt TP16.3 direkt: die Vor-Ort-Session rebootet dreimal Geräte, deren Mgmt am Core hängt. +3. 🟡 **`mcp/mikromcp/config/routers.yaml` hält `rosVersion: "7.24"`** für alle fünf — jetzt `7.24.1`. + ⚠️ **Die Datei liegt auf `main`, nicht auf diesem Branch** (der Worktree hat eine ältere Kopie + **ohne** ap3). Der Fix gehört auf `main`, nicht hierher — sonst gibt es einen Konflikt. +4. **CNAME-Flip (§5) und Abbau (§6)** — eigene Session, §6 frühestens 7 Tage nach dem Flip. + +--- + +## 6. Zugänge und Werkzeuge + +| Was | Wie | +|---|---| +| APs | MikroMCP `ap1`/`ap2`/`ap3` (Reads), SSH `admin@10.40.99.11/.12/.13`, Creds `ROUTER_AP{1,2,3}_PASS` in `~/.secrets` | +| Reboot | `bash -c 'set -a; . ~/.secrets/credentials.env; set +a; SSHPASS="$ROUTER_AP3_PASS" sshpass -e ssh admin@10.40.99.13 "/system reboot"'` | +| Config-Änderungen an MikroTik | **ausschließlich Nautobot GC** (`nautobot/gc-manual-plan.py`), Token aus `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored, **nur im Haupt-Checkout**) | +| Cloudflare | `CF_API_TOKEN` / `CF_ACCOUNT_ID` in `~/.secrets`, Zone `1b5bc02fdf61b6b989afde543f3f4134` | +| Caddy | `caddy-bf1`, LXC 130 auf pve, `10.40.20.40`; Logs `ssh root@10.40.20.100 'pct exec 130 -- journalctl -u caddy -n 50'` | + +⚠️ **`device-mode`- und Container-Schritte sind KEINE Golden-Config-Sache** — sie sind Bootstrapping am +Gerät (wie die genehmigte Ausnahme #2 beim CRS326-Bring-up). Die GC-Pflicht gilt für +`/ip firewall`, `/interface` & Co. **Wenn die Session eine Firewall- oder Interface-Zeile braucht: +über `gc-manual-plan.py`, nicht per Direkt-Write.** + +--- + +## 7. Abnahme dieser Session + +- [ ] `/system package print` zeigt auf **allen drei** APs `container` in **7.24.1**, nicht disabled +- [ ] `/system device-mode print` zeigt `container: yes` auf allen dreien +- [ ] `/container print` zeigt je AP **einen** Container `running` +- [ ] `.tar` je AP gelöscht (Flash zurück) +- [ ] `bf-public` meldet `healthy` mit Connectoren > 0 +- [ ] WLAN je AP zurück — **frische DHCP-Lease pro SSID**, nicht Registration-Table +- [ ] Compliance nicht schlechter als **134/135** (die `wifi-security`-Drift aus Punkt 5.1 bleibt bestehen) diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index b331f1e..fdbd37f 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -40,6 +40,13 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d unterstellte einen Registry-Pull (`fetch: no` auf ap1/ap2) und enthielt eine **falsche** Firewall-Freigabe-Behauptung. **CF-Stand:** `bf-public` `inactive` / 0 Connectoren; die vier öffentlichen Namen laufen noch über bf-mm/bf-nas/bf-pve **an Caddy vorbei** direkt aufs Backend. +- 🆕 **ROS-Upgrade 7.24.1 auf allen fünf (2026-08-31, User).** In Nautobot nachgezogen: **ap1** stand + als einziges Gerät noch auf `7.24` (die anderen vier waren schon richtig) ⇒ auf `7.24.1` gesetzt. + 🔴 **Eine Drift kam mit dem Upgrade: `crs326-core` / `wifi-security`** — `sec-main` hat am Gerät + `ft-over-ds=no`, Soll ist `yes` (`ft=yes` steht korrekt). **134/135 compliant.** Bewusst **nicht** + angefasst: das ist eine Roaming-Einstellung und braucht eine Entscheidung (Gerät korrigieren **oder** + den neuen Wert zum Soll erklären) plus Re-Provisionierung der CAPs — **nicht nebenbei**. + ⚠️ Ein Re-Baseline ohne Entscheid würde die Drift-Erkennung wertlos machen. - 🆕 **TP16.3 §1-Blocker remote erledigt (2026-08-31):** Ingress von `bf-public` auf `https://10.40.20.40:443` + `originServerName`; Firewall-Freigabe `vlan99 → vlan20:443` **über Nautobot GC** (Plan `Completed`, Re-Baseline `ingest-core-firewall.py --apply`, danach @@ -47,11 +54,12 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d Vor-Ort-Session** (§2/§3: `container.npk`, `device-mode`, Container) und danach Flip + Abbau. - 🆕 **Globale Verhaltensregeln konsolidiert (2026-08-29, TP35 Schicht 3):** `~/.config/opencode/AGENTS.md` ist jetzt die **SSoT** für globale Verhaltensregeln (Secrets, VLAN-Schema, 5 Lessons, SoC, Übergabe-Mechanik, Mid-Action-Checkpoint, Commit-Hinweis, Backup-Guard). `~/.claude/CLAUDE.md` hält denselben Block als Claude-Code-Spiegelung mit Verweis auf die SSoT. opencode liest AGENTS.md mit Vorrang vor `~/.claude/CLAUDE.md`. -- **Fünf MikroTik seit 2026-08-30** — neu dazu **`ap3`** (cAP ax `cAPGi-5HaxD2HaxD`, **`10.40.99.13`**, - Uplink core `ether21`, in der MikroMCP-Registry). ⚠️ **ap3 läuft `7.24.1`, die anderen vier `7.24`** - (live gelesen 08-30) — für versionsgebundene Pakete (`container.npk`) sind das **zwei** Dateien, - nicht eine. RouterBOOT jeweils = `upgrade-firmware`. - *(Hier stand bis 08-29 „Alle vier MikroTik laufen auf RouterOS 7.24" — mit ap3 überholt.)* +- **Fünf MikroTik, alle auf RouterOS `7.24.1`** (User-Upgrade 2026-08-31, live gegengelesen; + RouterBOOT jeweils = `upgrade-firmware` = `7.24.1`). Neu dabei **`ap3`** (cAP ax `cAPGi-5HaxD2HaxD`, + **`10.40.99.13`**, Uplink core `ether21`). + ✅ **Folge für TP16.3:** die versionsgebundene `container.npk` ist damit **eine** Datei für alle drei + APs (alle `arm64`) — die Zwei-Dateien-Regel vom 08-30 war rein versionsbedingt und ist erledigt. + *(Hier stand bis 08-29 „Alle vier MikroTik … 7.24" — durch ap3 und das Upgrade zweifach überholt.)* - **L3 + WAN-Edge = `crs326-core`** (CRS326-24G-2S+, Technik UG): Routing, DHCP (7 Server, 54 Reservierungen), DNS, NAT, **NTP-Server für den Standort** und der WG-Hub (TP10, Peers `fm`/`dw`) laufen hier. Mgmt-SVI **`10.40.99.1`** (vlan99), Gateway `.1` je VLAN. **WAN = `vlan100`** — DHCP-Client diff --git a/docs/tp16.3-bf-public-ap-connector-runbook.md b/docs/tp16.3-bf-public-ap-connector-runbook.md index eb1c408..4018b25 100644 --- a/docs/tp16.3-bf-public-ap-connector-runbook.md +++ b/docs/tp16.3-bf-public-ap-connector-runbook.md @@ -17,15 +17,21 @@ | AP | IP | Modell | Arch | ROS **live** | Flash frei | RAM frei | `device-mode` | `container` | `fetch` | |---|---|---|---|---|---|---|---|---|---| -| **ap1** | 10.40.99.11 | hAP ax² (`C52iG`) | arm64 | **7.24** | 95 MB / 128 | 686 MB | `home` | ❌ `no` | ❌ `no` | -| **ap2** | 10.40.99.12 | hAP ax² (`C52iG`) | arm64 | **7.24** | 95 MB / 128 | 694 MB | `home` | ❌ `no` | ❌ `no` | +| **ap1** | 10.40.99.11 | hAP ax² (`C52iG`) | arm64 | **7.24.1** | 95 MB / 128 | 712 MB | `home` | ❌ `no` | ❌ `no` | +| **ap2** | 10.40.99.12 | hAP ax² (`C52iG`) | arm64 | **7.24.1** | 95 MB / 128 | 699 MB | `home` | ❌ `no` | ❌ `no` | | **ap3** | 10.40.99.13 | **cAP ax** (`cAPGi`) | arm64 | **7.24.1** | 96 MB / 128 | 689 MB | **`basic`** | ❌ `no` | ✅ `yes` | -🔴 **Zwei Unterschiede, die je einen eigenen Arbeitsschritt kosten:** -1. **ap3 läuft 7.24.1, ap1/ap2 laufen 7.24.** Ein `container.npk` **muss exakt zur RouterOS-Version - passen** — sonst bleibt es nach dem Reboot schlicht deaktiviert. ⇒ **zwei verschiedene Downloads**, - nicht einer. Nicht aus Bequemlichkeit dieselbe Datei auf alle drei schieben. -2. **ap3 steht auf `device-mode=basic`**, nicht `home` — dort sind `fetch`/`scheduler` bereits erlaubt. +*(Versionsspalte am **2026-08-31** nachgelesen: Christoph hat alle fünf MikroTik auf **7.24.1** gehoben, +RouterBOOT mitgezogen. Bis 08-30 liefen ap1/ap2 auf 7.24 — daher stand hier vorher eine Zwei-Dateien-Regel.)* + +✅ **Seit dem Upgrade auf 7.24.1 (2026-08-31) reicht EINE `.npk` für alle drei APs** — alle sind `arm64` +und alle auf derselben Version. Die frühere Zwei-Dateien-Regel war **rein versionsbedingt**, nicht +modellbedingt: `cAP ax` und `hAP ax²` teilen sich die `arm64`-Pakete. +🔴 **Die Regel dahinter gilt weiter:** ein `container.npk` **muss exakt zur RouterOS-Version passen** — +sonst bleibt es nach dem Reboot deaktiviert. Wer künftig einen AP einzeln hebt, hat wieder zwei Dateien. + +🟡 **Ein Unterschied bleibt:** +- **ap3 steht auf `device-mode=basic`**, nicht `home` — dort sind `fetch`/`scheduler` bereits erlaubt. Das ändert am `container`-Flag nichts (auch dort `no`, auch dort physische Bestätigung), macht ap3 aber zum einzigen AP, der theoretisch aus der Registry ziehen könnte. **Wir nutzen das bewusst nicht** — gleicher `.tar`-Weg auf allen dreien, damit alle drei dasselbe Image fahren. @@ -201,19 +207,18 @@ in `bash -c '…'`. **Wer das übersieht, jagt einen „ungültigen Token", der ## §2 Vorbereitung am Schreibtisch (vor der Fahrt, mit Internet) -### 2.1 Die **zwei** `container.npk` holen — Versionen nicht mischen +### 2.1 Die `container.npk` holen — **eine Datei für alle drei APs** ```bash mkdir -p ~/tp16.3 && cd ~/tp16.3 -# ap1 + ap2 (7.24) -curl -fLO https://download.mikrotik.com/routeros/7.24/all_packages-arm64-7.24.zip -unzip -o -j all_packages-arm64-7.24.zip 'container-7.24-arm64.npk' -# ap3 (7.24.1) curl -fLO https://download.mikrotik.com/routeros/7.24.1/all_packages-arm64-7.24.1.zip unzip -o -j all_packages-arm64-7.24.1.zip 'container-7.24.1-arm64.npk' -ls -la container-*.npk +ls -la container-7.24.1-arm64.npk ``` -*(Beide URLs am 2026-08-30 mit `HTTP 200` verifiziert.)* +*(URL am 2026-08-30 mit `HTTP 200` verifiziert.)* + +⚠️ **Vor dem Upload gegenprüfen, dass das Gerät wirklich auf 7.24.1 steht** (`/system resource print`). +Stimmt die Version nicht, ist die Datei die falsche — und das merkt man erst nach dem Reboot. ### 2.2 cloudflared als `.tar` bauen — **Registry-Pull ist auf ap1/ap2 gesperrt** (`fetch: no`) @@ -265,13 +270,15 @@ printf "%s" "$T" > ~/tp16.3/bf-public.token; chmod 600 ~/tp16.3/bf-public.token' > sich ihre Config nach dem Boot vom Manager (`crs326-core`) per **L2-Discovery auf `vlan99`** — > das kommt von selbst zurück, braucht aber ~1–2 min. **Nicht ungeduldig nachprovisionieren.** -### 3.1 `.npk` hochladen — **die richtige Datei zum richtigen AP** +### 3.1 `.npk` hochladen | AP | Datei | |---|---| -| ap1 (`.99.11`) | `container-7.24-arm64.npk` | -| ap2 (`.99.12`) | `container-7.24-arm64.npk` | -| **ap3 (`.99.13`)** | **`container-7.24.1-arm64.npk`** | +| ap1 (`.99.11`) | `container-7.24.1-arm64.npk` | +| ap2 (`.99.12`) | `container-7.24.1-arm64.npk` | +| ap3 (`.99.13`) | `container-7.24.1-arm64.npk` | + +✅ **Seit 2026-08-31 dieselbe Datei überall** (alle drei `arm64` + 7.24.1). ```bash cd ~/tp16.3 @@ -554,8 +561,9 @@ physischem Gang. Es schadet nicht — ohne laufenden Container ist es ein gesetz ## §8 Fallen -1. **Zwei ROS-Versionen, zwei `.npk`.** ap1/ap2 = 7.24, ap3 = **7.24.1**. Falsche Datei ⇒ Paket bleibt - nach dem Reboot inaktiv. **Nach jedem Reboot `/system package print` lesen**, nicht annehmen. +1. **`container.npk` ist versionsgebunden.** Seit 08-31 sind alle drei APs auf 7.24.1 ⇒ **eine** Datei. + Weicht ein Gerät später ab, braucht es wieder seine eigene. Falsche Datei ⇒ Paket bleibt nach dem + Reboot **inaktiv, ohne Fehler**. **Nach jedem Reboot `/system package print` lesen**, nicht annehmen. 2. **Die Freigabe `vlan99 → vlan20:443` existiert nicht** — entgegen der Behauptung im Vorgänger-Runbook. Ohne §1.2 landet der Connector im `TP5-WOULD-DROP`-Log. **Erst dort nachsehen.** 3. **Caddy antwortet auf `:80` mit 308.** Ein Ingress auf `:80` erzeugt eine Redirect-Schleife — From d5b3ce37ff49de1a18147777b71a32386804e92e Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Mon, 31 Aug 2026 15:05:31 +0200 Subject: [PATCH 5/5] TP37 angelegt: Out-of-Band-Management, ein untagged-VLAN99-Port je Switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auslöser (User 2026-08-31): beim Core-Neustart war crs326-branch nicht mehr erreichbar, weil sein Management ausschliesslich ueber den Core laeuft. Live nachgemessen: auf dem Branch ist vlan99 tagged=ether1,ether24,bridge mit untagged="" - es gibt also keinen Weg zu 10.40.99.2, der nicht durch den Core fuehrt. Wer den Core zerkonfiguriert, verliert den Branch gleich mit und damit die Moeglichkeit, es vom Branch aus zu reparieren. Zweiter, unangenehmerer Befund: crs326-branch hat GAR KEINE input-Regeln (Kette leer). Sein gesamter Schutz besteht heute darin, dass VLAN99 nur ueber den Core erreichbar ist und dessen forward-Kette deny-by-default faehrt - die Erreichbarkeitsbeschraenkung IST die Zugangskontrolle. Damit ist ein untagged MGMT-Port ein physischer Generalschluessel, und das TP traegt eine explizite Auflage: Port disabled im Normalbetrieb ODER input-Regeln auf beiden Switches ODER physische Sicherung. Empfehlung im Abschnitt: die input-Regeln, weil sie eine Luecke schliessen, die unabhaengig von diesem TP besteht. Kandidat ether23 - auf core und branch beide running=false, also frei, und auf beiden Geraeten dieselbe Nummer. Abnahme im Abschnitt so formuliert, dass sie den Zweck prueft: Laptop an branch ether23, Core-Trunk gezogen, 10.40.99.2 muss antworten. Alles andere misst nur den Normalfall. Quick-Index (nach TP36 einsortiert), TP-Abschnitt und Ist-Stand mitgezogen; Handover 20260831 auf den angelegten TP umgestellt. Co-Authored-By: Claude Opus 5 --- .../handoff-20260831-tp16.3-vorort-session.md | 14 ++-- docs/teilprojekte.md | 78 +++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/docs/handoff-20260831-tp16.3-vorort-session.md b/docs/handoff-20260831-tp16.3-vorort-session.md index b954b04..7a2a74d 100644 --- a/docs/handoff-20260831-tp16.3-vorort-session.md +++ b/docs/handoff-20260831-tp16.3-vorort-session.md @@ -84,13 +84,13 @@ kann nur vom Server kommen, die Firewall-Freigabe trägt also. cloudflared schic `ft-over-ds=no`, Soll `yes` (`ft=yes` ist korrekt). **Kam mit dem 7.24.1-Upgrade.** Braucht eine Entscheidung — Gerät korrigieren *oder* neuen Wert zum Soll erklären — plus Re-Provisionierung der CAPs. ⚠️ **Kein Re-Baseline ohne Entscheid**, das macht die Drift-Erkennung wertlos. -2. 🆕 **TP-Vorschlag (Christoph 2026-08-31, noch nicht angelegt): dedizierter untagged-VLAN99-Port - auf beiden Switches.** Auslöser ist eine reale Beobachtung: **beim Core-Neustart war `crs326-branch` - nicht mehr erreichbar**, weil das Management über den Core läuft. Ein Out-of-Band-Zugang je Switch - (ein Port, untagged VLAN99, Laptop direkt) macht den Branch unabhängig vom Core. - **Bewusst nicht eigenmächtig angelegt** (TP-Management-Konvention: vorschlagen, abstimmen, dann - anlegen). ⇒ Nummer vergeben und Abschnitt schreiben, sobald Christoph zustimmt. - 💡 Berührt TP16.3 direkt: die Vor-Ort-Session rebootet dreimal Geräte, deren Mgmt am Core hängt. +2. 🆕 **[TP37](teilprojekte.md#tp37) — OOB-Management, angelegt 2026-08-31.** Je ein untagged-VLAN99-Port + auf beiden Switches (Kandidat `ether23`, auf core **und** branch frei). Auslöser: beim Core-Neustart + war `crs326-branch` nicht erreichbar, weil sein Mgmt **ausschließlich** über den Core läuft. + 🔴 Nebenbefund mit eigenem Gewicht: **der Branch hat gar keine `input`-Firewallregeln** — sein Schutz + ist heute allein die Erreichbarkeit über den gehärteten Core. + 💡 **Berührt diese Session, ist aber keine Voraussetzung:** hier rebooten dreimal Geräte, deren Mgmt + am Core hängt. Wer vor Ort ist, kann `ether23` schon mal beschriften — gebaut wird TP37 separat. 3. 🟡 **`mcp/mikromcp/config/routers.yaml` hält `rosVersion: "7.24"`** für alle fünf — jetzt `7.24.1`. ⚠️ **Die Datei liegt auf `main`, nicht auf diesem Branch** (der Worktree hat eine ältere Kopie **ohne** ap3). Der Fix gehört auf `main`, nicht hierher — sonst gibt es einen Konflikt. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index fdbd37f..d5c336d 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -40,6 +40,11 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d unterstellte einen Registry-Pull (`fetch: no` auf ap1/ap2) und enthielt eine **falsche** Firewall-Freigabe-Behauptung. **CF-Stand:** `bf-public` `inactive` / 0 Connectoren; die vier öffentlichen Namen laufen noch über bf-mm/bf-nas/bf-pve **an Caddy vorbei** direkt aufs Backend. +- 🆕 **TP37 angelegt (2026-08-31):** OOB-Management — je ein untagged-VLAN99-Port auf beiden Switches. + Auslöser: beim Core-Neustart war `crs326-branch` nicht erreichbar, weil sein Mgmt **ausschließlich** + über den Core läuft (`vlan99` dort `tagged=ether1,ether24,bridge`, kein untagged-Member). + 🔴 **Nebenbefund mit eigenem Gewicht: `crs326-branch` hat gar keine `input`-Firewallregeln** — sein + Schutz ist heute allein die Erreichbarkeit über den gehärteten Core. Das ist die Hauptauflage des TPs. - 🆕 **ROS-Upgrade 7.24.1 auf allen fünf (2026-08-31, User).** In Nautobot nachgezogen: **ap1** stand als einziges Gerät noch auf `7.24` (die anderen vier waren schon richtig) ⇒ auf `7.24.1` gesetzt. 🔴 **Eine Drift kam mit dem Upgrade: `crs326-core` / `wifi-security`** — `sec-main` hat am Gerät @@ -252,6 +257,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP34 | 🔜 | Caddyfile via Nautobot/GC (Stub, Inventur-Handoff 2026-08-27) | — | > | TP35 | ⏳ | MCP-Schichtung + 3-Schicht-CLAUDE.md + Backup-Guard (Schicht 1+3+6 ✅) | — | > | TP36 | 🔜 | SSoT via Nautobot für teilprojekte.md-Live-Daten + Datei-Aufteilung | — | +> | TP37 | 🔜 | OOB-Mgmt: untagged-VLAN99-Port je Switch | — | > | — | — | **34 archivierte TPs** in `teilprojekte-archiv.md` | — | ### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt @@ -1869,6 +1875,78 @@ kann in Ruhe nachziehen. - **Verzahnt mit:** TP22 (Nautobot-GC als Vorbild), TP35 (geht Hand in Hand — beide sind „SSoT + Konsolidierung"), `bf-infra/CLAUDE.md`-Lessons. - **Referenzen:** Lessons-Block-Eintrag „Manuell gepflegte Doku mit Live-Daten verliert gegen die Realität" · Handoff Session A 2026-08-28. + +### TP37 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch ▸ 🔜 **ANGELEGT 2026-08-31** + +> **Auslöser (User 2026-08-31, aus eigener Anschauung):** *„ich durfte beim Neustart vom core gerade +> erleben, dass ich nicht mehr auf den branch zum verbinden kam. weil das eben alles über den core +> läuft."* + +#### Der Befund, live nachgemessen (2026-08-31) +Das Management des Standorts hat **einen einzigen Weg**, und der führt über den Core: + +| | `crs326-core` | `crs326-branch` | +|---|---|---| +| Mgmt-IP | `10.40.99.1` (VLAN99-SVI) | `10.40.99.2` (`vlan99` auf der Bridge) | +| VLAN99 auf der Bridge | SVI + Trunks | **`tagged=ether1,ether24,bridge`**, `untagged=""` | +| Weg dorthin | direkt | **nur über `ether1` → core `ether8`** | + +⇒ **`crs326-branch` ist ausschließlich durch den Core hindurch erreichbar.** Rebootet der Core, ist der +Branch für die Dauer weg — nicht weil er ausfällt, sondern weil der einzige Pfad zu ihm fehlt. Dasselbe +gilt für jeden Fehler, der den Core-Trunk oder dessen VLAN99-Konfiguration betrifft. **Wer den Core +zerkonfiguriert, verliert den Branch gleich mit** — und damit die Möglichkeit, es vom Branch aus zu +reparieren. + +🔴 **Der zweite Befund ist der unangenehmere: `crs326-branch` hat KEINE `input`-Firewallregeln** (Kette +leer, live gelesen). Sein gesamter Schutz besteht heute darin, dass VLAN99 nur über den Core erreichbar +ist und **dessen** `forward`-Kette seit TP5 deny-by-default fährt. **Die Erreichbarkeitsbeschränkung +IST die Zugangskontrolle.** Das ist für die Lösung unten kein Detail, sondern die Hauptauflage. + +#### Zielbild +**Je Switch ein Port, untagged in VLAN99** — ein Laptop direkt daran erreicht das Gerät ohne jeden +Umweg über den Core, ohne Trunk, ohne funktionierendes Routing. + +``` +Laptop ──(statische IP 10.40.99.x/24)── ether23 (untagged VLAN99) ── crs326-branch 10.40.99.2 + ^ funktioniert auch, wenn der Core steht +``` + +**Kandidat `ether23` auf beiden Geräten** — auf core **und** branch heute `running=false`, also frei, +und symmetrisch (ein Port, eine Nummer, an beiden Standorten dieselbe). *(Core hat reichlich freie +Ports; auf dem Branch sind `ether23` und die Reihe darunter frei, `ether24` ist ap2.)* + +#### Umsetzung (Skizze — eigene Session) +1. **Port aus dem Trunk-Verhalten nehmen und in VLAN99 untaggen:** + `bridge port` mit `pvid=99` + `frame-types=admit-only-untagged-and-priority-tagged`, + dazu `ether23` als `untagged`-Member in die `bridge vlan`-Zeile für VLAN99. +2. **Kein DHCP dort** — der VLAN99-DHCP läuft auf dem Core, und genau der soll ja nicht Voraussetzung + sein. ⇒ **statische IP am Laptop** (`10.40.99.50/24`), im Runbook mitschreiben. +3. **Beschriftung.** Ein OOB-Port, den niemand erkennt, wird im Zweifel für „ist ja frei" gehalten. +4. **Abnahme, die den Zweck wirklich prüft:** Laptop an `ether23` des Branch, **Core-Trunk `ether1` + gezogen** (oder Core im Reboot) → `10.40.99.2` muss antworten. Alles andere misst nur den Normalfall. + +#### 🔴 Auflage, ohne die das TP nicht gebaut werden darf +Ein untagged-MGMT-Port ist **ein physischer Generalschlüssel**: wer ihn steckt, steht ungefiltert im +Management-VLAN eines Geräts, das (Branch) keinerlei `input`-Filter hat. Deshalb gehört **eine** der +folgenden Absicherungen dazu, und die Entscheidung darüber in die Umsetzungs-Session: +- **Port `disabled=yes` im Normalbetrieb**, im Bedarfsfall am *anderen* Gerät freigeschaltet — schützt, + kostet aber genau dann Zugriff, wenn das andere Gerät weg ist (halbierter Nutzen, ehrlich benennen); +- **oder** `input`-Regeln auf beiden Switches nachziehen (der Branch braucht sie ohnehin — das ist die + eigentliche Lücke, die dieses TP nebenbei aufgedeckt hat); +- **oder** rein physisch: abschließbarer Schrank, Port beschriftet und dokumentiert. + +⚖️ **Meine Einschätzung:** die `input`-Regeln sind der richtige Weg, weil sie eine Lücke schließen, die +**unabhängig von diesem TP** besteht. Der OOB-Port macht sie nur sichtbar. + +#### Abgrenzung +- **Nicht** Teil von TP16.3 — dort wird nur festgestellt, dass dreimal Geräte rebooten, deren Mgmt am + Core hängt. TP37 entschärft das für künftige Sessions, ist aber **keine Voraussetzung** dafür. +- **Nicht** VLAN-Schema-Änderung (TP32 bleibt unberührt), **nicht** Konsolen-/Serial-Zugang (das wäre + echtes OOB im engeren Sinn; hier geht es um ein zweites *Netz*-Bein). +- **Abh.:** TP5 (Firewall-Systematik, liefert das Muster für die `input`-Regeln), TP15 (Portbelegung + beider CRS326), TP22 (die Umsetzung läuft über Golden Config, `ip-firewall-filter` und + `bridge`-Sektionen stehen unter Drift-Kontrolle). + --- ## Abgeschlossene Teilprojekte ▸ [`teilprojekte-archiv.md`](teilprojekte-archiv.md)