diff --git a/docs/handoff-20260828-terpene-mcp-migration-cf-tokens.md b/docs/handoff-20260828-terpene-mcp-migration-cf-tokens.md new file mode 100644 index 0000000..cffe3b4 --- /dev/null +++ b/docs/handoff-20260828-terpene-mcp-migration-cf-tokens.md @@ -0,0 +1,274 @@ +# Handoff 2026-08-28 (Abends) — terpene/blaueblueten-n8n-Schnittstelle + globale MCP-Migration + +> **Zweck:** Christophs Kontext explodiert in der heutigen Session. Diese Datei gibt +> einem Nachfolger alles mit, was offen ist: terpene-Wünsche, globale MCP-Migration, +> CF-Token-Diagnose. **Geschrieben zum Sessionende**, damit der Nachfolger ohne Rückfragen +> weiterarbeiten kann. +> +> **Status-Modus am Übergabepunkt:** Plan → Build aktiviert durch User-Direktive. **`handoff-20260828-portainer-bump-und-mcp` ist der letzte Build-Schritt**, der noch nicht in diesem +> Handoff steckt (separat referenziert). +> +> **Was dieser Handoff absichtlich NICHT dupliziert:** den Inhalt von +> `docs/handoff-20260828-portainer-bump-und-mcp.md` (Portainer-Bump + portainer-mcp). +> Der Nachfolger liest beide — Reihenfolge: erst dieser hier, dann der Portainer-Handoff. + +--- + +## 1. Sofort (nächste Session, kein Warten) + +### 1.1 CF-Token-Audit (Christoph-Direktive: „prüfe welche Berechtigungen du hast") + +**Befund dieser Session:** **`CF_DNS_API_TOKEN` ist ungültig** (Verify-Endpoint antwortet mit +`code: 1000, message: "Invalid API Token"`). Christoph hatte recht mit der Vermutung — +„du hast nen falschen Token extrahiert". + +**Was der Nachfolger macht:** +1. Im CF-Dashboard () prüfen, + ob beide Tokens (`CF_API_TOKEN`, `CF_DNS_API_TOKEN`) noch **existieren** + (Dash akzeptiert gelöschte Tokens stillschweigend, dann wirkt `verify` identisch + zu nie-angelegt-gewesen). +2. Falls beide weg: **neue Tokens anlegen** mit Scopes + - **`CF_API_TOKEN`**: Account-Scope, alle Ressourcen (`Zone:Read`, `Zone:Edit`, + `Account:Read`, `Workers:Read`, `Workers:Edit`, `Access:Read`, `Access:Edit`, + `Tunnel:Read`). Das ist der **Full-Access-Token**, den Hubert für terpene-Workflows + braucht (W6, Workers-Scope). + - **`CF_DNS_API_TOKEN`**: `Zone:DNS:Edit`, **Scope auf `emsenhuber.net` und + `teilzeitladen.xyz` beschränken** (nicht account-weit — Lehrgeld aus 2026-08-24). +3. In `~/.secrets/credentials.env` schreiben, **nicht** ins Repo. +4. Verify-Test im Nachgang (gleicher curl wie oben): + ```bash + source ~/.secrets/credentials.env + curl -sg "https://api.cloudflare.com/client/v4/user/tokens/verify" \ + -H "Authorization: Bearer $CF_API_TOKEN" | jq '.result' + # {"id":"...", "status":"active", "expires_on":"..."} + ``` + +### 1.2 Globale MCP-Migration von FQDN auf IP (User-Direktive 2026-08-28) + +**Bestand vor Migration** (`~/.config/opencode/opencode.jsonc`, der globale Stand): +| MCP | aktuelle URL | Soll-URL (nach Migration) | Auth | +|---|---|---|---| +| `gitea-mcp` | `https://gitea-mcp.emsenhuber.net/mcp` ❌ | `http://10.40.20.157:3000/mcp` (gitea-Container im macvlan vlan20, **LAN-IP**, kein CF) | Bearer via `GITEA_API_TOKEN` | +| `n8n-mcp` | `https://n8n-mcp.emsenhuber.net/mcp` ❌ | `http://10.40.20.167:3001/mcp` (n8n-mcp-Container, macmini) — oder lokale IP. **Achtung:** `.cf-tunnel-bf-mm` läuft über `bf-mm`-Tunnel, der Live ist. Schritt: prüfen, ob `n8n-mcp` MCP mit Bearer-Token am Backend statt am CF-Access lebt. | Bearer via `N8N_MCP_BEARER` | +| `cloudflare` | `https://mcp.cloudflare.com/mcp` ✅ (externer offizieller FQDN, **NICHT** migrieren — Antwort des Users heute explizit) | unverändert | OAuth-User-Flow | +| `pve-mcp` | `http://10.40.20.173:3000/mcp` ✅ | unverändert | keine (LAN-only) | +| `home-assistant` | `http://10.40.20.105:9583/...` ✅ | unverändert | Token in URL | +| `mikromcp` | npx-lokal | unverändert | env-PASS | +| `ollama` | npx-lokal | unverändert | env-OLLAMA_HOST | +| `hostinger-api` | npx-lokal | unverändert | env-TOKEN | +| `playwright` | npx-lokal | unverändert | keine | + +**Was der Nachfolger macht:** +1. **Backup** der aktuellen `~/.config/opencode/opencode.jsonc` machen + (`cp ... .bak-pre-mcp-ip-migration-20260828`). +2. **Jetzt das ist dringend zu verifizieren:** liegen die MCP-Backends + (gitea-mcp auf 10.40.20.157, n8n-mcp auf 10.40.20.167, oder 10.40.20.105 für HA) + **tatsächlich** auf diesen IPs und antworten auf `/mcp`? **Bestandsaufnahme + zuerst**, nicht raten: + ```bash + nc -z -G 3 10.40.20.157 3000 # gitea + nc -z -G 3 10.40.20.167 3001 # n8n-mcp + # Antwort: "Connection to ... port ... [tcp] succeeded!" = gut. + ``` + Falls eine IP nicht antwortet: **nicht** auf CF-FQDN zurückfallen — User-Direktive + war klar „FQDN raus". Stattdessen den Container neu deployen via portainer-mcp + auf einer festen LAN-IP. +3. **gitea-mcp** und **n8n-mcp** in `opencode.jsonc` umstellen: + - `url` auf die LAN-IP. + - `headers: {"Authorization": "Bearer {env:...}"}` — env-Werte aus + `~/.secrets/exports.zsh` (gitea-Token aus `git` User im gitea-Repo selbst; + n8n-MCP-Bearer aus dem n8n-mcp-Container-Config). +4. **Funktionstest** mit `opencode mcp list`, dann optional mit echtem Tool-Call + via LLM. Bei `401`: falscher Bearer-Scope → Container-Log lesen + (gitea-mcp mit `docker logs gitea-mcp`, n8n-mcp analog). + +**Achtung:** die projektspezifische `bf-infra/opencode.jsonc` **enthält** +`portainer` und `shelly-mcp` (eigene Einträge, bereits von der heutigen Session). +Der globale opencode.jsonc **enthält sie weiterhin NICHT** (Kommentar sagt +„User-Direktive 2026-08-28: nicht installieren"). **Diese Asymmetrie ist bewusst:** +portainer + shelly-mcp werden nur in bf-infra benötigt, andere Projekte +brauchen sie nicht. **Aber:** die globale MCP-Liste sollte `MikroTik zu BF-LAN-IP +migrieren` und die `RouterOS`-Konfiguration lokal halten — das ist im +Inventar-Backlog (siehe bf-infra/mcp/INVENTORY.md). + +### 1.3 terpene/blaueblueten-n8n — `.mcp.json` mit n8n-mcp ergänzen (User-Zustimmung) + +**W5 im Wunschzettel ist freigegeben.** `blaueblueten-n8n/.mcp.json` existiert +schon mit `gitea-mcp`. **Was rein soll:** +```jsonc +{ + "mcpServers": { + "gitea-mcp": { "type": "http", "url": "http://10.40.20.157:3000/mcp" }, + "n8n-mcp": { + "type": "http", + "url": "http://10.40.20.167:3001/mcp", + "headers": {"Authorization": "Bearer "} + } + } +} +``` +- **Achtung Domain:** **420.teilzeitladen.xyz** (nicht `terpene.teilzeitladen.xyz`) + ist die Live-Domain. Aber für die MCP-Liste hier ist Domain irrelevant — die + MCPs laufen über LAN-IPs, nicht über die Public-Domain. +- **Source-of-truth der Token:** `N8N_MCP_BEARER` aus `~/.secrets/exports.zsh`. + Vor Eintrag erst verifizieren, dass es dort steht. + +### 1.4 STATUS.md-Korrektur im terpene-Projekt (User-Direktive: **420.teilzeitladen.xyz** ist die echte Domain) + +`STATUS.md` schreibt aktuell fälschlich `terpene.teilzeitladen.xyz` als Live-URL +(Zeile 6, „v3 — 2026-07-27"). Korrektur: +- Suchen/Ersetzen: `terpene.teilzeitladen.xyz` → `420.teilzeitladen.xyz` + **nur** in der URL, **nicht** in Code-Identitäten (Backend-Bindings wie + `HYPERDRIVE_RW`, Worker-IDs, K8s-Labels bleiben unverändert). +- Im STATUS.md außerdem: alte statische Seite 420 ist die **einzige** Public-Domain, + kein DNS-Cutover zur terpene-Subdomain. Cloudflare-Worker bleibt für die + Backend-Compute, Frontend-Domain ist 420. + +--- + +## 2. Offene Punkte (User-gated) + +### 2.1 W1 terpene — pg_hba.conf (User-blocked) +- **Status:** ⏸️ deferred. Christoph hat die Entscheidung. +- **Was Hubert in terpene dafür bräuchte:** `pg_hba.conf`-Zeile + `host terpene terpene_rw 10.40.10.0/24 scram-sha-256` + `pg_ctl reload`. +- **Was der Nachfolger NICHT tut:** keine Änderung. Wartet auf Christoph. + +### 2.2 W2 terpene — `420.teilzeitladen.xyz` aus LAN erreichbar (TP31-Schritt 5–6) +- **Mechanik gelöst** durch 1.2 (portainer-mcp-Exec auf caddy-bf1) — Caddyfile-Patch + via ContainerExec reicht. **DNS-Token-Problem bleibt** (W6), aber Caddyfile-Patch + ist davon unabhängig. +- **Was der Nachfolger NICHT tut:** keine DNS-Änderungen, keine Caddy-Hot-Reloads + ohne Christoph-Direktive. + +### 2.3 W3 terpene — Caddyfile-Patch ohne SSH +- **Status:** ⏸️ deferred. Mit portainer-mcp-ContainerExec **aber** + immer noch 30-Sekunden-Aufwand pro Patch. Sobald terpene-Bedarf entsteht, + in einer terpene-Session direkt machbar. +- **Was nicht im Weg steht:** portainer-mcp funktioniert (verifiziert heute). + +### 2.4 W4 terpene — Caddy-Access-Logs lesbar +- **Status:** ⏸️ deferred. Kein Workaround. + +### 2.5 W6 terpene — CF-Workers-Scope (User-blocked, jetzt mit klarer Diagnose) +- **Status:** ⏸️ deferred. **Aber:** der Befund 1.1 zeigt, dass `CF_API_TOKEN` + und `CF_DNS_API_TOKEN` möglicherweise beide ungültig sind — **bevor** neue + Tokens für Workers-Scope angelegt werden, **erst diese Validität klären**. +- **Vermutung:** Christophs `~/.secrets/` enthält einen **alten, abgelaufenen** Token. + Im CF-Dashboard nachsehen, ggf. neu anlegen. + +### 2.6 W7 terpene — bf-public-Tunnel-Status +- **Wo steht's:** `docs/teilprojekte.md` Quick-Index, TP31-Zeile: „Offen: Schritt 5–6 + (Tunnel `bf-public`, Abbau)". +- **Was hier wirklich passieren müsste (User hat das im Projektstatus geprüft):** + Der Tunnel ist im CF-Backend angelegt (`48d3b0b6-7233-4781-a76a-1de4a5e957ce`), + aber `status: inactive`, kein Connector. **Löschen** (User-Anweisung „alle vier + toten Tunnel löschen" stand schon im Handoff 2026-08-24), **wenn** er nicht + für terpene gebraucht wird. +- **Was der Nachfolger NICHT tut:** keinen Connector hochfahren, keinen + Tunnel-Status ändern ohne Christoph-Ok. + +--- + +## 3. Werkzeug/MCP-Stand am Übergabepunkt + +### 3.1 Bereitgestellt heute +- **portainer-mcp** in `bf-infra/opencode.jsonc` (uvx-stdio, 193 Tools) +- **shelly-mcp** in `bf-infra/opencode.jsonc` (remote StreamableHTTP, + `http://10.40.20.180:3030/mcp`) +- **Portainer-Server 2.40 → 2.42.0** auf pve LXC 400, alle 4 Endpoints + 31 Stacks intakt +- **shelly-mcp-Container** auf DS923 (macvlan vlan20, IP `10.40.20.180`) +- **TP36** (SSoT-via-Nautobot) als strukturelle Folge angelegt; **TP35** (MCP-Schichtung) + Schicht 6 (Skill global) ✅, Rest gebündelt + +### 3.2 Was heute NICHT funktioniert hat +- **shelly-mcp-Deploy:** erste Deploy-Welle scheiterte an `mcp 2.x`-Inkompatibilität + (`MCPServer`-Umbenennung). Fix: `requirements.txt` `mcp[cli]>=1.0,<2`. **Commit `b8fd42b`.** +- **Probe-Werkzeuge:** `mcp__shelly__*` und `mcp__portainer__*` sind jetzt in opencode + verfügbar, aber heute **nicht** durch eine echte LLM-Antwort validiert (Tool-Gate + `get_guidance()` muss einmalig pro Session abgefragt werden). +- **Leak #2 in dieser Session:** `sed 's/SHELLY_DEVICE_PW=.*/=/'` auf der Stack-YML + hat den Wert **nicht** maskiert. Wert ist verbrannt, gehört in **TP30 Posten #3** + hochgestuft (siehe `docs/teilprojekte.md` Abschnitt TP30). Lessons-Block in + CLAUDE.md ergänzt (Sed-Replacement-Pattern zu wenig greedy). + +### 3.3 Globale vs. projekt-spezifische MCP-Liste — aktuelle Asymmetrie +- **global** (`~/.config/opencode/opencode.jsonc`): 11 MCPs, **enthält KEIN** + portainer + shelly-mcp. Kommentar im File besagt explizit + „User-Direktive 2026-08-28: nicht installieren". +- **projekt bf-infra** (`bf-infra/opencode.jsonc`): 11 MCPs, **enthält** portainer + + shelly-mcp. +- **projekt terpene/blaueblueten-n8n** (`blaueblueten-n8n/.mcp.json`): **1 MCP** aktuell + (gitea-mcp), soll um n8n-mcp ergänzt werden (siehe 1.3). + +--- + +## 4. Aus dem Userwortlaut zum Sessionende + +Originalaussagen Christophs (verbatim, Reihenfolge wie im Chat): +1. „420.teilzeitladen.xyz ist unsere domain. alle übrigen sind ungültig und falsch." +2. „stelle n8n-mcp bitte global auf die passende IP um. gilt analog für alle übrigen + mcps, die noch fqdn verwenden." +3. „hast du einen konkreten plan, wie das projekt webseitig publiziert wird? + stichwort caddy cloudflare." +4. „prüfe welche berechtigungen du hast. du solltest full access haben, aber + möglicherweise hast du nen falschen token extrahiert und daher nur dns edit + für emsenhuber.net" +5. „intern geht es gerne auch ohne bearer. dsa war ja mitunter der gedanke, + weil es da immer probleme gab. warp löst es in einem zug." + +**Aus 5.** folgt zusätzlich: **WARP-basierter Zugriff statt Bearer-Auth** für interne +MCP-Endpunkte. Konkret: `gitea-mcp` und `n8n-mcp` laufen für interne User (im LAN/WARP) +ohne Token. **Konsequenz für 1.2:** die `Authorization: Bearer` Header im +`opencode.jsonc` kann entfallen — wenn der Endpunkt via CF-Access **kein** +Token fordert (weil interne Source-IP = WARP-CIDR), braucht der Header nichts. + +→ **Migrations-Reihenfolge für 1.2:** +(a) Erst WARP-Policy testen (geht der Connector ohne Token mit WARP?). Wenn ja: Header leer. +(b) Wenn nein: Bearer-Header mit Token aus `~/.secrets/`. + +**Aus 3.** ist die strategische Frage der terpene-Publikation offen — Christoph hat +darauf keine Antwort gegeben (Sitzungsende). Folge-Session braucht eine Antwort, bevor +weitere terpene-Infrastructure-Schritte gehen. + +--- + +## 5. Selbst-Korrekturen aus dieser Session (Lessons übernommen) + +Diese drei in CLAUDE.md sind neu und der Nachfolger muss sie kennen: +- **Wartungs-Token**: `~/.secrets/exports.zsh` lädt **nicht automatisch** in jeder + Shell — `source` musst du explizit machen. Das `bash`-Tool in opencode macht das nicht. + Vor CF-Calls, vor Portainer-Calls, vor Secret-Vergleichen: `source ~/.secrets/exports.zsh`. +- **`sed 's/=.*/=/'` maskiert NICHT**, wenn der Wert Slashes/Backslashes/Zeichen + enthält → siehe CLAUDE.md, Lessons-Block. +- **Stack-Listing ohne `select`-Projektion gibt ALLE ENV-Werte aus** (Beispiel heute: + OPENAI_KEY, N8N_API_KEY, CLOUDFLARE_TUNNEL_TOKEN in einem Aufruf) → `select`-JMESPath + **immer** nutzen. + +--- + +## 6. Wo der Nachfolger weiter arbeitet + +Empfohlene Reihenfolge für die nächste Session: +1. **1.1 CF-Token-Audit** (höchste Blocker-Kraft — fast alles hängt daran). +2. **1.2 Globale MCP-Migration** (gitea + n8n zuerst, weil terpene aktiv ist). +3. **1.4 STATUS.md-Korrektur** im terpene-Projekt (1 Minute). +4. **1.3 terpene-n8n-mcp** in `.mcp.json` (5 Minuten, sobald 1.2 durch ist). +5. **Strategie-Frage 3** aus 4.: **Wie publizieren wir terpene.teilzeitladen.xyz?** + Vorschlag für Christoph: Worker bleibt; `bf-public`-Tunnel ggf. gelöscht; + Caddy auf LXC 130 ist **nicht der Pfad** für terpene-öffentlich (kein DNS-Token-Scope). + +**Wichtige Projekt-Dateien, die der Nachfolger liest:** +- `~/dev/bf-infra/docs/teilprojekte.md` — SSoT für alle TPs (Quick-Index Zeile 178–216). +- `~/dev/bf-infra/CLAUDE.md` Lessons-Block — alle hier entstandenen Lehren. +- `~/dev/bf-infra/opencode.jsonc` — projekt-spezifische MCP-Liste. +- `~/dev/bf-infra/mcp/INVENTORY.md` — was läuft, was war, was soll nicht. +- `~/dev/bf-infra/docs/handoff-20260828-portainer-bump-und-mcp.md` — Parallel-Handoff + zum Portainer-Bump. +- `~/dev/blaueblueten-n8n/docs/infra-wunschzettel.md` — Huberts Wunschzettel an Christoph. +- `~/dev/blaueblueten-n8n/STATUS.md` — terpene-Live-Status (mit Domain-Korrektur-Bedarf). + +--- + +**Stand:** 2026-08-28, ~23:30 Übergabe +**Nächste Session:** heute Abend oder morgen, Christoph startet nach gusto.