Handoff 2026-08-28 (Abends): terpene W1-W7 + globale MCP-Migration + CF-Token-Diagnose

Christophs Kontext explodiert am Sessionende. Handoff bündelt:
- CF-Token-Audit-Befund: CF_DNS_API_TOKEN ungültig (Code 1000 'Invalid API Token'),
  Christophs Verdacht 'falscher Token extrahiert' bestätigt.
- Globale MCP-Migration FQDN → IP für gitea-mcp + n8n-mcp; cloudflare bleibt
  extern (User-Direktive explizit). WARP-Pfad statt Bearer-Header wenn der
  Connector intern ohne Token lebt.
- terpene W5: n8n-mcp in blaueblueten-n8n/.mcp.json (User-Zustimmung).
- Domain-Korrektur: 420.teilzeitladen.xyz ist die echte terpene-Domain
  (NICHT terpene.teilzeitladen.xyz) — User-Direktive, gilt für alle.
- W1/W6 bleiben User-blocked, W7 wartet auf TP31-Schritt 5/6.

Dringend beim Nachfolger: CF-Token-Validität zuerst klären, dann erst
MCP-Migration. STATUS.md im terpene-Repo ist auf die falsche Domain
korrigieren.
This commit is contained in:
2026-08-28 23:51:46 +02:00
parent 733d911c03
commit b9176914e2

View File

@@ -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 (<https://dash.cloudflare.com/profile/api-tokens>) 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:** `<n8n-mcp>.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 <N8N_MCP_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=.*/=<set>/'` 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/=.*/=<set>/'` 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.