5 Commits

Author SHA1 Message Date
dd12ab505b Korrektur: bf-infra HAT ein Gitea-Remote (Inventur-Aussage vom 08-27 ueberholt)
CLAUDE.md behauptete "lokal, kein Gitea-Remote (Working-Copy). Verloren bei
Festplattencrash." Live geprueft 2026-08-30:
  origin = https://gitea.emsenhuber.net/admin/bf-infra.git
  origin/main = b917691 (Handoff 2026-08-28)
Das Remote existiert also seit spaetestens 2026-08-28. Die Aussage war bei
der Inventur am 08-27 richtig und ist seither ueberholt -- nicht falsch
erhoben, sondern nicht mitgezogen.

Anlass: eine Session hat die Zeile uebernommen und "lokaler Master ohne
Gitea-Remote" behauptet, statt `git remote -v` zu tippen. Genau der Fall aus
dem Lessons-Block "Kommentar in Code != Verhalten". Merkregel deshalb an der
korrigierten Zeile verankert.

CLAUDE.md: Zeile direkt korrigiert (lebende Anweisungsdatei).
Handoff 2026-08-27: Originalzeile bewusst erhalten, Korrekturvermerk
darunter -- historische Fassungen werden hier nicht umgeschrieben.

Die Backup-Luecke bleibt real, hat aber eine andere Ursache als notiert:
nicht fehlende Struktur, sondern ungepushte Commits (am 08-30 waren es vier).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 11:43:24 +02:00
803a28772f TP16.3 grosser Schnitt: 12 Hostnames aus CF-Ingress + DNS entfernt
Public-Strang eingedampft. Entfernt aus Ingress UND DNS:
pve, portainer, pve-mcp, ha-mcp, ds-bf, ccu, iobroker, z2m25, pbs
sowie die drei CF-Access-SSH-Ziele 82unkpb5 / 3k23uow7 / j44d6tua.
Ingress-Bilanz: bf-mm 6->4, bf-nas 6->3, bf-pve 9->2, Catch-all je letzte.
pbs + SSH standen nicht in der Kandidatenliste -- User-Entscheid 2026-08-30
"beide mesh-only".

Abnahme beidseitig gemessen:
- intern traegt weiter: AdGuard loest alle neun Namen auf 10.40.20.40,
  Caddy antwortet HTTPS 200 (pve 0,08s / portainer 0,03s / pbs 0,03s)
- oeffentlich weg: NXDOMAIN gegen 1.1.1.1
- Public-Strang unberuehrt: vw/hass/n8n/gitea weiterhin HTTP 200
Nachweis ist das Zuruecklesen der Ingress-Config, nicht die Frontend-Antwort
(Access antwortet vor dem Tunnel -- TP16.3-Regel).

Bewusst NICHT geschnitten: gitea-mcp + n8n-mcp. Die TP16.3-Vorpruefung
schlaegt an -- gitea-mcp.emsenhuber.net steht in 9 Dateien (8 fremde
Projekte + bf-infra/opencode.jsonc), ~/.claude.json nutzt zusaetzlich
n8n-mcp. TP35 Schicht 1 ist nur global vollzogen, nicht auf Projekt-Ebene.
Erst migrieren, dann schneiden -> Follow-up F1.

Blocker fuer den Abbau der drei Alt-Tunnel: bf-public hat 0 Connectoren.
Er gehoert laut Entscheid auf ap1/ap2, dort fehlen aber container-Paket
UND device-mode container=yes (physische Bestaetigung am Geraet, fetch=no).
Vor-Ort-Session noetig -> Follow-up F2. Bis dahin stehen bf-mm/bf-nas/bf-pve
mit sechs Containern (LXC400, macmini, NAS -- alle lokalisiert).

Doku an allen drei Stellen nachgezogen: Quick-Index, TP16.3-Abschnitt,
Ist-Stand-Block. TP37 (CF-Connectoren in Nautobot) als Vorschlag
eingetragen, Okay steht aus.

