TP16.3 F1 DURCH: gitea-mcp/n8n-mcp geschnitten + Doku nachgezogen

Der grosse Schnitt (803a287) hatte diese zwei Namen bewusst ausgelassen,
weil Werkzeug daran hing. F1 hat die Abhaengigkeit aufgeloest, danach
fiel der Schnitt:

  Ingress bf-mm  4 -> 2  (nur noch n8n + Catch-all)
  DNS            beide CNAMEs geloescht
  Gesamt         14 statt 12 Hostnames

Migriert wurden 12 Client-Configs (nicht 10 wie geschaetzt) - zwei
Worktree-Kopien in bf-network-r6{,.opencode} fand erst ein Sweep. Der
opencode.jsonc-Teil steckt bereits in fd05286: ich hatte den
Main-Checkout direkt editiert, damit opencode die IP sofort hat, und
die Parallelsession hat ihn mit eingesammelt.

Doku an vier Stellen nachgezogen (Ist-Stand, "Naechster Schritt",
Quick-Index, Follow-up F1) - die Drei-Stellen-Regel aus CLAUDE.md.

Befunde, jetzt im F1-Eintrag festgehalten:
- Claude Code und opencode haben VERSCHIEDENE Konfigpfade: .mcp.json
  ist pro Projekt (Claude Code), opencode liest EINE globale Datei.
  Deshalb reichte fuer opencode die eine Migration aus TP35 Schicht 1,
  waehrend Claude Code acht Einzeldateien brauchte.
- n8n-mcp laeuft NICHT ohne Bearer (process.exit(1) ohne AUTH_TOKEN);
  er ist der einzige echte Zugriffs-Bearer im Setup.
- gitea-mcp auf 10.40.20.167:3000 gebunden; Nutzen klein, da der
  macmini nur en0 hat.
- Geschnittene Namen antworten NOERROR + leer, nicht NXDOMAIN -
  gegengeprueft an den vier Namen aus dem grossen Schnitt.

Neu angelegt: F4 (n8n-mcp-Compose versoehnen - Container stimmt mit
keiner seiner drei Definitionsquellen ueberein, compose up waere
destruktiv) und F5 (Secret-Verteilung auf Container-Hosts; credentials.env
liegt nur auf dem MacBook, macmini hat kein ~/.secrets).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 12:25:01 +02:00
parent 709cc626bb
commit c82fb45f7f

View File

