Die 22 globalen Skills waren reiner Home-Ordner-State - kein Git, kein Backup. Master ist jetzt das Gitea-Repo admin/claude-skills, ~/.claude/skills ist dessen Arbeitsbaum. Sync-Ablauf inkl. cp-Schritt nach opencode festgehalten. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
28 KiB
BF-Netzwerk — Projekt-Kontext (auto-read)
Standort BF: VLAN-segmentiertes Heimnetz auf MikroTik/RouterOS. Ziel: CAKE/QoS gegen Bufferbloat, DNS/DHCP-Ablösung, WLAN via CAPsMAN auf dem Core (TP28, 2026-08-17), Firewall Least-Privilege (TP5 deny-by-default scharf seit 2026-08-18), Multi-Site VPN.
Pflichtlektüre pro Sitzung (Reihenfolge)
- Status/Index = Gitea-Board (seit TP36-Cutover 2026-09-07):
admin/bf-infra· Issues — TP = Issue, Status = Labelstatus:*, Gruppierung =thema:*. Detail/Kontext eines TP →### TPnnindocs/teilprojekte.md(aktiv) bzw.docs/teilprojekte-archiv.md. docs/handoff-*.md(neueste) — wo die letzte Session aufgehört hat.- Bei Live-Writes/Inventur:
docs/cf-tunnel-mapping.md,docs/gotchas.md(ausführliche Diagnose-Fallen),docs/teilprojekte-archiv.md.
Aktueller Fokus: TP45 (IP-Schichten A/B/E, C4→TP34.2), TP34.2 (interne DNS-Namen als Daten, Gitea #73) und TP46 (CF-/Tunnel-SSoT) offen — Status im Gitea-Issue, Detail in den ### TPnn-Abschnitten von teilprojekte.md.
SSoT & Branch-Konvention (TP36-Cutover 2026-09-07)
- SSoT-Dreiteilung: Index/Status = Gitea-Issues (
admin/bf-infra, Labelsstatus:*/thema:*) · Prosa/Detail = Repo (docs/teilprojekte.mdaktiv +teilprojekte-archiv.md,### TPnn-Abschnitte) · Live-Gerätedaten = Nautobot (Compliance-Zahl, RouterOS-Version …). Ein Status ändert sich in Gitea, nicht mehr in einer Repo-Datei. Sync-/Abgleich-Werkzeug fürs Archiv:bin/gitea-tp-sync.py(Dry-Run default, Token aus~/.hermes/.env, nie ausgeben). - Branch-Konvention: Branch
tpNN-kurzjederzeit mitten in der Session möglich (git switch -c, uncommittete Arbeit wandert mit); für Multi-Session-TPs der Normalfall. Merge nachmain, sobald der Code/Config des TP steht. Weil Steuerungs-Updates nach Gitea gehen (nicht inteilprojekte.md), kollidieren TP-Branches nicht mehr auf der Steuerungs-Doku — nur noch der eigene### TPnn-Prosa-Abschnitt wird angefasst. - TP anlegen/schließen: neues aktives TP = direkt als Gitea-Issue (Labels setzen), Prosa-Abschnitt
im Repo dazu; abschließen = Issue
closed, Prosa wandert bei Bedarf ins Archiv. Keine Handtabelle mehr.
Was du sofort wissen musst (Inventur 2026-08-27)
bf-infra= aktiver Master mit Gitea-Remote:origin=https://gitea.emsenhuber.net/admin/bf-infra.git. Das Remote ist nur so viel wert wie der letzte Push — Backup ist eine Disziplinfrage:git push. Der Backup-Guard (~/dev/.bin/backup-guard.sh, TP35 Schicht 5) bricht Commits ab, solange kein Backup markiert ist — das ist die Erinnerung ans Pushen, kein Hindernis. Nicht mit--no-verifyumgehen. 🔴 Vor jeder Aussage über Remotes/Backupsgit remote -vlive prüfen (eine Session hat „kein Remote" behauptet, statt zu tippen).admin/bf-network-r6(Gitea) ist Archiv → read-only-Header.admin/bf-golden-config(Gitea) ist Bot-Master, nicht manuell. Täglich 03:30 UTC per ScheduledJob „GC Backup+Compliance – taeglich" (Nautobot-Job Execute All Golden Configuration Jobs - Multiple Device, Scopeplatform=RouterOS, Reihenfolge Backup→Intended→Compliance). Stand prüfen:backup_last_success_dateam Gerät bzw.scheduled-jobsin Nautobot.admin/blaueblueten-n8n(Gitea) ist Master für terpene-Repo.inventory/ist tot (Pipeline stale). Live-Daten aus MikroMCP/CF/Portainer.mcp/INVENTORY.mdist BF-Netz-SSoT;~/.claude.json/opencode.jsoncsind globalere Werkzeugkästen.- Terpenfinder =
420.teilzeitladen.xyz(CF-Worker, Repoadmin/blaueblueten-n8n, hinter CF Access), kein Caddy, kein Mac mini im Pfad.terpene.*existiert nicht und soll es nicht (live geprüft 2026-09-10). Einziger terpene-benannter CF-Gegenstand: der interne Tunnelterpene-db(s.docs/cf-tunnel-mapping.md§17). ⚠️ Grenze: blaueblüten ist ein eigenes Projekt — nicht anfassen. - shelly-mcp / portainer-mcp bewusst NICHT als MCP-Client (User-Direktive 2026-08-28): shelly-mcp-Container auf DS923 wird via Portainer-API (Skript-Aufrufe,
mcp/portainer/.envals Token-Quelle) deployt, nicht via MCP-Protokoll.mcp-host-policy.mdist SSoT für die Host-/IP-Konvention. - Mac mini
10.40.20.167ist der n8n-Host (n8n:5678, n8n-redis:6379, n8n-mcp:3001) — NICHT der zentrale MCP-Host. Der MCP-Host liegt auf der DS923 (nas-bf, Portainer Endpoint 14). gitea-mcp läuft noch auf dem macmini (historisch) — gehört bei Gelegenheit auf die DS923 umgezogen. Container-Definitionen für n8n leben auf dem macmini (Docker Desktop); FQDN→Backend-Mapping imcaddy/Caddyfile.
TP-Management (2026-08-26)
Wenn während einer Session ein neues Teilprojekt, eine Abhängigkeit oder ein Querschnittsthema sichtbar wird, das bisher in keinem TP abgebildet ist:
- Vorschlagen und mit dir abstimmen (nicht eigenmächtig anlegen).
- Auf dein Okay hin anlegen: Gitea-Issue
TPnn — <Kurztitel>mit Labelsstatus:🔜(todo) + passendemthema:*+ Body (Anriss + Verweis auf den Prosa-Abschnitt) und einen### TP<n>-Prosa-Abschnitt indocs/teilprojekte.md(Scope, Abhängigkeiten, Abgrenzung). Nächste freie Nummer vergeben (höchste vergebene + 1; Lücken aus verworfenen TPs bleiben frei). - Status/Reihenfolge/„was lief zuletzt" gehen ins Gitea-Issue (Label ändern, Kommentar), nicht in eine Quick-Index-Tabelle oder einen Ist-Stand-Block — die gibt es seit dem TP36-Cutover (2026-09-07) nicht mehr in der Datei.
Abgeschlossene TPs stehen seit 2026-08-23 in docs/teilprojekte-archiv.md (Volltext, unverändert, mit Inhaltsverzeichnis) und ihr Gitea-Issue ist closed (Label status:✅/❌). Ein TP wandert erst ins Archiv, wenn nichts mehr daran zu tun ist — und wieder zurück, sobald doch etwas dazukommt (Issue dann reopen). Ein archiviertes TP ist ein geschlossenes Ticket, kein versiegeltes: bei neuer Arbeit an derselben Baustelle wandert der Volltext zurück nach teilprojekte.md (Zeiger bleibt im Archiv), kein Zwillings-TP mit neuer Nummer (Christoph-Direktive 2026-08-30, erster Fall TP28/ap3).
VLAN-Modell (kanonisch)
| VLAN | Name | Subnetz | Zweck |
|---|---|---|---|
| 10 | MAIN | 10.40.10.0/24 | Trusted (Handy/Laptop) |
| 20 | INFRA | 10.40.20.0/24 | NAS/VMs/Docker/HA |
| 30 | IOT | 10.40.30.0/24 | Smart-Home |
| 40 | MEDIA | 10.40.40.0/24 | Sonos/AppleTV/TV/Cast |
| 50 | UNTRUSTED | 10.40.50.0/24 | Konsole/Low-Trust |
| 90 | GUEST | 10.40.90.0/24 | nur Internet |
| 99 | MGMT | 10.40.99.0/24 | Switch/AP/Hypervisor-Mgmt |
| 100 | WAN | (Arris-Transfer) | zum Arris-Kabelmodem (Liwest, Public-IP) |
GW je VLAN = .1 (crs326-core L3) · Mgmt VLAN99: core .99.1, branch .99.2, ap1 .99.11, ap2 .99.12
Hardware & Zugang
| Gerät | IP | Zugang |
|---|---|---|
| crs326-branch (CRS326, Technik OG) | 10.40.99.2 (VLAN99) · Bootstrap Rev4 2026-08-08 | RouterOS 7.24.1 · admin/~/.secrets (CRS326_BRANCH_*) · REST http://10.40.99.2/rest · MikroMCP crs326-branch ✅ live · TP15 ✅: ersetzt sw2 (L2-Switch), Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap3) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 |
| crs326-core (CRS326, Technik UG) | 10.40.99.1 (VLAN99-SVI = L3-GW; same-subnet-Fallback 10.40.10.1) · Identity crs326-core |
RouterOS 7.24.1 · admin/~/.secrets (CRS326_CORE_*) · MikroMCP routerId crs326-core ✅ live · PRODUKTIVER L3-Router — Routing/DHCP (7 Server + 54 Leases)/DNS (DoH zum CF-Gateway mit verify-doh-cert=yes, TP23 ✅)/NTP-Server/NAT/WG-Hub (fm+dw live); WAN vlan100=Liwest 90.146.33.177 · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 |
| hAP ax² #1 (ap1) | 10.40.99.11 (VLAN99) · Identity ap1 |
RouterOS 7.24.1 · admin/~/.secrets (ROUTER_AP1_*) · REST http://10.40.99.11/rest · MikroMCP ap1 ✅ live · reiner CAP (Manager = crs326-core) · aktiv am Sideboard (Wohnzimmer; Uplink ether1 → core ether3, PoE-Port poe-out=off) — Kanäle 5260/Ce (Band B, DFS, Fallback 5280) + 2412/ch1 · MAC D0:EA:11:4D:40:E0 · Config routeros/.secrets/ap1-prov-base.rsc |
| hAP ax² #2 (ap2) | 10.40.99.12 (VLAN99) · Identity ap2 |
RouterOS 7.24.1 · admin/~/.secrets (ROUTER_AP2_*) · REST http://10.40.99.12/rest · MikroMCP ap2 · reiner CAP — Technik UG, Uplink ether1 (PoE) an core ether21; Kanäle 5300/Ce (Band B, DFS, Fallback 5320) + 2462/ch11 · MAC D0:EA:11:4D:40:39 (ether1; Bridge/Mgmt :3A) · Config routeros/.secrets/ap2-prov-base.rsc |
| cAP ax (ap3) | 10.40.99.13 (VLAN99) · Identity ap3 |
✅ LIVE seit 2026-08-30 · cAPGi-5HaxD2HaxD, RouterOS 7.24.1 + RouterBOOT 7.24.1 · reiner CAP, Manager crs326-core · Technik OG, Uplink ether1 → branch ether24 (Trunk) · Kanäle 5180/ax (Band A, non-DFS) + 2437 (ch6) · 🔴 nur 2 Ethernet-Ports → ap.extra_ports=[] · device-mode=basic (scheduler/fetch/romon erlaubt) · Dateien routeros/tp28-ap3-phase{1,2}-*.rsc |
| 🔑 Einheitliches Admin-PW | alle 4 MikroTik (core/branch/ap1/ap2) | user admin, gleiches PW (~/.secrets/credentials.env → ROUTER_*_PASS/CRS326_*_PASS; „Überlebensversicherung", User-Vorgabe). |
| NAS (nas-bf, DS923) | 10.40.20.10 | SSH adm.christoph@10.40.20.10 (kein PW nötig) · Docker (Portainer endpointId=14) |
| platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (pct exec 105 --) · TP20 ✅: PG16, DB nautobot, Creds postgres/.secrets |
| Nautobot (Portainer-Stack, NAS) | 10.40.20.154 | Portainer ep14, Stack Id 203 · TP21 ✅: ghcr.io/nautobot/nautobot:3.1.7, DB=platform-pg, Creds docs/nas-vlan20/stacks/210-nautobot.yml (gitignored) · TP22 ✅: Golden-Config-App + Device-Adoption DURCH (alle 4 MikroTik, Image 3.1.7-gc9) |
| pve-mcp | 10.40.20.173 | StreamableHTTP http://10.40.20.173:3000/ (Root-Pfad, NICHT /sse) · NAS-Stack ep14 (=nas-bf .20.10, NICHT LXC .126!) id 202 (vlan20-only, TP14.1) · mcp-pve v0.6.1 (TP14.2a) · ~/.claude.json ✓ |
| Proxmox (pve) | 10.40.20.100 | ssh root@10.40.20.100 (SSH-Key ✓) · TP14: Host-Mgmt VLAN1→VLAN20 (vmbr0.20 + Default-GW-Flip) |
| Docker-LXC (auf pve) | 10.40.20.126 | Portainer CE 2.40.0 (https://10.40.20.126:9443) · TP14.2: LXC 400 net0 tag=20/.20.126 (iface heißt veth20, NICHT eth0); Docker-Netz vlan20=ipvlan parent veth20; adguard_bf1 .20.16, adguard_fm2 .20.17 |
| Mac mini | 10.40.20.167 (VLAN20) | ssh macmini · n8n-Host (siehe oben) |
| Arris-Kabelmodem | (Bridge-Mode, keine Mgmt-IP im BF-LAN) | Liwest-Edge · speist crs326-core vlan100 (VLAN100-Transfer, WAN-IP 90.146.33.177) · FritzBox 6660 seit 2026-07-01 ausgebaut (Liwest-Tausch, kein Zurück); Telefonie nicht weitergeführt |
Ausgemusterte Geräte (ALLNET sw1, Omada sw2/Controller/ES208G, omada-mcp): Historie in TP15/TP2.1-Archiv + switch/README.md.
Tooling
- MikroTik (core/branch/ap1/ap2): MikroMCP-Server live →
mcp__mikromcp__*-Tools (routerIdcrs326-core/crs326-branch/ap1/ap2). Reads immer safe; Writes mitdryRun:truepreviewen, dannapply_plan. Passwort setzen viamanage_user set-password(/user set*ist per Deny-Policy blockiert). ⚠️ Reboot geht auf ROS 7.24 gar nicht über MikroMCP — Weg istSSHPASS="$PW" sshpass -e ssh admin@<ip> "/system reboot"(PW aus~/.secrets, im Output maskieren). 🔴 Niemalssshpass -p— das schreibt das Passwort in die Kommandozeile (ps/argv-Trace; so ist am 2026-08-22 das Core-PW in ein Transkript geraten).-ehält es in der Umgebung des Kindprozesses. Neue Skripte erben die Regel nicht von selbst:env=dict(os.environ, SSHPASS=pw)einbauen.bridge/vlan/setnur auf[find … and !dynamic]. 🔴list_wifi_interfacesgibtsecurity.passphraseALLER Profile im Klartext zurück (2026-08-24: fünf WLAN-PSKs ins Transkript). Für Kanäle/interface wifi channel print detail, für die CAP-Zuordnung… print where master=yesmit Feldauswahl. Der Scan (/interface wifi scan, nur am Manager) ist unbedenklich. Merkregel: vor jedem Read fragen, was das Tool AUSSER dem Gesuchten noch ausgibt. - Nautobot Golden Config (= der einzige legitime Config-Weg für Netzgeräte):
nautobot/gc-configplan.py(Token =NAUTOBOT_SUPERUSER_API_TOKENausdocs/nas-vlan20/stacks/210-nautobot.yml, gitignored).--job <name>legt manuelle Pläne an (Dry-Run default),--deploy-ids …deployt gestaffelt,--refresh-compliancefährt Backup → Intended → Compliance in der einzig richtigen Reihenfolge. ⚠️POST /api/plugins/golden-config/config-plan/ist405— ViewSet read-only. ConfigPlans entstehen nur über den JobGenerate Config Plans(plan_type=manual+commands-TextVar). - Docker-Management: Portainer API
https://10.40.20.126:9443, Key inmcp/portainer/.env. - 🔴
pct pushmeldet Exit 0, auch wenn es die Quelldatei NICHT lesen kann (gemessen 2026-09-21).rc=0trotzfailed to open … for reading; ein&&/||dahinter beweist nichts, undvalidate/reloadprüft dann die alte Datei → grüne Abnahme bei unverändertem Zustand (so sah der Nautobot-Umzug „gelandet" aus, live stand noch die alte Backend-IP). Regel: Push-Werkzeug wird geprüft, nicht geglaubt — nach jedempct pushden Inhalt vergleichen (md5sumdiesseits/jenseits in einem Remote-Aufruf). Muster:push_verified()incaddy/deploy.sh. Erst das eigene Werkzeug verdächtigen, dann das Netz. - 🔴 Die Core-Adressliste
nautobotwird von Golden Config NIRGENDS gerendert (Fund 2026-09-21). Der Mgmt-Zugang für die Deploy-Pipeline läuft übersrc-address-list=nautobot(Regel*28), die Liste stammt aber aus dem handgelaufenenrouteros/tp5-etappe2-whitelists.rsc— steht in keinem Template. 🔴 Ein GC-Recovery von null erzeugt einen Core, in den Nautobot nicht mehr per SSH kommt — die Pipeline wäre tot. Gleiche Lücke wie die DHCP-Reservierungen. Merkregel: was auf dem Gerät existiert, aber nicht im Template, ist eine Recovery-Lücke, auch wenn Compliance 140/140 grün meldet — die Zahl prüft nur, was gerendert wird. Ablösung durch Komodo (native MCP) geplant.
Kritische Gotchas
- 🔥 Die Firewall ist seit 2026-08-18 deny-by-default — in BEIDEN Ketten. Ein neuer Dienst über eine VLAN-Grenze braucht eine explizite Regel, sonst funktioniert er nicht. Diagnosewerkzeug = Log-Regel direkt vor dem Drop (
TP5-WOULD-DROP/TP5-INPUT-WOULD-DROP, Kollektor10.40.20.18:514, Dateien Docker-LXC/opt/tp5-syslog/log/). Erst dort nachsehen, bevor irgendwo anders gesucht wird. MAIN→INFRA/MEDIA/MGMT/IOT offen (IOT seit TP5 E5, 2026-08-19); IOT→MAIN bewusst zu. Ad-hoc-Zugriff aus MAIN in geschlossenes VLAN (UNTRUSTED, GUEST):/ip firewall address-list add list=jit-admin address=<deine-IP> timeout=1h.- 🖨️ Drucker fällt mit udp/161 (SNMP) auf, nicht 631 — macOS fragt zuerst SNMP, bricht dann ab. Bei „Gerät antwortet nicht" das Log lesen, statt den erwarteten Port zu raten.
- Beide APs = CAPs, Manager ist
crs326-core(TP28 ✅). Mesh = NEIN (beide APs haben wired Trunk-Uplink → Loop-Risiko). Der Core braucht dafür keinwifi-qcom(capsman steckt im Basispaket).- 🔴🔴
/interface wifi cap slaves-datapathMUSS auf beiden APs gesetzt sein (dp-caps-slaves,bridge=bridge, ohnevlan-id). Fehlt es, assoziieren Clients auf-iot/-media/-untrusted/-guestsauber und bekommen nie DHCP, währenddirtyvader-mainweiterläuft (so lagen IOT+MEDIA am 2026-08-18 ~14 h). Abnahme für jeden WLAN-Umbau = frische DHCP-Lease pro SSID — NICHT die Registration-Table. - 🔴
/interface wifi scangeht am CAP NICHT — nur am Manager.cap-wifiN-Zuordnung vorher überradio-macbestimmen, sie ist nicht stabil. - 🔴 CAPs finden den Manager per L2-Discovery (
discovery-interfaces=vlan99,current-caps-man-address= MAC). Deshalb MNDP (udp/5678) + CAPWAP (udp/5246-5247) in der input-Kette des Core offen lassen. - 🔁 Slave-VAPs ändern = zweimal arbeiten: nach
slave-configurations-Änderung/interface wifi capsman remote-cap provision [find where identity="apX"]nachziehen (beide Richtungen). Abnahme nicht am Kommentar (comment~"ssid"), sonderndatapath.vlan-idbzw. Interface-Zahl am CAP. - 🔎 „connect ok, kein DHCP": zuerst
torchauf dem VLAN am Core UND den TP5-Drop-Log lesen. Beide leer ⇒ nichts wird verworfen, es kommt nichts an → L2 (Bridge-Mitgliedschaft/Tagging), nicht Regelwerk.
- 🔴🔴
- 📝 WLAN-Log-Topic heißt
wireless, nichtwifi. Zwei Regeln auf dem Core (nicht den CAPs) schickenwireless+capszum Kollektor10.40.20.18:514. ⚠️ RouterOS UND-verknüpft Topics in EINER Regel —topics=wireless,capsmatcht nichts, es braucht zwei Regeln./system loggingsteht unter der ComplianceRulesystem-logging. Merkregel: bevor man ein sporadisches Problem sucht, erst prüfen, ob es überhaupt mitgeschrieben wird. - 🧮 Log-Burst über VIELE MACs = Infrastruktur, über EINE MAC = Client. Vor jeder Client-Diagnose erst
grep " caps,"+/system resource get uptimelesen (ein Massenburst war oft ein geplanter Reboot). Umkehrschluss: 93/93reauthenticatingvon einer MAC auf einer SSID = echter Client-/SSID-Befund. - 🗺️ Die
cap-wifiN-Karte verschiebt sich bei JEDEM CAP-Rejoin. Vor jeder Auswertung/interface wifi radio print terse proplist=cap,radio-mac,interfaceam Manager lesen; liegt zwischen Ereignis und Auswertung ein Rejoin, Zuordnung als unbekannt behandeln statt raten. - 🔑 CAPsMAN verteilt KEINE Login-Passwörter — nur WLAN-PSKs. WPA-Passphrase (
/interface wifi security) liegt am Manager ✅; Geräte-Login (/user) ist eine lokale DB je Gerät. Das einheitliche Admin-PW ist eine Hand-Konvention, keine Automatik. - 🔴 Ein neuer CAP braucht
configuration.manager=capsmanauf JEDEM Master-Radio./interface wifi cap set enabled=yesallein reicht nicht — im Werkszustand ist das Feld leer (=local). - 🔌 Werks-MikroTik am Trunk:
ether1ist WAN. Interface-ListeWAN→ Discovery aus (kein MNDP), Input-TCP (22/80/8291/8728) zu, ICMP erlaubt → es pingt, wirkt erreichbar. Weg rein ist die LAN-Seite (ether2), dort/interface list member remove [find where interface=ether1]. - 🔀
pvidist INGRESS,tagged/untaggedist EGRESS. Werksgerät-Port gehört untagged in die VLAN-Zeile. - 🕳️ Ein neues Gerät ohne
secrets_grouperzeugt eine LÜCKE, keine Fehlermeldung. Backup-Job scheitert still, keine Compliance-Einträge. 🔴 Eine gleichbleibende Compliance-Zahl nach dem Hinzufügen eines Geräts ist ein BEFUND, kein Erfolg. Zweite Voraussetzung: dedizierte Service-Accountsnautobot/haexistieren auf einem Werksgerät nicht. - 🔢 Firewall-Regelnummern aus
print wheresind Handles, keine Positionen. Beweis ist der Zähler (print stats), nicht die Zahl. - 🔌
crs326-corehat KEIN PoE-out (CRS326-24G-2S+). AP am Core-Port braucht eigenes Netzteil; der cAP ax bringt Netzteil + PoE-Injector im Karton mit. - 🧩 Die AP-Flotte ist heterogen (cAP ax = 2 Ports vs hAP = 5). Bridge-Ports kommen aus
ap.extra_ports(ConfigContextap-common; ap3 überschreibt perlocal_config_context_datamit[]). ⚠️ Nautobot merged Dicts tief, Listen ERSETZEND. Bevor ein zweites Modell derselben Rolle dazukommt, Literal-Blöcke lesen („Gerätetyp" vs „Rolle"). - 🔵 Container auf MikroTik: ap1/ap2 =
arm64, 95 MB Flash frei → ein kleiner Container ok (~70 MB nutzbar).crs326-coredagegen NIE (arm32-bit, 2,9 MB frei) — SPOF, kein Anwendungs-Host (Hardware-Tatsache).cloudflaredbraucht kein TUN (Userspace-Proxy) → RouterOS-Container möglich; der WARP-Connector nicht (braucht TUN). Schwere Container laufen weiterhin auf NAS/pve, nicht am Router. - hAP
device-mode=home(ap1/ap2): scheduler + fetch blockiert → AP-Provisionierung idempotent ohne Scheduler.crs326-coreist KEINdevice-mode=home→ dort gehen scheduler/fetch (DDNS-Updater). - Erstzugriff Werks-hAP am CRS-Fabric: Uplink
ether1= Werks-WAN →192.168.88.1(LAN) darüber NICHT erreichbar. Weg: LAN-Port (ether2) patchen ODER Core-Temp-Pfad192.168.88.2/24+ srcnat masq192.168.88.0/24, dann scp+/import(danach Temp-Pfad + stale ARP.88.1entfernen). - 🖨️ ZWEI Drucker, nicht verwechseln: Brother MFC = eigentlicher Drucker, VLAN10 (
10.40.10.6, MAC30:05:5C:78:86:20, statisch). P750W = Etikettendrucker, VLAN30 (10.40.30.5), läuft aufdirtyvader-iot. P750W-Coverage SSoTdocs/wifi-p750w-coverage-and-map.md. ⚠️ Power-Save ist KEIN Thema (frühere „DHCP-geht-aber-Ping-nicht=ARP/Power-Save"-Merkregel war Fehldiagnose). - Ping auf JEDE lokale IP tot, aber TCP geht überall = WARP-Client lokal noch aktiv.
cloudflared/WARP ist QUIC-Proxy, kann kein ICMP. Fix: WARP trennen, kein Router-Eingriff. Memoryref_warp_local_icmp_trap. - L3-Router =
crs326-core(TP21.1 ✅, ersetzt ap1). Bridge vlan-filtering=yes; VLAN-SVIs.1je VLAN mit DHCP (7 Server/54 Leases); DNS über DoH zum CF-Gatewayverify-doh-cert=yes(TP23 ✅). Beide Firewall-Ketten enden mitdrop; Mgmt-Input nur aus VLAN10, von HA (8728), von Nautobot (22) und übercf-mesh. ap1/ap2 sind NUR noch APs. - ✅✅ Edge-Cutover: FritzBox aus, Arris Bridge-Mode. WAN terminiert auf
crs326-core vlan100, öffentliche Liwest-IP90.146.33.177, kein Double-NAT. core = Public-Edge + WG-Hub. Runbookrouteros/wan-cutover-runbook.md. - mgmt-VLAN-Wechsel am Switch = Lockout-Risiko: VLAN muss existieren + Port muss es tragen, in einem Schritt.
Diagnose-Fallen (Detail in docs/gotchas.md, hier nur die Merkregel):
- ⏰ Zeitstempel zwischen Geräten widersprüchlich →
/system ntp client get status(Zeitquelle = Core, DHCP-Option 42, ComplianceRulesystem-ntp). - 🔌 Vor jedem geplanten LXC-Neustart
systemctl --failedlesen + danach Ports gegen vorher vergleichen. - 🧪 „alles gleichzeitig kaputt" → zuerst das eigene Werkzeug verdächtigen (
nc -zlügt), dann das Netz. - 📡 Sonos sucht hunderte Male ohne Verbindung → Relay, nicht Netz (Richtungsbilanz
grep -c "via eth1"vs"via eth0"). - ⏱️ Konstanter 10-s-Hänger = DNS-Timeout, nicht Last (DB-Reverse-Lookup auf toten Resolver).
- 🏠 HA-
http:-Block inconfiguration.yamlwirkungslos →.storage/http+ WShttp/config/configure→promote. - 🐳 Nautobot auf DS923: Image-Healthcheck aus;
cpus:geht auf Synology nicht.
Lessons for Sessions (2026-08-27, aus der Inventur-Session)
Verhaltensregeln für nachfolgende AI-Sessions — kleiner, klarer, weniger Drift:
- Bestandsaufnahme vor Plan. Erst was ist da, wo ist der Schaden — dann Vorschläge. Nicht umgekehrt.
- Eine große Fragenrunde statt vieler kleiner. Wenn der Plan von Annahmen abhängt, sammle die Annahmen in einer Runde, nicht in 3–4 Mikrorunden.
- Master-Frage früh stellen. Bei jedem Repo, jeder Datei, jedem Tooling: „Was ist die SSoT, wo lebt der Master, was ist Mirror?" — bevor du eine Lösung vorschlägst.
- Drift zuerst, Lösungen später. Drift-Tabelle posten + fragen, was richtig ist. Keine Lösungen vor dem Befund.
- MCP-Erstellung/-Stilllegung = aktiv melden (2026-08-27, Christoph-Direktive). Wer einen neuen MCP anlegt, ändert oder abschaltet — meldet das proaktiv an den User, nicht stillschweigend im Commit. Jede AI-Session ist zugleich Oberhand über Container und Garbage-Collector auf Infra-Ebene.
- MCPs brauchen Host-Verifikation vor Edit: per Live-Read (nc/ps/curl) prüfen, ob der Host noch antwortet. Beispiel:
shelly-mcpzeigte auf10.40.20.30:3030, kein Host antwortet →enabled=false, dokumentieren. (Stand 2026-08-28: shelly-mcp auf DS92310.40.20.180deployt.) - Tote MCP-Skripte → ins Projekt-Repo, nicht global. Projekt-spezifische MCPs (
cannaleo,terpene-pg) →~/dev/<projekt>/mcp.json+ Skript im Projekt-Repo. Globale~/.claude.json/opencode.jsonc= nur projekt-übergreifende Tools. - opencode-Skills: Default-Pfad kopieren, NICHT symlinken (TP35 Schicht 6). Claude-Skill
~/.claude/skills/<name>/SKILL.mdist die Quelle; für opencodecpnach~/.config/opencode/skills/<name>/SKILL.md(keinskills.paths-Eintrag nötig). Symlink verletzt Christophs „opencode = eigene Pfade"-Direktive. Lehre: opencode-Verhalten nicht raten, Doku lesen. - Skills sind IMMER global, niemals pro Projekt (Christoph-Direktive). Nur
~/.claude/skills/und~/.config/opencode/skills/. Kein Repo-Override. Wer überschreiben will, editiert die globale Datei oder schreibt einen zweiten Skill. - Skills sind seit 2026-09-25 versioniert (Befund: sie waren reiner Home-Ordner-State, kein Git, kein Backup — bei 22 Skills über mehrere Monate gewachsen). Master = Gitea
admin/claude-skills(privat),~/.claude/skills/ist dessen Arbeitsbaum. Ablauf ab jetzt: editieren → committen → pushen →cp -R ~/.claude/skills/<name> ~/.config/opencode/skills/<name>. ⚠️ Dercp-Schritt ist der, den man vergisst — genau so entsteht stille Drift zwischen den beiden Kopien. Secret-Gebot gilt auch hier: keine Tokens/Keys in Skills, Platzhalter (REPLACE_WITH_…) sind okay. - Kommentar in Code ≠ Verhalten.
mcp/shelly/.envtrug „gitignored", es gab aber kein.gitignore. Behauptungen in Kommentaren pergit check-ignore -v <datei>verifizieren, bevor man darauf vertraut. - opencode-TUI: führendes Leerzeichen vor
/commandmanchmal nötig (bekannter Bug, anomalyco/opencode#27528, „Not planned"). Workaround (führendes Leerzeichen) ist der offizielle Pfad. - Manuell gepflegte Doku verliert gegen die Realität, wenn sie lang wird. Wer eine Stelle ändert, muss die anderen mitlesen (Quick-Index, TP-Abschnitt, Ist-Stand). Strukturelle Antwort = TP36 (SSoT via Nautobot).
- Maskierungs-Muster: auf den SCHLÜSSELNAMEN ankern, nie auf ein Trennzeichen. Sicher ist nur
sed -E 's/(TOKEN|PASSWORD|SECRET|KEY|PASS)=.*/\1=<redacted>/'— greedysed 's/=.*/…/'schneidet Slashes/Backslashes nicht. Werte nie suchen, um sie zu sehen — den Schlüssel persed -n 's/^…KEY=//p'direkt in eine Variable holen. Drei Leaks dieser Klasse: PSK 2026-08-24,SHELLY_DEVICE_PW2026-08-28, Nautobot-Tokens 2026-08-31. - Vor jeder Drift-Bewertung
python3 nautobot/gc-transaktion.py statuslesen (Befund 2026-08-31, zweimal gestolpert). Drift aus einem laufenden Versuch sieht identisch aus wie ein Versehen — die Compliance-Zahl unterscheidet nicht. 🔴 Eine solche Drift grün zu machen macht den Versuch unsichtbar. Was dauerhaft gelten soll, gehört ins Soll/Template, nicht in eine Transaktion. ⚠️ Das Skript findet den Token aus einem Worktree heraus nicht (210-nautobot.ymlnur im Haupt-Checkout). - Vor jedem Flash-Budget auf MikroTik
docker export | wc -cmessen, nichtdocker image inspect .Size(Befund 2026-08-31, TP16.3)..Sizemeldete fürcloudflared27,5 MB, entpackt 69,5 MiB. 🔴docker saveschreibt seit Docker 25 OCI statt docker-v1 → RouterOS meldetno config found in manifest. Beides indocs/tp16.3-bf-public-ap-connector-runbook.md§2.2.
Checkliste vor jedem Edit-Vorschlag:
- Habe ich die SSoT für die Datei/das Repo benannt?
- Habe ich die Drift zwischen Doku und Live dokumentiert (nicht stillschweigend korrigiert)?
- Habe ich eine Fragenrunde mit allen offenen Annahmen gestellt?
- Habe ich für jeden Edit deine Bestätigung eingeholt (nicht nur für den Plan-Output)?
- MCP-Änderung? Vorher Live-Check (Port/Prozess), aktiv melden, Garbage-Collector-Pflicht.
- Output-Länge. Christoph hat sich wiederholt in langen Outputs verirrt. Merkregel: eine Antwort pro Edit-Vorschlag, eine Zeile pro Bestätigung, keine Wiederholung dessen, was im Diff steht. Keine Frage-Runden, wenn keine echte Annahme kollidiert. Erklärungstexte unter 6 Zeilen, sofern nicht ausdrücklich verlangt.