Rollback: docs/cf-backup/*-20260830-vor-tp16.3-schnitt.json

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 11:40:31 +02:00
39ba5da6b0 TP31: cf-sync.py Parser+Logik-Fix, CF-Leichen geloescht, grafana-Ingress-Backup
- cf-sync.py: Multi-Hostname-Caddy-Bloecke geparst (27 statt 5 Sites),
  Vergleich CF->Caddy statt CF->Origin (TP31-Zielbild), *.bf.*-intern gefiltert
- CF: 6 CNAME- und 1 Ingress-Leiche geloescht (grafana, gateway-dw, nautobot,
  phpmyadmin.bf, speedtest.bf, z2m25.bf)
- Backup vor grafana-Removal
2026-08-29 19:35:53 +02:00
bd1ba5ed13 TP35 Schicht 3: globale Verhaltensregeln konsolidiert (AGENTS.md SSoT + CLAUDE.md Spiegel) 2026-08-29 18:55:12 +02:00
f1a8a4a3e7 TP35 Schicht 1: MCP-Migration FQDN→IP (gitea-mcp .167:3000, n8n-mcp .167:3001) + Ist-Stand/Quick-Index nachgezogen 2026-08-29 18:42:33 +02:00
10 changed files with 127 additions and 17 deletions

View File

@@ -13,7 +13,7 @@ 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 **mit Gitea-Remote**: `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git` (live verifiziert 2026-08-30). ⚠️ **Die frühere Zeile „lokal, kein Gitea-Remote → verloren bei Festplattencrash" ist überholt** — sie stammt aus der Inventur vom 2026-08-27 und war damals richtig; das Remote existiert seit spätestens 2026-08-28 (`origin/main` = `b917691`). **Die Backup-Lücke ist damit keine Struktur-, sondern eine Disziplinfrage: `git push`.** Am 2026-08-30 lagen vier Commits nur lokal. 🔴 **Merkregel:** diese Zeile hat eine Session dazu gebracht, „kein Remote" zu behaupten, statt `git remote -v` zu tippen — vor jeder Aussage über Remotes/Backups **live prüfen**.
- **`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.

View File

@@ -52,14 +52,16 @@ def caddy_sites():
sites, site = {}, None
with open(os.path.join(HERE, "Caddyfile"), encoding="utf-8") as fh:
for line in fh:
m = re.match(r"^([a-z0-9.-]+\.%s)\s*\{" % re.escape(ZONE), line)
# Block-Header: ein oder mehrere Hostnamen (kommagetrennt) vor "{"
m = re.match(r"^([a-z0-9.-]+(?:\s*,\s*[a-z0-9.-]+)*\.%s)\s*\{" % re.escape(ZONE), line)
if m:
site = m.group(1)
continue
if site:
m2 = re.search(r"reverse_proxy\s+(\S+)", line)
if m2:
sites[site] = m2.group(1)
for host in site.split(","):
sites[host.strip()] = m2.group(1)
site = None
return sites
@@ -94,16 +96,20 @@ def main():
drift = []
# 1) Ingress gegen Caddyfile
# 1) Ingress gegen Caddyfile.
# Zielbild (TP31): CF-Ingress zeigt auf Caddy (10.40.20.40), Caddy aufs Origin.
# Zeigt CF direkt auf ein Origin (nicht Caddy), ist das die alte Architektur = Drift.
for host, (tun, svc) in sorted(ingress.items()):
if svc.startswith("ssh://"):
continue
if host not in caddy:
drift.append(f"NUR-CF {host:<32} {tun}: {svc} (in Caddy nicht vorhanden)")
elif caddy[host] != svc:
drift.append(f"ABWEICHEND {host:<32} CF={svc} Caddy={caddy[host]}")
elif not svc.startswith(f"http://{CADDY_IP}"):
drift.append(f"ABWEICHEND {host:<32} CF={svc} (zeigt nicht auf Caddy {CADDY_IP})")
# Caddy-Sites ohne CF-Ingress: nur die suffixlosen Namen sind potenziell Drift.
# *.bf.*-Namen sind bewusst intern (Split-Horizon) und brauchen keinen Ingress.
for host, svc in sorted(caddy.items()):
if host not in ingress:
if host not in ingress and not host.endswith(".bf." + ZONE):
drift.append(f"NUR-CADDY {host:<32} {svc} (kein CF-Ingress — intern oder bewusst)")
# 2) CNAMEs, die auf einen Tunnel zeigen, aber keine Ingress-Regel mehr haben

View File

@@ -0,0 +1 @@
{"success":true,"errors":[],"messages":[],"result":{"tunnel_id":"7a322261-380a-447b-97d2-8ebce99f5ed1","version":4,"config":{"ingress":[{"service":"http://10.40.20.167:5678","hostname":"n8n.emsenhuber.net"},{"service":"http://10.40.20.167:3001","hostname":"n8n-mcp.emsenhuber.net"},{"service":"http://10.40.20.167:3000","hostname":"gitea-mcp.emsenhuber.net"},{"service":"ssh://10.40.20.167:22","hostname":"82unkpb5.emsenhuber.net"},{"service":"https://10.40.20.135:8007","hostname":"pbs.emsenhuber.net","originRequest":{"noTLSVerify":true}},{"service":"http_status:404"}],"warp-routing":{"enabled":false}},"source":"cloudflare","created_at":"2026-07-27T13:53:23.512528Z"}}

View File

@@ -0,0 +1,23 @@
{
"success": true,
"errors": [],
"messages": [],
"result": {
"tunnel_id": "220c9958-401b-4111-9451-f3db09bba2d5",
"version": 4,
"config": {
"ingress": [
{"service": "http://10.40.20.157:3000", "hostname": "gitea.emsenhuber.net"},
{"service": "http://10.40.20.109:3000", "hostname": "grafana.emsenhuber.net"},
{"service": "http://10.40.20.173:3000", "hostname": "pve-mcp.emsenhuber.net"},
{"service": "http://10.40.20.127", "hostname": "vw.emsenhuber.net"},
{"service": "http://10.40.20.10:8080", "hostname": "ds-bf.emsenhuber.net"},
{"service": "ssh://10.40.20.10:22", "hostname": "3k23uow7.emsenhuber.net"},
{"service": "http_status:404"}
],
"warp-routing": {"enabled": true}
},
"source": "cloudflare",
"created_at": "2026-08-24T08:28:18.806844Z"
}
}

View File

@@ -0,0 +1 @@
{"success":true,"errors":[],"messages":[],"result":{"tunnel_id":"220c9958-401b-4111-9451-f3db09bba2d5","version":6,"config":{"ingress":[{"service":"http://10.40.20.157:3000","hostname":"gitea.emsenhuber.net"},{"service":"http://10.40.20.173:3000","hostname":"pve-mcp.emsenhuber.net"},{"service":"http://10.40.20.127","hostname":"vw.emsenhuber.net"},{"service":"http://10.40.20.10:8080","hostname":"ds-bf.emsenhuber.net"},{"service":"ssh://10.40.20.10:22","hostname":"3k23uow7.emsenhuber.net"},{"service":"http_status:404"}],"warp-routing":{"enabled":true}},"source":"cloudflare","created_at":"2026-08-29T17:30:22.531419Z"}}

View File

@@ -0,0 +1 @@
{"success":true,"errors":[],"messages":[],"result":{"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"}}

View File

@@ -0,0 +1 @@
{"success":true,"errors":[],"messages":[],"result":{"tunnel_id":"f7fdf3a6-b67d-4567-93b1-b0f273794d09","version":3,"config":{"ingress":[{"service":"https://10.40.20.100:8006","hostname":"pve.emsenhuber.net","originRequest":{"noTLSVerify":true}},{"service":"ssh://10.40.20.100:22","hostname":"j44d6tua.emsenhuber.net"},{"service":"http://10.40.20.232","hostname":"ccu.emsenhuber.net"},{"service":"http://10.40.20.107:8081","hostname":"iobroker.emsenhuber.net"},{"service":"http://10.40.20.106:8080","hostname":"z2m25.emsenhuber.net"},{"service":"https://10.40.20.126:9443","hostname":"portainer.emsenhuber.net","originRequest":{"noTLSVerify":true}},{"service":"http://10.40.20.105:8123","hostname":"hass.emsenhuber.net"},{"service":"http://10.40.20.105:9583","hostname":"ha-mcp.emsenhuber.net"},{"service":"http_status:404"}],"warp-routing":{"enabled":true}},"source":"cloudflare","created_at":"2026-07-16T11:36:24.193655Z"}}

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,7 @@
## TL;DR
- `bf-infra` ist **aktiver Fork** von `bf-network-r6`; aktiv gepflegt, **kein Gitea-Remote** → Backup-Lücke.
- 🔴 **Korrektur 2026-08-30 (Befund steht, Schlussfolgerung überholt):** das Remote existiert inzwischen — `origin` = `https://gitea.emsenhuber.net/admin/bf-infra.git`, `origin/main` = `b917691` (2026-08-28). Der Satz oben war am 08-27 zutreffend und ist **bewusst erhalten**; er darf aber nicht mehr als Ist gelesen werden. Die Backup-Lücke schließt ein `git push`, kein neues Repo.
- `admin/bf-network-r6` (Gitea) = historischer Master, letzte aktive Commits 2026-08-25 → auf read-only stellen (deine Aktion).
- `admin/bf-golden-config` (Gitea) = Bot-Master (Nautobot BACKUP/GENERATE 2×/Tag) → kein lokaler Mirror.
- `admin/blaueblueten-n8n` (Gitea) = Master für terpene-Repo, aktiv.

View File

@@ -32,6 +32,18 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
*(Die Fassung vom 2026-08-16 stand bis zum 18. hier und war an vier Stellen überholt. Wer diesen Block
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).
- 🆕 **CF-Public-Strang eingedampft (2026-08-30, TP16.3 grosser Schnitt):** **12 Hostnames** aus Ingress
**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
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`.
- 🆕 **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).
@@ -94,7 +106,12 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d
</details>
**Nächster Schritt (Stand 2026-08-28): drei Folge-Sessions parallel** (siehe `docs/handoff-20260828-mcp-bestand-und-performance.md`):
**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).
**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.
> - 🔜 **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).
@@ -198,7 +215,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 | ⏳ | cloudflared-Reduktion (12 Hostnames geschnitten 08-30; Abbau braucht Vor-Ort-Session) | — |
> | TP17.1 | ❌ | Shelly Gen4 → Zigbee | 2026-08-23 |
> | TP17.3 | ✅ | Shelly-Regression behoben | 2026-08-23 |
> | TP18 | 🔜 | Garage-Objektspeicher | — |
@@ -212,8 +229,9 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
> | TP32 | 🟢 | Nummern-Systematik (Konzept) | — |
> | TP33 | 💡 | Sonos fern (Idee, zurückgestellt) | — |
> | TP34 | 🔜 | Caddyfile via Nautobot/GC (Stub, Inventur-Handoff 2026-08-27) | — |
> | TP35 | ⏳ | MCP-Schichtung + 3-Schicht-CLAUDE.md + Backup-Guard (Schicht 6 ✅) | — |
> | 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 | 💡 | CF-Connectoren in Nautobot abbilden (**vorgeschlagen 2026-08-30, dein Okay fehlt**) | — |
> | — | — | **34 archivierte TPs** in `teilprojekte-archiv.md` | — |
### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt
@@ -406,7 +424,7 @@ 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) ▸ ⏳ **GROSSER SCHNITT DURCH (2026-08-30), Public-Strang blockiert** — `docs/tp16.3-nautobot-mesh-only-runbook.md §8`
- ▶️ **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.
@@ -426,9 +444,66 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na
- ⚠️ **Vorprüfung, die vor jedem weiteren Schnitt zu wiederholen ist:** hängt Werkzeug am
öffentlichen Namen? Hier nein — `gc-configplan.py` spricht `http://10.40.20.30:8080/api`, und
kein Repo-Skript nutzt den FQDN.
- ⏳ **Offen: das Betriebsfenster.** Eine Woche fahren (bis ~**2026-08-31**), dann entscheiden, ob es so
bleibt und ob `pve`/`portainer`/`grafana`/`pve-mcp`/`ha-mcp`/`gitea-mcp`/`ds-bf`/`ccu`/`iobroker`/`z2m25`
folgen.
- ✅ **Betriebsfenster ausgewertet 2026-08-30 (Tag 6/7): `nautobot` bleibt mesh-only.** Kein Rückweg
gebraucht, kein Ausfall. `nautobot.emsenhuber.net` hat inzwischen auch **keinen DNS-Record** mehr
(mit den CF-Leichen am 08-29 gelöscht, Commit `39ba5da`) — der Dienst ist vollständig aus dem
öffentlichen Strang heraus und ausschließlich über `10.40.20.30:8080` bzw. intern über Caddy erreichbar.
#### ▶️ Der grosse Schnitt — ausgefuehrt 2026-08-30 (User-Entscheid „kompletter Schnitt in einem Zug")
- **12 Hostnames entfernt**, aus Ingress **und** DNS, in einem Durchgang:
`pve` · `portainer` · `pve-mcp` · `ha-mcp` · `ds-bf` · `ccu` · `iobroker` · `z2m25` · `pbs`
und die drei CF-Access-SSH-Ziele `82unkpb5` (macmini) · `3k23uow7` (NAS) · `j44d6tua` (pve).
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.
- ✅ **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 ·
`ds-bf` 0,91 s · `z2m25` 0,03 s · `pbs` 0,03 s).
- **öffentlich weg:** `pve`/`portainer`/`ha-mcp`/`pbs` gegen `1.1.1.1` → **NXDOMAIN**.
- **Public-Strang unberührt:** `vw`/`hass`/`n8n`/`gitea` weiterhin **HTTP 200** über Cloudflare.
- Nach TP16.3-Regel ist der gültige Nachweis das **Zurücklesen der Ingress-Config**, nicht die
Antwort des Frontends — die Bilanz oben stammt aus dem `GET` nach dem `PUT`.
- 📦 **Rollback:** `docs/cf-backup/{bf-mm,bf-nas,bf-pve,bf-public}-ingress-20260830-vor-tp16.3-schnitt.json`
+ `docs/cf-backup/dns-emsenhuber.net-20260830-vor-tp16.3-schnitt.json` (alle `success:true`).
Ingress zurück = ein `PUT` je Tunnel; DNS zurück = CNAME auf `<tunnel-id>.cfargotunnel.com`, proxied.
#### 🔴 Blocker: der Abbau der drei Alt-Tunnel haengt an einer Vor-Ort-Session
Der Schnitt endet hier, weil `bf-public` **noch keinen Connector hat** (0 Verbindungen, angelegt
2026-08-27) und die vier Public-Dienste weiterhin auf `bf-mm`/`bf-nas`/`bf-pve` liegen. User-Entscheid
2026-08-30: der Connector gehört auf **ap1/ap2** (TP31-Begründung: der AP ist eine eigene Maschine,
kein Docker-Host — gegenüber macvlan/ipvlan sind wir dort nicht blind).
**Live gelesen am 2026-08-30 — der Preis ist nicht bezahlt:**
```
ap1: /system package print → routeros 7.24, wifi-qcom 7.24 (2 Pakete, KEIN container)
ap1: /system device-mode print → mode: home · container: no · fetch: no · scheduler: no
```
⇒ Es fehlt **beides**: das `container`-Paket (extra `.npk`, arm64) **und** `device-mode container=yes`,
und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik UG, ap2 Technik OG.
`fetch: no` heißt zusätzlich: das Image kann der AP nicht selbst ziehen, es muss als `.tar` hoch und
über `/container add file=` importiert werden. **Das ist eine Vor-Ort-Session, kein Remote-Schritt.**
→ 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.
- **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`:
`cloudflared-bfpve`+`cloudflared-bfnas` · NAS `.10`: `cloudflared-bfpve`+`cloudflared-bfmm`).
⚠️ `terpene-cloudflared` (LXC400) gehört zum Tunnel `terpene-db` und **bleibt** — anderes Projekt.
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.
- 📌 **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.**
@@ -1761,13 +1836,13 @@ kann in Ruhe nachziehen.
---
### TP35 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard ▸ 🔜 ANGELEGT 2026-08-28, Scope-Ausarbeitung eigene Session
### TP35 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard ▸ ⏳ **ANGELEGT 2026-08-28, Schicht 1 (FQDN→IP) + Schicht 3 (globale Regeln) + Schicht 6 (Skill) ✅**
- **Auslöser:** Bestandsaufnahme Session 2026-08-28 (Handoff `docs/handoff-20260828-pre-opencode-migration.md`). Erkenntnis: MCP-Configs sind heute über `~/.config/opencode/opencode.jsonc` UND `~/.claude.json` verstreut, ohne klares Schema.
- **Befund (geprüft):** 11/12 Backups im Verzeichnis `~/dev/.pre-opencode-migration-backups/20260828-150335/` gzip-valid; `uwe-job-bewerbung.tar.gz` korrupt (Source unverändert da). Repos und globale Configs md5-/HEAD-identisch zum Vor-Session-Stand. Synology-Drive-Sync aktiv (Anwender, manuell).
- **Scope:**
1. **MCP-Schichtung** (global/projekt/user) — wo lebt welche MCP-Definition, wie wird sie aufgelöst, wie bleibt sie synchron, wenn ein Projekt physisch auf einen anderen Host umzieht.
1. **MCP-Schichtung** (global/projekt/user) — wo lebt welche MCP-Definition, wie wird sie aufgelöst, wie bleibt sie synchron, wenn ein Projekt physisch auf einen anderen Host umzieht. **✅ 2026-08-29 (Teilschritt):** `gitea-mcp` + `n8n-mcp` in der globalen `opencode.jsonc` von CF-FQDN auf LAN-IP migriert (s. Ist-Stand-Block). `cloudflare`-MCP bleibt FQDN (User-Direktive).
2. **Credentials-Schichtung** — alle Secrets in `~/.secrets/exports.zsh`, MCP-Configs ausschließlich `{env:...}`. Schließt TP30 Posten #1 ab.
3. **3-Schicht-CLAUDE.md** — `CLAUDE.md` (öffentlich) / `CLAUDE.local.md` (gitignored) / `~/.claude/CLAUDE.md` (global).
3. **3-Schicht-CLAUDE.md** — `CLAUDE.md` (öffentlich) / `CLAUDE.local.md` (gitignored) / `~/.claude/CLAUDE.md` (global). **✅ 2026-08-29 (globale Schicht):** `~/.config/opencode/AGENTS.md` als SSoT angelegt (Secrets + VLAN-Schema + 5 Lessons + SoC + Übergabe-Mechanik + Mid-Action + Commit-Hinweis + Backup-Guard); `~/.claude/CLAUDE.md` hält denselben Block als Spiegel. **Offen:** die pro-Projekt-Schicht (`CLAUDE.md`/`CLAUDE.local.md` + Handover-Posts) — eigene Sessions.
4. **Announcement-Mechanismus (Hybrid)** — zentraler Ort (voraussichtlich `bf-infra/docs/announcements.md`) + Skill `/news` oder `/announce` zum Einlesen beim Session-Start. Initial: Breaking News über die MCP-Schichtung selbst.
5. **Backup-Guard** — `~/dev/.bin/backup-guard.sh`, Pre-Commit-Hook pro Projekt, „3 Tage aktiv"-Definition: 5 Commits oder 1000 LOC diff.
6. **`/projektstatus`-Skill global** — als Erstes, vor den anderen Schichten. Alle Projekte verwenden dasselbe Skill. **✅ 2026-08-28:** `/projektstatus`-Skill nach `~/.config/opencode/skills/projektstatus/SKILL.md` kopiert (`cp`, nicht `ln -s`) — opencode scannt diesen Default-Pfad, kein `skills.paths`-Eintrag in der JSONC nötig. Lehre im CLAUDE.md-Lessons-Block.