@@ -44,9 +44,10 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d
**und** DNS entfernt — `pve`/`portainer`/`pve-mcp`/`ha-mcp`/`ds-bf`/`ccu`/`iobroker`/`z2m25`/`pbs`
plus die drei CF-Access-SSH-Ziele. Ingress: `bf-mm` 6→4, `bf-nas` 6→3, `bf-pve` 9→2. **Öffentlich
bleiben nur `vw`/`hass`/`n8n`/`gitea`** (weiterhin HTTP 200). Intern unverändert über AdGuard→Caddy
(9× HTTP 200). ⚠️ **`gitea-mcp`/`n8n-mcp` bewusst stehen gelassen** — hängen noch in 10 Dateien
(8 fremde Projekte, `bf-infra/opencode.jsonc`, `~/.claude.json`); **TP35 Schicht 1 ist nur global
vollzogen, nicht auf Projekt-Ebene.** 🔴 **`bf-public` hat weiterhin 0 Connectoren** — er gehört
(9× HTTP 200). ✅ **Nachtrag 2026-08-30 (F1 durch): `gitea-mcp`/`n8n-mcp` sind jetzt AUCH
geschnitten** — damit **14 Hostnames** gesamt und `bf-mm` **4→2** (nur noch `n8n` + Catch-all).
Möglich wurde das, weil zuvor **12 MCP-Client-Configs** auf LAN-IP migriert wurden (nicht 10 wie
geschätzt: zwei Worktree-Kopien in `bf-network-r6{,.opencode}` kamen dazu). 🔴 **`bf-public` hat weiterhin 0 Connectoren** — er gehört
laut Entscheid auf **ap1/ap2**, dort fehlen aber `container`-Paket **und** `device-mode container=yes`
(physische Bestätigung nötig). Bis dahin stehen `bf-mm`/`bf-nas`/`bf-pve` mit **6 Containern**.
Backups: `docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json`.
@@ -121,8 +122,9 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d
**Nächster Schritt (Stand 2026-08-30): Vor-Ort-Session ap1/ap2** — sie ist der einzige Weg, den
Public-Strang auf `bf-public` zu heben und die drei Alt-Tunnel samt sechs Containern abzubauen
(TP16.3 Follow-up F2). Davor unabhängig machbar: **F1** (`gitea-mcp`/`n8n-mcp` in 10 Dateien auf
LAN-IP, vollendet TP35 Schicht 1) und die Announcement-Doku (TP35 Schicht 4).
(TP16.3 Follow-up F2). ✅ **F1 ist am 2026-08-30 durch** (12 Client-Configs auf LAN-IP, danach
`gitea-mcp`/`n8n-mcp` aus Ingress+DNS geschnitten) — davor unabhängig machbar bleibt nur noch die
**Announcement-Doku (TP35 Schicht 4)**, die den anderen Projekten die neuen MCP-URLs mitteilt.
**Vorherige Fassung (Stand 2026-08-28): drei Folge-Sessions parallel** (siehe `docs/handoff-20260828-mcp-bestand-und-performance.md`):
> - ✅ **Session A** — `/projektstatus`-Skill global für opencode (TP35 Schicht 6). Funktionsbeweis erbracht 2026-08-28.
@@ -232,7 +234,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 (12 Hostnames geschnitten 08-30; Abbau braucht Vor-Ort-Session) | — |
> | TP16.3 | ⏳ | cloudflared-Reduktion (14 Hostnames geschnitten 08-30, F1 durch; Abbau braucht Vor-Ort-Session) | — |
> | TP17.1 | ❌ | Shelly Gen4 → Zigbee | 2026-08-23 |
> | TP17.3 | ✅ | Shelly-Regression behoben | 2026-08-23 |
> | TP18 | 🔜 | Garage-Objektspeicher | — |
@@ -474,13 +476,15 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
Ingress-Bilanz: `bf-mm` 6→4 · `bf-nas` 6→3 · `bf-pve` 9→2. Catch-all in allen dreien geprüft letzte.
- **`pbs` und die SSH-Ziele standen nicht in der Kandidatenliste** — User-Entscheid 2026-08-30: beide
mesh-only. PBS erreicht man über `10.40.20.135:8007`, SSH direkt per IP, jeweils mit WARP.
- 🔴 **Zwei Namen bewusst NICHT geschnitten: `gitea-mcp` und `n8n-mcp`.** Die TP16.3-Vorprüfung
(„hängt Werkzeug am öffentlichen Namen?") schlägt hier an: `gitea-mcp.emsenhuber.net` steht in
**9 Dateien** — `~/dev/{bf-network-r6,bf-network-r6.opencode,blaueblueten-n8n,dachterrasse,ha,
ha-xstate,poulios-recherche,valamar}/.mcp.json` plus `bf-infra/opencode.jsonc` — und
`~/.claude.json` nutzt zusätzlich `n8n-mcp.emsenhuber.net`. **TP35 Schicht 1 hat nur die globale
`~/.config/opencode/opencode.jsonc` auf LAN-IP migriert**; Projekt-Ebene und `~/.claude.json` sind
zurückgeblieben. Erst migrieren, dann schneiden → Follow-up **F1** unten.
- ✅ **Zwei Namen zunächst NICHT geschnitten: `gitea-mcp` und `n8n-mcp` — am selben Tag per F1 nachgeholt.**
Die TP16.3-Vorprüfung („hängt Werkzeug am öffentlichen Namen?") schlug hier an:
`gitea-mcp.emsenhuber.net` stand in **11 Dateien** (nicht 9 — beim Edit kamen zwei
Worktree-Kopien dazu, s. F1) — `~/dev/{bf-network-r6,bf-network-r6.opencode,blaueblueten-n8n,
dachterrasse,ha,ha-xstate,poulios-recherche,valamar}/.mcp.json` plus `bf-infra/opencode.jsonc` —
und `~/.claude.json` nutzte zusätzlich `n8n-mcp.emsenhuber.net`. **TP35 Schicht 1 hatte nur die
globale `~/.config/opencode/opencode.jsonc` auf LAN-IP migriert**; Projekt-Ebene und
`~/.claude.json` waren zurückgeblieben. Regel „erst migrieren, dann schneiden" eingehalten:
**F1 hat migriert, danach fiel der Schnitt** (Ingress `bf-mm` 4→2, beide DNS-CNAMEs gelöscht).
- ✅ **Abnahme (2026-08-30), beide Richtungen gemessen:**
- **intern trägt weiter:** AdGuard löst alle neun Namen unverändert auf `10.40.20.40` (Split-Horizon,
TP31 Schritt 4), Caddy antwortet über HTTPS mit **HTTP 200** (`pve` 0,08 s · `portainer` 0,03 s ·
@@ -511,9 +515,34 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik
→ Runbook-Bedarf als Follow-up **F2**; bis dahin bleiben die drei Alt-Tunnel mit je 2 Connectoren stehen.
#### 📌 Follow-ups aus dieser Session (2026-08-30)
- **F1 — `gitea-mcp`/`n8n-mcp` FQDN→LAN-IP in 10 Dateien** (8 fremde Projekte + `bf-infra/opencode.jsonc`
+ `~/.claude.json`). Vollendet **TP35 Schicht 1**, entsperrt die letzten zwei CF-Hostnames.
⚠️ Projektübergreifend → gehört ins **Announcement** (TP35 Schicht 4), eigene Session.
- ✅ **F1 — `gitea-mcp`/`n8n-mcp` FQDN→LAN-IP, DURCH 2026-08-30.** Statt der geschätzten 10 waren es
**12 Dateien**: `~/.claude.json` (beide Namen), `bf-infra/opencode.jsonc`, 8× `dev/*/.mcp.json` und
**zwei Worktree-Kopien** in `bf-network-r6{,.opencode}/.claude/worktrees/frosty-hermann-76f9cd/`,
die erst ein Sweep fand. Danach fiel der Schnitt (s. oben). Backups je Datei.
- 🔑 **Die zwei Clients haben verschiedene Konfigpfade — das ist die Stelle, an der man Dateien übersieht.**
**Claude Code** liest `<projekt>/.mcp.json`, also **eine Datei pro Projekt**. **opencode** liest
**eine globale** `~/.config/opencode/opencode.jsonc`; projekt-lokal existiert nur
`bf-infra/opencode.jsonc`. Deshalb genügte für opencode die eine globale Migration (TP35 Schicht 1),
während Claude Code acht Einzeldateien brauchte. `.mcp.json` ist **kein** opencode-Pfad.
- 🔴 **`n8n-mcp` lässt sich NICHT ohne Bearer betreiben.** `dist/http-server.js` ruft
`process.exit(1)`, wenn `AUTH_TOKEN` fehlt („required for HTTP mode"). Damit ist er der **einzige
echte Zugriffs-Bearer** im Setup — `gitea-mcp` hat gar keine Auth, alles andere (`mikromcp`,
`hostinger`, `portainer`, `github`) sind Arbeits-Credentials der MCPs, kein Zugriffsschutz.
Der Wunsch „Bearer für alle weg" ist also faktisch schon erfüllt, bis auf den einen, der es nicht kann.
- 🔧 **`gitea-mcp` auf `10.40.20.167:3000` gebunden** (war `0.0.0.0:3000`). Zustandslos, kein Volume,
per `docker run` rekonstruiert; Token über die Prozessumgebung (`-e VAR` ohne Wert), **nicht** über
argv — dieselbe Klasse wie die `sshpass -p`-Falle. ⚠️ Der Nutzen ist klein: der macmini hat **nur
`en0`**, `0.0.0.0` war dort faktisch schon `.167`. `n8n-mcp` blieb deshalb auf `*:3001`.
- ⚪ **Tote Diagnose-Permissions entfernt:** `blaueblueten-n8n/.claude/settings.local.json` hatte 3×
`Bash(dig/host/curl … gitea-mcp.emsenhuber.net)` in der `allow`-Liste (10→7 Einträge). Das waren
**keine** MCP-Verbindungen — `enabledMcpjsonServers: ["gitea-mcp"]` gibt per **Name** frei und
blieb unberührt. Projektweit geprüft: nur dieses eine Projekt betroffen.
- 📌 **Nachweis-Korrektur zur Abnahme oben:** geschnittene Namen antworten öffentlich mit
**`NOERROR` + leerer Antwort**, nicht mit `NXDOMAIN` — gegengeprüft an `pve`/`portainer`/`ha-mcp`/`pbs`
aus dem großen Schnitt, die sich identisch verhalten. Gültiger Nachweis bleibt der zurückgelesene
Ingress plus die leere Antwort.
- ⚠️ Projektübergreifend → gehört ins **Announcement** (TP35 Schicht 4), eigene Session.
Dort mitnehmen: **„achte auf opencode"** sowie den neuen Pfad `~/dev/bf-infra` / Repo `admin/bf-infra`.
- **F2 — Vor-Ort-Runbook ap1/ap2:** `container`-Paket + `device-mode`, cloudflared-Image als `.tar`,
`bf-public`-Token, danach CNAME-Umhang der vier Public-Dienste und Abbau von `bf-mm`/`bf-nas`/`bf-pve`
samt der **sechs** Container (LXC400 `.126`: `cloudflared-bfmm`+`cloudflared-bfnas` · macmini `.167`:
@@ -522,6 +551,33 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik
Ebenso unberührt: Tunnel `fm.emsenhuber.net` (Standort FM).
- **F3 — CF-Connectoren in Nautobot abbilden** (User-Wunsch 2026-08-30, „durchgehende Netzwerkdoku
Stück für Stück"). Als **TP37** vorgeschlagen, noch nicht angelegt — wartet auf dein Okay.
- 🔜 **F4 — `n8n-mcp`-Compose versöhnen** (Befund aus F1, 2026-08-30; User-Entscheid: eigener Punkt,
nicht nebenbei). Der laufende Container stimmt mit **keiner** seiner Definitionsquellen überein —
ein `docker compose up -d` wäre deshalb **destruktiv**, es würde die interne n8n-Anbindung umstellen:
| Quelle | `N8N_API_URL` | Netz | Port |
|---|---|---|---|
| `.env` (macmini) | `http://10.40.41.167:5678` ← **totes 41.x-Schema** | — | `3000` |
| `docker-compose.yml` | `https://n8n.emsenhuber.net` (hartkodiert, ignoriert die `.env`) | `web` | `${HOST_PORT:-3000}` |
| **laufender Container** | `http://n8n:5678` ← korrekt, Container-DNS | `n8n_default` | `3001` |
Zu tun: Netz als `external: true` auf `n8n_default`, `N8N_API_URL` aus der Env statt hartkodiert,
Image auf den laufenden Digest pinnen (`sha256:2c8ff077…` = v2.57.1; heute `:latest` +
`pull_policy: always`, was bei jedem Recreate unkontrolliert hochzieht), Bind auf `10.40.20.167`.
⚠️ **`N8N_API_KEY` ist in allen drei Quellen verschieden**; der laufende und der aus
`~/.secrets/credentials.env` wurden beide gegen die n8n-API getestet (**HTTP 200**), der `.env`-Wert
ist ungetestet. Der Bearer dagegen ist überall identisch. Volume `n8n-mcp-deploy_n8n-mcp-data`
überlebt einen Recreate, `gitea-mcp` hat gar kein Compose (reiner `docker run`).
- 🔜 **F5 — Secret-Verteilung auf Container-Hosts klären** (offen aus F1). `~/.secrets/credentials.env`
ist SSoT, liegt aber **nur auf dem MacBook**; auf dem macmini existiert **kein `~/.secrets`**, und
dort laufen die Container. Ein Symlink ist deshalb physisch unmöglich. Zu entscheiden: schlanke
Ableitung mit nur den nötigen Keys je Host, oder Vollspiegelung. Zusammen mit **TP30** lösen — dort
liegt der verwandte Posten: `~/.claude.json` hält **5 Secrets im Klartext** (4 Router-PW,
Hostinger-Token), wo die opencode-Configs an denselben Stellen `{env:…}` nutzen.
🔴 **Falle beim Auslesen des Stores:** die Zeilen tragen **Inline-Kommentare**
(`CF_MM_TUNNEL_ID=7a32… # bf-mm …`). Naives `cut -d=` liefert einen 80-Zeichen-Wert, die CF-API
antwortet dann `7003 Could not route`. Ein globales `set -a; source` setzt die Variablen gar nicht
(`len=0`, Grund ungeklärt). Richtig ist gezieltes Lesen **mit** `sed -E 's/[[:space:]]+#.*$//'`.
- 📌 **Ein Argument des TP ist entfallen:** die HA-Degradierung, die §7 als Nebeneffekt anführte, ist am
**selben Tag repariert** (TP16 ✅). Der Fall steht damit allein auf dem eigentlichen Argument —
**weniger bewegliche Teile für Dienste, die öffentlich nicht gebraucht werden.**