# Handoff — TP16.3 §2/§3 durch: drei Connectoren live, eine Entscheidung offen > **2026-08-31, Vor-Ort-Session.** Nachfolger von > [`handoff-20260831-tp16.3-vorort-session.md`](handoff-20260831-tp16.3-vorort-session.md). > **Nicht** Teil dieser Session: §5 (CNAME-Flip) und §6 (Abbau). --- ## 1. Was jetzt steht `bf-public` ist **healthy mit 12 Connectoren** (3 APs × 4 CF-Edges) — der Tunnel hatte vorher **null**. Je ein `cloudflared 2026.6.1` läuft auf ap1, ap2 und ap3. | | ap1 | ap2 | ap3 | |---|---|---|---| | `container`-Paket | 7.24.1 ✅ | 7.24.1 ✅ | 7.24.1 ✅ | | `device-mode container` | `yes` ✅ | `yes` ✅ | `yes` ✅ | | Container | `R` running | `R` running | `R` running | | `.tar` gelöscht | ✅ | ✅ | ✅ | | Flash frei | 54,0 MiB | 54,6 MiB | 55,0 MiB | WLAN auf allen drei zurück, CAPs `state=Ok` am Manager. Backups je AP liegen auf dem Gerät (`vor-tp16.3-apN.backup`) **und** lokal unter `~/tp16.3/backups/` — inklusive vollständigem `/export` (nicht der MCP-Export, s. Punkt 4). --- ## 2. 🔴 Die eine offene Entscheidung: Compliance steht auf 128/135 Nach vollem Refresh (Backup → Intended → Compliance, **nicht** die stale Zahl von vorher): | Gerät | Regel | Inhalt | |---|---|---| | ap1 / ap2 / ap3 | `ip-address` | `/ip address add address=172.17.0.1/24 interface=veth-cf` | | ap1 / ap2 / ap3 | `ip-firewall-nat` | `masquerade`, Kommentar `TP31: cloudflared egress` | | crs326-core | `wifi-security` | `ft-over-ds` — **offene Transaktion `TP25-fund-e-ft-over-ds`**, kein Versehen | **Ursache ist ein Widerspruch zwischen zwei Dokumenten**, kein Ausrutscher: Runbook §3.4 schreibt Direkt-Writes für `veth`/`ip address`/`nat` vor, die Governance stellt `/ip address` und `/ip firewall` unter GC-Pflicht. Ich bin dem Runbook gefolgt. ⚠️ **Korrektur 2026-08-31 (nachgetragen):** die siebte Drift wurde hier zuerst dem 7.24.1-Upgrade zugeschrieben — falsch. `gc-transaktion.py status` führt **`TP25-fund-e-ft-over-ds` als OFFEN** (gesetzt 08:09, Core-Upgrade erst 14:36). Sie ist Absicht einer laufenden Messung und braucht **keine** Entscheidung. **Merkregel: vor jeder Drift-Bewertung `gc-transaktion.py status` lesen** — Drift aus einem laufenden Versuch sieht identisch aus wie ein Versehen. **Die sechs Drifts sind inhaltlich richtig** — die Config *soll* dort stehen, ohne sie läuft kein Container. Der saubere Weg ist, sie ins **Soll** zu heben (AP-Template bzw. ConfigContext `ap-common`), damit die Compliance sie als konform sieht. 🔴 **Kein Re-Baseline** (macht die Drift-Erkennung wertlos) und **kein Rückbau** (killt die Tunnel). ⚠️ `/interface veth` erzeugt **keine** Drift — dafür existiert keine ComplianceRule. Wenn das Soll angepasst wird, gehört die Frage dazu, ob `veth` überhaupt erfasst sein soll. --- ## 3. 🔴 Drei Runbook-Fehler, die diese Session aufgedeckt hat **(a) Die Flash-Rechnung war um Faktor 2,5 zu optimistisch.** §3.5 rechnete mit „28 MB `.tar` + ~28 MB entpackt ≈ 39 MB Rest". Die 28 MB sind die **komprimierte** Archivgrösse; entpackt misst `cloudflare/cloudflared:2026.6.1` **72.926.208 B = 69,5 MiB**. Der Import braucht Tar **und** entpacktes Abbild gleichzeitig ⇒ 95,7 MiB gegen ~90 MiB frei. **Das offizielle Image passt auf keinen der drei APs.** ⚠️ **`docker image inspect .Size` führt in die Irre** (meldete 27.463.950). Für Flash-Budgets `docker export | wc -c` messen. **Lösung, ausgerollt:** eigenes `FROM scratch`-Image aus dem — nachweislich **statisch gelinkten** — Binary (36,8 MB) plus CA-Bundle: **35,3 MiB**, Spitzenbedarf 70,6 MiB. Build-Skript liegt jetzt im Repo (`tools/build-cfd-image.py`), ein Neubau ist **byte-identisch** (`f818bf40…`). 🔑 Der Pin ist die **Binary-SHA256 `e93824f6…`**, und cloudflared bestätigt sie beim Start selbst (`Version 2026.6.1 (Checksum e93824f6…)`) — im Log aller drei APs nachlesbar. **(b) `docker save` taugt seit Docker 25 nicht mehr als Quelle.** Mit containerd-Image-Store schreibt es OCI-Layout (`blobs/sha256/`, gzip-Layer); RouterOS will docker-v1 und meldet **`no config found in manifest`** — klingt nach kaputtem Image, ist das Format. (Konverter für den Fall, dass man doch ein Docker-Image braucht: `tools/oci2docker.py`, prüft die Layer-Digests gegen `rootfs.diff_ids`.) **(c) `/container/envs` heisst auf ROS 7.24.1 `list=`, nicht `name=`.** Mit `name=` kommt `bad parameter name` bzw. `unknown parameter name`. **Auch MikroMCPs `manage_container_env` benutzt `name=` und ist damit für dieses Feld unbrauchbar.** --- ## 4. Vier weitere Funde - 🔴 **Der `device-mode`-Bestätigungs-Boot installiert KEINE Pakete.** Auf ap1 lag die `.npk` schon bereit, als `device-mode` gesetzt wurde — der Stromzyklus hat `container: yes` bestätigt und die Datei **verbraucht, ohne zu installieren**. **Reihenfolge ist Pflicht: erst Paket-Reboot, dann `device-mode`.** (Nachholen ging per SSH, ohne zweiten Gang zum Gerät.) - 🟡 **`A - AVAILABLE` in `/system package print` ist ein Download-Katalog, kein Paket auf dem Gerät.** Gegenprobe: `crs326-core` hat 2,9 MB frei von 16 MB, listet aber `wifi-qcom` (10,1 MB) und `rose-storage` (4,3 MB) als „available"; die Grössen sind arch-spezifisch. `enable` löste einen Internet-Fetch aus — auf ap1/ap2 per `device-mode=home` gesperrt. **Der `.npk`-Weg bleibt richtig.** - 🟡 **MikroMCPs `export_config` exportiert unvollständig:** 301 Bytes gegen 6,2 KiB bei direktem `/export`. Ein Rollback-Artefakt, das existiert und trotzdem fast leer ist — dieselbe Klasse wie „Lücke statt Fehlermeldung". Für Backups `/export file=…` per SSH. - ⚠️ **WLAN-Abnahme nur teilweise belegbar:** `main` (3 Clients) und `iot` (27) haben frische Leases; `media`/`untrusted`/`guest` hatten **schon vor** dem Umbau null Clients. Kein Rückschritt, aber auch kein Beleg — nicht als „grün" buchen. --- ## 5. 🔴 Sicherheitsvorfall in dieser Session — Rotation nötig Beim Suchen des Nautobot-Tokens hat eine unzureichende `sed`-Maskierung (`grep -n … | sed 's/:.*TOKEN/…/' | cut -c1-60`) **zwei Werte im Klartext ins Transkript geschrieben**: - `NAUTOBOT_SUPERUSER_API_TOKEN` - `NAUTOBOT_GITEA_TOKEN` beide aus `docs/nas-vlan20/stacks/210-nautobot.yml`. **Als verbrannt behandeln → TP30.** Das ist exakt die Falle, die seit 2026-08-28 in der CLAUDE.md steht (der `SHELLY_DEVICE_PW`-Fall). **Merkregel, die offenbar noch nicht scharf genug formuliert war:** ein Maskierungs-Muster, das auf `:` ankert, matcht bei `grep -n` den **Zeilennummer-Präfix** statt den Wert. Sicher ist nur `sed -E 's/(TOKEN|PASSWORD|SECRET|KEY|PASS)=.*/\1=/'` — auf den **Schlüsselnamen** ankern, nie auf ein Trennzeichen, das auch im Präfix vorkommt. --- ## 6. Repo-Zustand - Branch `claude/nervous-williams-acf000` ist hier **hereingemergt**. Konflikte aufgelöst: - **Nummernkonflikt TP37**: `main` hatte TP37 = *Nautobot als Netzwerkdoku* (User-Okay 08-30), der Branch TP37 = *OOB-Mgmt*. Letzterer ist auf **TP38** umnummeriert. ✅ **Von Christoph bestätigt (2026-08-31).** - **Drei CF-Rollback-Snapshots hatten denselben Dateinamen bei verschiedenem Inhalt** (`bf-mm` v4/6 Regeln vs. v6/2 Regeln, analog `bf-nas`, `bf-pve`) — zwei Zeitpunkte, beide „vor-tp16.3-schnitt" genannt. **Keiner verworfen:** der ältere liegt jetzt als `*-vor-hostname-schnitt.json` daneben. - Neu: `tools/build-cfd-image.py`, `tools/oci2docker.py`. - `mcp/mikromcp/config/routers.yaml` meldet weiter `rosVersion: "7.24"` für alle fünf (live 7.24.1) — kosmetisch, unangetastet gelassen. --- ## 7. Nächste Schritte 1. **Entscheidung zu Punkt 2** (sechs Drifts ins Soll heben) — vor dem Flip, sonst geht man mit erklärter, aber ungeklärter Drift in den Produktivpfad. 2. **§5 CNAME-Flip**, einzeln mit Abnahme dazwischen, Testname zuerst (§5.1). ▸ Fertiger Übergabeprompt: [`tp16.3-paragraph5-session-handoff-prompt.md`](tp16.3-paragraph5-session-handoff-prompt.md). 🔴 `healthy` beweist nur, dass der Connector mit Cloudflare redet — **nicht**, dass das Origin antwortet. Der Streckenbeweis bis Caddy ist §5.1. 3. **§6 Abbau** frühestens 7 Tage nach dem Flip. 4. **TP30**: die zwei Tokens aus Punkt 5 rotieren.