Compare commits
25 Commits
c0c63882e4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c2d9c0ea7 | |||
| 5c8824850f | |||
| 359ad666c4 | |||
| 4d4779e400 | |||
| 4b307315f0 | |||
| 81cd7bfb7e | |||
| c5086db223 | |||
| a3f2ade4e0 | |||
| 52a8ac462c | |||
| 9638a03e4b | |||
| fb415ae52d | |||
| f63149a769 | |||
| 3601a2b6cb | |||
| 75bd43d1f7 | |||
| 315f1e79e7 | |||
| 6e2a7cfb47 | |||
| 5ad71b37c0 | |||
| 5bcd0b6989 | |||
| 1fddf5d798 | |||
| 08a1aaa80d | |||
| 808d3a168b | |||
| 3ae6399b93 | |||
| 528f133264 | |||
| 27a9977115 | |||
| 6601df5a1b |
21
CLAUDE.md
21
CLAUDE.md
@@ -17,8 +17,18 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17), Firewa
|
||||
- **SSoT-Dreiteilung:** **Index/Status = Gitea-Issues** (`admin/bf-infra`, Labels `status:*`/`thema:*`) ·
|
||||
**Prosa/Detail = Repo** (`docs/teilprojekte.md` aktiv + `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,
|
||||
nicht mehr in einer Repo-Datei. Archiv-Backfill-Werkzeug `bin/gitea-tp-sync.py` (Dry-Run default,
|
||||
Token aus `~/.hermes/.env`, **nie ausgeben**).
|
||||
- 🔴 **Die TP-Header in beiden Dateien tragen KEINEN Status mehr** (seit 2026-10-03). Form:
|
||||
`### TP25 · Gitea #17 — <Titel> — <Caveat>`. Der `· Gitea #N`-Zeiger ist die Brücke zum Issue.
|
||||
Wer einen Status sucht, liest das **Label im Board** — nicht den Datei-Kopf. Der alte `▸ ✅ FERTIG …`-Anspruch
|
||||
war eine dritte Statusquelle und ist entfernt; die Header-Marker waren teils älter als die Issues.
|
||||
⚠️ **Status-Marker im Fließtext** (in `└─`-Zeilen, Unterpunkten, Messchronik) bleiben erlaubt — sie
|
||||
beschreiben historische Ereignisse, keinen aktuellen Stand.
|
||||
- 🔴 **`bin/gitea-tp-sync.py` schreibt nur noch NEUE Issues** für Archiv-TPs, die im Board fehlen.
|
||||
Existierende Issues werden **nie** angefasst — kein Label-, kein State-Patch. Die Vorgängerversion
|
||||
glich bestehende Issues gegen den Datei-Marker ab und zog so #24 (`status:dropped` → `done`) und
|
||||
#32 auf falsche Labels. **Richtung Datei → Gitea ist nur fürs *Anlegen* zulässig, nie für *Status*.**
|
||||
- **Branch-Konvention:** Branch `tpNN-kurz` **jederzeit** mitten in der Session möglich (`git switch -c`,
|
||||
uncommittete Arbeit wandert mit); für Multi-Session-TPs der Normalfall. **Merge nach `main`, sobald der
|
||||
Code/Config des TP steht.** Weil Steuerungs-Updates nach **Gitea** gehen (nicht in `teilprojekte.md`),
|
||||
@@ -112,7 +122,9 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
|
||||
🔴 **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.
|
||||
prüft nur, was gerendert wird. Systematisch erfasst in **[TP48](docs/teilprojekte.md#tp48)** (Gitea #75),
|
||||
der auch prüft, ob Ansible Nautobot als SSoT ablösen kann. ⚠️ **Nicht Komodo** — es verwaltet Container,
|
||||
nicht RouterOS-Config (frühere Notiz hier war eine Kategorie-Verwechslung).
|
||||
|
||||
---
|
||||
|
||||
@@ -170,6 +182,11 @@ Verhaltensregeln für nachfolgende AI-Sessions — kleiner, klarer, weniger Drif
|
||||
- **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.md` ist die Quelle; für opencode `cp` nach `~/.config/opencode/skills/<name>/SKILL.md` (kein `skills.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>`.** ⚠️ Der `cp`-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.
|
||||
- **Projekt-Memories sind seit 2026-10-06 versioniert** — dasselbe Muster wie die Skills, aus demselben Befund (reiner Home-Ordner-State). Master = Gitea **`admin/claude-memories`** (privat), Arbeitsbaum **`~/dev/claude-memories/`**. Ein Ordner je Projekt, Name = Projektpfad ohne das Präfix `-Users-christophemsenhuber-`; **führender Unterstrich = projektübergreifend** (`_dev` = `~/dev`, `_home` = `~`). Anbindung per **Symlink**, nicht Kopie: `~/.claude/projects/<kodiertes-projekt>/memory/` → `~/dev/claude-memories/<projekt>/` (eine zweite Kopie wäre genau die Drift, die das Repo verhindern soll). **Ablauf: editieren → `MEMORY.md`-Zeile ergänzen → committen → pushen.**
|
||||
- 🔴 **`name:` muss dem Dateinamen entsprechen** — die `[[wikilinks]]` adressieren den Slug, nicht den Pfad. Laufen beide auseinander, brechen Verweise **still** (kein Fehler, kein Hinweis). Belegt am 2026-10-06: 12 Links zeigten ins Leere, weil 8 Dateien einen abweichenden `name:` trugen.
|
||||
- 🔴 **`MEMORY.md` ist ein Index, kein Log** — nur das Index-File wird in jede Session des Projekts geladen, die Einzeldateien nicht. Statusberichte/Chronologie gehören in eine eigene, nicht geladene Datei (Muster: `bf-network-r6/CHRONIK.md`). 67 Log-Zeilen hatten den r6-Index auf 16,4 KB getrieben — mehr als alle anderen elf Indizes zusammen.
|
||||
- Keine Secrets in Memories — sie werden in jede Session geladen und liegen versioniert. Platzhalter sind okay.
|
||||
- **Kommentar in Code ≠ Verhalten.** `mcp/shelly/.env` trug „gitignored", es gab aber kein `.gitignore`. Behauptungen in Kommentaren per `git check-ignore -v <datei>` verifizieren, bevor man darauf vertraut.
|
||||
- **opencode-TUI: führendes Leerzeichen vor `/command` manchmal 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).
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TP36 (reframed) — teilprojekte.md Index/Status → Gitea Issues (admin/bf-infra).
|
||||
TP36 (reframed) — Archiv-Backfill: fehlende Archiv-TPs als Gitea-Issues anlegen.
|
||||
|
||||
Idempotenter Sync (get-or-create, Match ueber "TPnn" im Titel):
|
||||
- Archiv-Header in docs/teilprojekte-archiv.md -> closed Issues (nur TPs, die in Gitea
|
||||
NICHT offen sind; ein reopentes TP mit Archiv-Zeiger bleibt offen).
|
||||
- Labels status:* (Marker) + thema:* (Gruppierung) werden vorab get-or-create.
|
||||
🔴 RICHTUNG (seit 2026-10-03): **Gitea ist die SSoT fuer Index/Status.** Dieses Werkzeug
|
||||
liest die Datei NUR, um zu pruefen, ob jedes archivierte TP ein Issue hat. Es schreibt
|
||||
**ausschliesslich** NEUE Issues fuer noch nicht existierende Archiv-TPs — existierende
|
||||
Issues werden NIE angefasst (kein Label-, kein State-Patch).
|
||||
|
||||
Warum das wichtig ist: die Vorgaenger-Version glich auch bestehende Issues ab und leitete
|
||||
den Zielzustand aus dem Datei-Marker ab (fest `✅`). Damit konnte sie ein bewusst gesetztes
|
||||
Label ueberschreiben — so wurde #24 (TP34, `status:dropped`) auf `status:done` und #32
|
||||
(TP43) auf ein falsches `thema:` gezogen. Die Richtung Datei → Gitea fuer *Status* ist
|
||||
seit dem TP36-Cutover (2026-09-07) falsch; sie bleibt hier nur fuer das *Anlegen* erhalten.
|
||||
|
||||
Abgrenzung:
|
||||
- Aktive TPs: werden DIREKT als Gitea-Issue gepflegt (nicht hier).
|
||||
- Status/Thema/State bestehender Issues: aendert nur der Mensch im Board.
|
||||
- Per-TP-Prosa: bleibt in docs/teilprojekte.md / teilprojekte-archiv.md.
|
||||
|
||||
SSoT-Modell (seit TP36-Cutover 2026-09-07): Gitea fuehrt Index+Status — aktive TPs werden
|
||||
DIREKT als Gitea-Issue angelegt/gepflegt, NICHT mehr aus einer Datei-Tabelle abgeleitet
|
||||
(die Quick-Index-Tabelle wurde aus teilprojekte.md entfernt). Dieses Werkzeug haelt nur noch
|
||||
die ARCHIVIERTEN (closed) TPs mit teilprojekte-archiv.md idempotent abgeglichen und fasst
|
||||
offene Issues nie an. Die per-TP-Prosa bleibt in docs/teilprojekte.md / teilprojekte-archiv.md.
|
||||
Token: ~/.hermes/.env (GITEA_ACCESS_TOKEN, GITEA_HOST) — wird NIE ausgegeben.
|
||||
|
||||
Aufruf:
|
||||
python3 bin/gitea-tp-sync.py # Dry-Run (default) — zeigt, was passieren wuerde
|
||||
python3 bin/gitea-tp-sync.py --commit # legt Labels + Issues an / gleicht sie ab
|
||||
python3 bin/gitea-tp-sync.py # Dry-Run (default) — zeigt, was fehlt
|
||||
python3 bin/gitea-tp-sync.py --commit # legt fehlende Archiv-Issues an
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@@ -33,16 +39,6 @@ HERMES = Path.home() / ".hermes" / ".env"
|
||||
OWNER_REPO = "admin/bf-infra"
|
||||
COMMIT = "--commit" in sys.argv
|
||||
|
||||
# Marker -> (status-label, gitea-state)
|
||||
MARKER = {
|
||||
"🔜": ("status:todo", "open"),
|
||||
"⏳": ("status:wip", "open"),
|
||||
"⏸️": ("status:paused", "open"),
|
||||
"💡": ("status:idea", "open"),
|
||||
"🟢": ("status:concept", "open"),
|
||||
"✅": ("status:done", "closed"),
|
||||
"❌": ("status:dropped", "closed"),
|
||||
}
|
||||
LABEL_COLORS = {
|
||||
"status:todo": "#ededed", "status:wip": "#fbca04", "status:paused": "#c5def5",
|
||||
"status:idea": "#d4c5f9", "status:concept": "#bfdadc", "status:done": "#0e8a16",
|
||||
@@ -105,23 +101,31 @@ def api(method, path, body=None):
|
||||
|
||||
# --------------------------------------------------------------------------- parse
|
||||
def parse_archiv(exclude_nums):
|
||||
"""Archiv-Header -> archivierte TPs. exclude_nums = TP-Nummern, die in Gitea OFFEN sind
|
||||
(reopente TPs mit Archiv-Zeiger) -> die bleiben offen, nicht als closed abgleichen."""
|
||||
"""Archiv-Header -> archivierte TPs, die ein Issue bekommen SOLLEN.
|
||||
|
||||
Header-Form seit 2026-10-03: `### TP25 · Gitea #17 — WLAN-Stabilisierung — …`
|
||||
Der Status-Anspruch (frueher `▸ ✅ FERTIG …`) ist entfernt; die Datei traegt keinen
|
||||
Status mehr. Der Gitea-Zeiger `· Gitea #N` ist die Bruecke, falls er schon da ist.
|
||||
|
||||
exclude_nums = TP-Nummern, die in Gitea OFFEN sind -> die brauchen kein neues Issue.
|
||||
"""
|
||||
out = []
|
||||
hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\s*—\s*(.+?)\s*▸", )
|
||||
hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\b\s*(?:·\s*Gitea\s*#(\d+)\s*)?—\s*(.+?)\s*$")
|
||||
seen = set()
|
||||
for line in ARCHIV.read_text().splitlines():
|
||||
m = hdr.match(line)
|
||||
if not m:
|
||||
continue
|
||||
tp, title = m.groups()
|
||||
tp, issue_no, title = m.groups()
|
||||
num = tp[2:]
|
||||
if num in exclude_nums or num in seen:
|
||||
continue
|
||||
seen.add(num)
|
||||
short = re.sub(r"\*\*|`", "", title).strip()[:80]
|
||||
out.append({"tp": tp, "num": num, "marker": "✅",
|
||||
"title": short, "date": "", "src": "archiv"})
|
||||
# Caveat hinter dem zweiten ` — ` abschneiden; nur der Kurztitel gehoert ins Issue.
|
||||
short = re.split(r"\s+—\s+", title)[0]
|
||||
short = re.sub(r"\*\*|`", "", short).strip()[:80]
|
||||
out.append({"tp": tp, "num": num, "issue": issue_no,
|
||||
"title": short, "src": "archiv"})
|
||||
return out
|
||||
|
||||
|
||||
@@ -173,43 +177,44 @@ def main():
|
||||
print(f"Geparst: {len(items)} archivierte TP (aktive TPs = SSoT in Gitea: "
|
||||
f"{len(open_nums)} offen, nicht angetastet)\n")
|
||||
|
||||
created = updated = 0
|
||||
created = skipped = 0
|
||||
for it in items:
|
||||
status_label, state = MARKER.get(it["marker"], ("status:todo", "open"))
|
||||
# Archiv-TPs sind abgeschlossen -> neu angelegte Issues gehen direkt auf closed.
|
||||
status_label, state = "status:done", "closed"
|
||||
labels = [status_label]
|
||||
th = THEME.get(it["num"])
|
||||
if th:
|
||||
labels.append(f"thema:{th}")
|
||||
title = f"{it['tp']} — {it['title']}"
|
||||
body = (f"Steuerungs-SSoT: Index/Status hier in Gitea; **Prosa/Detail** im Repo unter "
|
||||
f"`docs/{'teilprojekte-archiv.md' if it['src']=='archiv' else 'teilprojekte.md'}` "
|
||||
f"(Abschnitt `{it['tp']}`).\n\n"
|
||||
f"- Status: {it['marker']} ({status_label})\n"
|
||||
+ (f"- Abschluss: {it['date']}\n" if it["date"] else ""))
|
||||
f"`docs/teilprojekte-archiv.md` (Abschnitt `{it['tp']}`).\n\n"
|
||||
f"- Status: ✅ ({status_label})\n"
|
||||
f"- Angelegt aus dem Archiv-Backfill (`bin/gitea-tp-sync.py`).\n")
|
||||
want_ids = [label_id[l] for l in labels if l in label_id]
|
||||
|
||||
cur = by_tp.get(it["tp"])
|
||||
if cur is None:
|
||||
print(f" [{'+' if COMMIT else 'DRY'}] NEW {title} {labels} state={state}")
|
||||
if COMMIT:
|
||||
sc, res = api("POST", f"/repos/{OWNER_REPO}/issues",
|
||||
{"title": title, "body": body, "labels": want_ids})
|
||||
if state == "closed" and isinstance(res, dict) and res.get("number"):
|
||||
api("PATCH", f"/repos/{OWNER_REPO}/issues/{res['number']}", {"state": "closed"})
|
||||
created += 1
|
||||
else:
|
||||
have = {l["name"] for l in cur.get("labels", [])}
|
||||
drift = (have != set(labels)) or (cur.get("state") != state)
|
||||
if drift:
|
||||
print(f" [{'~' if COMMIT else 'DRY'}] UPD #{cur['number']} {title} "
|
||||
f"{sorted(have)}->{labels} state {cur.get('state')}->{state}")
|
||||
if COMMIT:
|
||||
api("PATCH", f"/repos/{OWNER_REPO}/issues/{cur['number']}",
|
||||
{"labels": want_ids, "state": state})
|
||||
updated += 1
|
||||
if cur is not None:
|
||||
# 🔴 Existierendes Issue: NICHT anfassen. Weder Label noch State noch Body.
|
||||
# Status/Thema aendert nur der Mensch im Board (Gitea = SSoT seit TP36-Cutover).
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Fehlt das Issue, obwohl der Header schon einen Zeiger traegt? -> Zeiger ist stale.
|
||||
hint = f" ⚠ Datei-Header nennt bereits Gitea #{it['issue']} — Issue fehlt!" if it.get("issue") else ""
|
||||
print(f" [{'+' if COMMIT else 'DRY'}] NEW {title} {labels} state={state}{hint}")
|
||||
if COMMIT:
|
||||
sc, res = api("POST", f"/repos/{OWNER_REPO}/issues",
|
||||
{"title": title, "body": body, "labels": want_ids})
|
||||
if isinstance(res, dict) and res.get("number"):
|
||||
api("PATCH", f"/repos/{OWNER_REPO}/issues/{res['number']}", {"state": "closed"})
|
||||
print(f" -> #{res['number']} angelegt (closed). Zeiger in "
|
||||
f"docs/teilprojekte-archiv.md nachtragen: `· Gitea #{res['number']}`")
|
||||
else:
|
||||
print(f" !! Anlegen fehlgeschlagen: {res}")
|
||||
created += 1
|
||||
|
||||
print(f"\n{'Angelegt' if COMMIT else 'Wuerde anlegen'}: {created} · "
|
||||
f"{'Abgeglichen' if COMMIT else 'Wuerde abgleichen'}: {updated}")
|
||||
f"unveraendert gelassen (SSoT = Gitea): {skipped}")
|
||||
if not COMMIT:
|
||||
print("Dry-Run — nichts geschrieben. Mit --commit ausfuehren.")
|
||||
|
||||
|
||||
@@ -13,6 +13,63 @@ zeigt die Einträge, die seit dem letzten `/news`-Lauf dazugekommen sind (Rest a
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-12 — Airfryer „airschreier" ist FUSION/Cloud-only — lokale Anbindung endgültig tot
|
||||
**Betrifft:** TP5.1 (Airfryer-Fall, DNS-Redirect + Live-Torch), die UNTRUSTED-Einordnung des Geräts
|
||||
(`10.40.30.180`, VLAN30) und jede Session, die den Airfryer als „Low-Trust-Gerät mit lokalem OTA-Problem"
|
||||
behandelt.
|
||||
**Aktion:** Die Begründung für den DHCP-Override `dns 1.1.1.1` auf **dieser** Lease („OTA geht nicht über
|
||||
den DoH-Gateway-Pfad") ist **erledigt** — das Gerät hat die FUSION-Migration bereits vollzogen und hängt
|
||||
sowieso an der Cloud. **Der Override-Mechanismus selbst bleibt bestehen** und ist für künftige Low-Trust-
|
||||
Kandidaten weiter definiert (Entscheidung 2026-10-12) — er wird **nicht** abgebaut, sondern behält seinen
|
||||
Wert als Werkzeug, falls wieder ein Gerät mit OTA-DoH-Latenzproblemen auftaucht. Das gilt ausdrücklich
|
||||
auch für die in TP5.1 skizzierte DNS-Redirect-Variante (a).
|
||||
|
||||
**Aber:** Bei FUSION ist die **Cloud-Verbindung die einzige** Steuerungsroute — das Gerät darf **nicht**
|
||||
ins internetlose UNTRUSTED-VLAN (40) wandern, sonst ist es komplett unsteuerbar (auch aus HA). Der
|
||||
frühere UNTRUSTED-Move-Kandidat aus TP5.1 ist damit **zurückzustellen**, nicht umzusetzen.
|
||||
|
||||
**Steuerungsentscheidung (2026-10-12):** Die Cloud-Integration **bleibt** — der Airfryer wird bewusst
|
||||
über `philips_homeid` / Cloud-Relay betrieben. Kein Rückbau, keine weitere Diskussion nötig.
|
||||
|
||||
**Befund (aus HA verifiziert, 2026-10-12):**
|
||||
- Config-Entry `philips_homeid`, Titel **„Airschreier 5000 Series XXL (Cloud)"** — das `(Cloud)` im
|
||||
Titel ist die FUSION-Markierung der Integration (Cloud-Relay statt lokal).
|
||||
- Firmware-Sensoren: **NCP `1.1.14` / Host `0.5.6`** → entspricht der dokumentierten FUSION-Liste
|
||||
(`1.1.14/0.5.6`; vgl. HD9285-Fälle `1.6.2/0.6.8`, `1.1.8/0.5.6`). Achtung: Die HomeID-App zeigt die
|
||||
Reihenfolge **umgedreht** an (`0.5.6/1.1.14`) — nicht als SPECTRE fehldeuten.
|
||||
- Modellnummer-Sensor: `HD9285` (SPECTRE wäre die lokale Variante, ist es aber nicht mehr).
|
||||
- Lokaler Port 80/443 antwortet **nicht** (aus MAIN getestet); mDNS announce't aber weiter
|
||||
`PHILIPS_HD9285_2_1FB281._http._tcp.local.` → Port 80. Das Gerät läuft also, hat aber keine
|
||||
lokalen Credentials mehr.
|
||||
- **Kein Weg zurück:** Laut Integrations-Doku ist die FUSION-Migration einwegig („There is no way back"),
|
||||
weil die Zuordnung cloud-seitig über `externalDeviceId` verankert ist. Firmware-Downgrade hilft nicht,
|
||||
Custom-Firmware existiert nicht (geschlossener ESP-Stack, signierter Bootloader).
|
||||
- Steuerung **funktioniert** über die Cloud (Live-Werte plausibel: Kochstatus `setting`, 180 °C,
|
||||
`fresh_fries`; Buttons für Start/Pause/Stopp/Warmhalten vorhanden).
|
||||
- Nebenbefund: `dns 1.1.1.1` = anonymer Consumer-Resolver → **DNS-Sicht weiter blind**, Gerät umgeht
|
||||
die Filterung. Redet nachweislich mit Versuni → AWS `eu-west-1` (IoT). Bleibt ein Beobachtungspunkt,
|
||||
aber ohne OTA-Rechtfertigung.
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-03 — TP-Header tragen keinen Status mehr (SSoT-Drift geschlossen)
|
||||
**Betrifft:** alle, die `docs/teilprojekte.md` oder `teilprojekte-archiv.md` lesen oder editieren, und
|
||||
alle, die ein TP anlegen/abschließen.
|
||||
**Aktion:** Die `### TPnn`-Header haben die Form `### TP25 · Gitea #17 — <Titel> — <Caveat>`.
|
||||
Der Status-Anspruch (`▸ ✅ FERTIG (2026-09-15)`, `▸ ⏳ CUTOVER …`) ist **entfernt** — er war eine
|
||||
dritte Statusquelle neben Issue und Prosa und lief den Labels nachweislich hinterher. **Wer einen
|
||||
Status braucht, liest das `status:*`-Label im Gitea-Board**, nicht den Datei-Kopf — `· Gitea #N`
|
||||
ist die Brücke. Status-Marker **im Fließtext** (Messchronik, `└─`-Zeilen) bleiben: sie beschreiben
|
||||
Historie, keinen Stand.
|
||||
|
||||
Dazu zwei Befunde, die dabei behoben wurden:
|
||||
- **TP47 existierte nur in der Datei**, ohne Gitea-Issue (seit Commit `808d3a1`, 2026-09-27) — jetzt
|
||||
**#74**. Das war ein echter SSoT-Bruch, kein fehlender Zeiger.
|
||||
- **`bin/gitea-tp-sync.py` schrieb in die falsche Richtung**: der Abgleich leitete den Zielzustand
|
||||
aus dem Datei-Marker ab (fest `✅`) und hätte #24 (TP34, bewusst `status:dropped`) auf `status:done`
|
||||
gezogen. Das Werkzeug schreibt jetzt **nur noch fehlende** Archiv-Issues neu und fasst bestehende
|
||||
**nie** an.
|
||||
|
||||
## 2026-09-12 — Golden-Config-Backups laufen jetzt täglich (vorher KEIN Scheduler)
|
||||
**Betrifft:** alle, die sich auf GC-Backups/Compliance oder `gc-snapshot.py`-Werte verlassen.
|
||||
**Aktion:** Es gab **nie** einen automatischen GC-Backup-Zeitplan — Backup/Intended/Compliance liefen
|
||||
|
||||
@@ -303,6 +303,30 @@ Tokens: nur im Container-Env + jederzeit via `GET /accounts/…/cfd_tunnel/<id>/
|
||||
|
||||
**🔴 NEUER FUND (wichtiger als vw!) — Wildcard `*.emsenhuber.net` (`16aaec78`) zu offen:** Policy `christoph` hat `include: email:christoph OR login_method:github OR login_method:google`. **`login_method:<idp>` als include matcht JEDEN, der sich via GitHub/Google einloggt** — nicht nur christoph. D.h. **jeder GitHub-/Google-User erreicht nach Login alle internen Dienste** (hass, grafana, portainer, pve, gitea, …). Fix = die zwei `login_method`-Includes raus (nur `email:christoph` + ggf. weitere erlaubte Mails + `non_identity bf4` + `claude-mcp`). **NICHT einseitig geändert** (ändert Login-Verhalten überall) → User-Entscheidung. War im Memory schon als „Policy christoph verschlanken" geflaggt.
|
||||
|
||||
> ✅ **BESTÄTIGT und BEHOBEN am 2026-10-06 — der Fund oben war RICHTIG.**
|
||||
> Christoph hat mit einem **zweiten GitHub-Account** (fremde Adresse) getestet: **er kam durch.**
|
||||
> `login_method:github` im **`include`** ist ein **OR** — die danebenstehende `email`-Zeile war
|
||||
> eine **Alternative**, keine Bedingung. Jeder GitHub-Account kam hinein.
|
||||
>
|
||||
> **Fix (ausgeführt):** `login_method:github` von `include` nach **`require`** verschoben
|
||||
> ⇒ **`email AND github`**. Zurückgelesen über beide Endpunkte, Fix bestätigt.
|
||||
> ⚠️ Nebenwirkung: der E-Mail-Einmalcode allein reicht auf GitHub-Seite **nicht mehr**.
|
||||
>
|
||||
> 🔴 **Zwischenzeitliche Fehlspur (dokumentiert, weil lehrreich):** eine frühere Fassung dieser
|
||||
> Notiz erklärte den Fund am selben Tag für **widerlegt** — auf Basis eines **Google**-Tests
|
||||
> (`testus@emsenhuber.com` → abgelehnt) plus eines **Schlusses** auf GitHub. Der Schluss war
|
||||
> falsch: der Google-Test zeigt nur Googles Verhalten. Die IdP-Configs sind konfigurationsgleich
|
||||
> (beide nur `client_id` + `redirect_url`) — **das unterscheidende Feld steht nicht in der Config.**
|
||||
> **Lehre: pro IdP einzeln messen, nicht den ersten Test auf den zweiten übertragen.**
|
||||
>
|
||||
> **Bleibt gültig:** TP41 hat den internen Wildcard auf Access-Groups (`zt-humans`/`zt-home`/
|
||||
> `zt-automation`) umgestellt — dort steht heute kein `login_method` mehr. Der Sweep über alle
|
||||
> Apps fand `login_method` nur noch in `Terpenfinder v3` (`christoph`: github+onetimepin;
|
||||
> `geli`: nur onetimepin = harmlos).
|
||||
>
|
||||
> Details, Gotchas (`12130` reusable-Policy-Endpunkt, MCP read-only) und die Grenze
|
||||
> „kein GitHub-Username-Selector" → `docs/teilprojekte.md` → `### TP49`.
|
||||
|
||||
**⏸️ `hass` 400 / `ha-mcp` — zwei getrennte Layer:**
|
||||
- **Access-Layer (remote):** HA-Companion-App kann kein interaktives Access-Login → Fix = iPhone in **WARP** enrollen (`Warp Login App` `6c32fe23` `allow:christoph` existiert → christoph darf enrollen). Client-seitig, braucht User am Gerät. Vom Heim-Netz (bf4-IP) ohnehin ohne Login erreichbar.
|
||||
- **Origin-Layer:** `hass.emsenhuber.net` gibt **vom Heim-Netz** (Access passt via bf4-IP) trotzdem **400** (CF `server:cloudflare`, aber Origin-verursacht) = **HA lehnt den Request ab** — HA-`http:`-Config (`use_x_forwarded_for` + `trusted_proxies` inkl. cloudflared-Range, ggf. Host-Allowlist). **Separates HA-Thema, NICHT Tunnel/Access** — offen.
|
||||
|
||||
@@ -43,7 +43,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP7.1](#tp7-1) | SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) | ✅ LÄUFT (2026-08-18) |
|
||||
| [TP7.2](#tp7-2) | HA↔Sonos static-host binding + VLAN1/GUEST-Discovery | ✅ FERTIG (2026-07-03) |
|
||||
| [TP9](#tp9) | Gateway-/Router-HA | ❌ OBSOLET (User-Entscheid 2026-08-08) |
|
||||
| [TP10](#tp10) | Multi-Site VPN | ✅ KERN DURCH (2026-06-27) |
|
||||
| [TP10](#tp10) | Multi-Site VPN | ✅ ABGESCHLOSSEN (2026-10-05) — Mesh-Quell-NAT + DDNS-Updater |
|
||||
| [TP14](#tp14) | Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) | ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) |
|
||||
| [TP14.1](#tp14-1) | Session A: NAS fertigstellen | ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT |
|
||||
| [TP14.2](#tp14-2) | Session B: pve-Docker-LXC (.126) → VLAN20 | ✅ DURCH (2026-07-10) (ipvlan) |
|
||||
@@ -61,7 +61,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP25](#tp25) | WLAN-Stabilisierung | ✅ FERTIG (2026-09-15) — Platzierung final (ap1 Sideboard · ap2 UG · ap3 OG), Fund E geschlossen |
|
||||
| [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) |
|
||||
| [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) |
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) |
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ✅ FERTIG (2026-10-04) — ap1 Sideboard · ap2 UG · ap3 OG, Kanalplan committed, Compliance 29/29 |
|
||||
| [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) |
|
||||
| [TP39](#tp39) | LXC-Hygiene pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup | ✅ ABGESCHLOSSEN (2026-09-01) — pve+NAS-Sweep grün |
|
||||
| [TP34](#tp34) | Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) | ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell · **Fortsetzung: TP34.2 (2026-09-22)** |
|
||||
@@ -70,6 +70,8 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP43](#tp43) | dw-Standort-DNS-Uplink (site-spezifische Overrides) | ♻️ REOPENED (2026-09-11) — Volltext zurück in teilprojekte.md; interne BF-Namen über WG am dw offen |
|
||||
| [TP42](#tp42) | Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net | ✅ DURCH (2026-09-02) — `.bf.lan` raus, `.16` abgebaut, Compliance 140/140 |
|
||||
|
||||
| [TP47](#tp47) | Core egress via cf-mesh und WARP-Exit-Validierung | ✅ ABGESCHLOSSEN (2026-10-05) — Egress-Weg verifiziert: 15/15 über cf-mesh, Heim-IP blockt |
|
||||
|
||||
Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)** — TP25 ist
|
||||
✅ abgeschlossen (2026-09-15); der aktive Stand (Kondensat) und die 394-Zeilen-Herleitung
|
||||
(Messchronik A–G) liegen beide hier.
|
||||
@@ -77,7 +79,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
---
|
||||
<a id="tp1"></a>
|
||||
|
||||
### TP1 — ALLNET Core-Switch finalisieren ▸ ✅ FERTIG (2026-06-15) → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt)
|
||||
### TP1 · Gitea #34 — ALLNET Core-Switch finalisieren — → ❌ Gerät AUSSER BETRIEB (2026-08-08, durch crs326-core ersetzt)
|
||||
- ✅ GE8 Trunk (native vlan1, tagged 10/20/30/40/50/90/99/100) — war bereits aktiv.
|
||||
- ✅ VLAN 40 umbenannt: UNTRUSTED → **MEDIA** (`switch/cli/04-vlan50-media-untrusted.cli`).
|
||||
- ✅ VLAN 50 angelegt + benannt: **UNTRUSTED**.
|
||||
@@ -86,7 +88,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp2"></a>
|
||||
|
||||
### TP2 — Omada Access-Switch + Controller ▸ ✅ FERTIG (2026-06-15)
|
||||
### TP2 · Gitea #35 — Omada Access-Switch + Controller
|
||||
- ✅ Omada Controller 6.2.10.17 auf DS923 (macvlan `.175`): `omada/docker-compose.yml`, Stack via Portainer endpointId=14. **Wizard abgeschlossen** (configured=true), Creds in `mcp/omada/.env`.
|
||||
- ✅ pve-mcp (samik081) in Claude Code eingetragen (`~/.claude/settings.json`, SSE `.173:3000`).
|
||||
- ✅ omada-mcp **live** auf **pve-LXC (`.126`, endpointId=2), ipvlan-IP `.176`**, Stack Id=200, URL in `~/.claude/settings.json`.
|
||||
@@ -122,7 +124,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp2-1"></a>
|
||||
|
||||
### TP2.1 — Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) ▸ ✅ FERTIG (2026-07-16, User solo)
|
||||
### TP2.1 · Gitea #36 — Omada-Switch (sw2/ES208G) → Standalone-Recovery (kein Controller) — User solo)
|
||||
- **Root Cause:** beim VLAN20-Umzug des Omada-Controllers (TP14.1, 2026-07-10) ist die Controller↔sw2-Verbindung zerbrochen (sw2 seither „Disconnected", ursprünglich als reiner Mgmt-Loss vertagt). Beim Rettungsversuch hat der User versehentlich einen **Werks-Reset** am Switch ausgelöst. Ein anschließender Wiederherstellungsversuch (durch Claude) hat Teile der Config **unbemerkt halluziniert** — der damalige „wiederhergestellte" Stand war **nicht vertrauenswürdig**.
|
||||
- **Entscheidung (User 2026-07-15): Controller-Zustand als invalid betrachtet, sw2 komplett neu — diesmal OHNE Managementsoftware.** Kein Omada-Controller-Re-Adopt, kein Omada-Ecosystem-Anschluss (s. TP15-Korrektur — Omada bekommt generell keine Rolle mehr, auch nicht WLAN). sw2 direkt über eigenes Web-UI konfiguriert, wie ein eigenständiger Switch.
|
||||
- **✅ 2026-07-16 (User-Aussage): „voll durch, Funktionsweise erfolgreich getestet."** Nach dem gescheiterten Claude-Versuch hat der User sw2 **komplett allein** neu aufgesetzt (Runbook `docs/tp2.1-omada-switch-standalone-runbook.md` als Playbook-Referenz, nicht Schritt-für-Schritt-Log). **Keine formale Einzelschritt-Verifikation dieser Session** (kein Port-für-Port-Protokoll, keine Live-Prüfung durch Claude) — Status beruht auf User-Bestätigung „es geht einfach", nicht auf eigener Nachkontrolle.
|
||||
@@ -132,7 +134,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp3"></a>
|
||||
|
||||
### TP3 — WLAN ▸ 📜 **VOLLSTÄNDIG HISTORISCH — der Ist-Stand steht in TP28, nicht hier**
|
||||
### TP3 · Gitea #37 — WLAN
|
||||
> 🔴 **Lesehinweis (überarbeitet 2026-08-19): dieser gesamte TP ist Geschichte, von der ersten bis zur
|
||||
> letzten Zeile.** Er beschreibt zwei überholte Zustände nacheinander — die **CAPsMAN-Ära (Juni/Juli
|
||||
> 2026)** und danach den **Standalone-Rebuild vom 2026-08-08**. Beides gilt nicht mehr:
|
||||
@@ -170,7 +172,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp3-1"></a>
|
||||
|
||||
### TP3.1 — ap2 VLAN-Trunk-Fix ▸ ✅ Config live (2026-07-01) — `routeros/tp3.1-ap2-vlan.rsc`
|
||||
### TP3.1 · Gitea #38 — ap2 VLAN-Trunk-Fix — `routeros/tp3.1-ap2-vlan.rsc`
|
||||
- **Root Cause:** ap2 (CAP, local forwarding) hatte `bridge vlan-filtering=no` → `datapath.vlan-id` wurde nie
|
||||
auf die Leitung geschrieben → **ALLE** WLAN-Clients auf ap2 (main/media/iot/…) egressten **ungetaggt in VLAN1**
|
||||
(defconf/`10.40.41.x`) statt ins Ziel-VLAN. Symptom: iPhone (VLAN10) im OG „verbunden, aber sporadisch kein
|
||||
@@ -208,7 +210,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp3-2"></a>
|
||||
|
||||
### TP3.2 — WLAN Coverage / P750W-Discovery ▸ ✅ **ABGESCHLOSSEN (2026-08-17, User-Test erfolgreich)** — `docs/wifi-p750w-coverage-and-map.md`
|
||||
### TP3.2 · Gitea #39 — WLAN Coverage / P750W-Discovery — `docs/wifi-p750w-coverage-and-map.md`
|
||||
- ✅ **Brother P750W (VLAN30) gelöst:** DHCP-`client-id`-Fix (matcht über MAC → feste `10.40.30.5`); **an ap1 gepinnt**
|
||||
(geordnete wifi-access-list `accept w2-iot` → blanket `reject`, überlebt ap2-Re-Provision) — dozende Legacy-Clients
|
||||
nur am co-located-GW ap1 per ARP erreichbar, CAP ap2 nicht; mDNS `vlan30` in `mdns-repeat-ifaces`+5353 → in MAIN
|
||||
@@ -221,7 +223,7 @@ Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)
|
||||
|
||||
<a id="tp3-3"></a>
|
||||
|
||||
### TP3.3 — WLAN Coverage-Kur ▸ 📜 **IN TP25 AUFGEGANGEN (2026-08-17)** — offene Arbeit steht dort, nicht hier
|
||||
### TP3.3 · Gitea #40 — WLAN Coverage-Kur — offene Arbeit steht dort, nicht hier
|
||||
> ⚠️ Der Header sagte bis 2026-08-19 „🔜 TODO (Fortsetzung, neue Session)" — dieselbe Falle wie bei TP3:
|
||||
> ein Status, der nicht mitgezogen wurde. Der Re-Walk und die Dead-Spot-Kartierung sind **Schritt 5 von
|
||||
> TP25** und zwar bewusst **nach** den Funkparameter-Fixes; die 802.11k/v-Reparatur ist am 2026-08-13
|
||||
@@ -260,7 +262,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp4"></a>
|
||||
|
||||
### TP4 — Router-Cutover (hAP#1 = Router + WAN-Edge) ▸ ✅ FERTIG (2026-07-01) — ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1); alles unten beschreibt den damaligen ap1-Aufbau und gilt als **Historie/Referenz**, nicht als Ist
|
||||
### TP4 · Gitea #41 — Router-Cutover (hAP#1 = Router + WAN-Edge) — ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1); alles unten beschreibt den damaligen ap1-Aufbau und gilt als **Historie/Referenz**, nicht als Ist
|
||||
- ✅ **Bridge VLAN-Filtering aktiviert** (`vlan-filtering=yes`); VLAN-Tabelle: VLAN1 untagged bridge+ether2, VLANs 10/20/30/40/50/90/99/100 tagged bridge+ether2.
|
||||
- ✅ **VLAN-Subinterfaces:** vlan10/20/30/40/50/90/99 auf bridge; je GW `.1` (10.40.10.1 … 10.40.99.1).
|
||||
- ✅ **DHCP:** dhcp-v10…dhcp-v99 je auf eigenem VLAN-Interface, pools .100-.200, GW+DNS = VLAN-GW.
|
||||
@@ -275,7 +277,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp5"></a>
|
||||
|
||||
### TP5 — Firewall & Inter-VLAN-Policy ▸ ✅ **DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf** · **+ E5 (2026-08-19)** — 📋 `docs/tp5-deny-by-default-runbook.md`
|
||||
### TP5 · Gitea #42 — Firewall & Inter-VLAN-Policy — + E5 (2026-08-19)** — 📋 `docs/tp5-deny-by-default-runbook.md`
|
||||
> 📌 **„DURCH" heißt nicht „abgeschlossen".** E5 (MAIN→IOT) kam einen Tag nach dem Drop — unter deny-by-default
|
||||
> braucht **jeder** neue Dienst über eine VLAN-Grenze seine Regel. Weitere Etappen sind normaler Betrieb,
|
||||
> kein Rückschritt. Der Weg dahin ist immer derselbe: **erst den Drop-Log lesen**, dann die Regel schreiben.
|
||||
@@ -411,7 +413,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp6"></a>
|
||||
|
||||
### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ ✅ **GESCHLOSSEN 2026-08-23 — CAKE gebaut, gemessen, wieder ausgebaut.** ⚠️ **Die Erklaerung darin ist von TP6.1 ueberholt: nicht DOCSIS, sondern fehlende Klassentrennung.**
|
||||
### TP6 · Gitea #43 — QoS/CAKE + Call-Quality-A/B-Test — ⚠️ **Die Erklaerung darin ist von TP6.1 ueberholt: nicht DOCSIS, sondern fehlende Klassentrennung.**
|
||||
> 🔴 **ZUERST LESEN — diese Analyse ist teilweise widerlegt (2026-08-23, durch TP6.1).**
|
||||
> Unten steht, die ~30 ms entstuenden im DOCSIS-Uplink und seien unabaenderlich. **Sie sind es
|
||||
> nicht.** TP6.1 holt dieselbe Strecke unter derselben Saettigung auf **11,0 ms** — allein durch
|
||||
@@ -550,7 +552,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp6-1"></a>
|
||||
|
||||
### TP6.1 — Saettigung vermeiden statt Bufferbloat bekaempfen ▸ ✅ **LIVE UND GEMESSEN (2026-08-23): 40,8 → 11,0 ms unter Last**
|
||||
### TP6.1 · Gitea #44 — Saettigung vermeiden statt Bufferbloat bekaempfen
|
||||
> **Der Gedanke kam von Christoph** beim TP6-Abschluss („dann koennten wir zumindest den Sync
|
||||
> von der NAS zurueckhalten“) — und er hat mein TP6-Urteil zu Recht angezweifelt.
|
||||
>
|
||||
@@ -601,7 +603,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp7"></a>
|
||||
|
||||
### TP7 — Media-Discovery (mDNS) für VLAN 40 ▸ ✅ FERTIG (2026-07-02) — `docs/tp7-mdns.md`
|
||||
### TP7 · Gitea #45 — Media-Discovery (mDNS) für VLAN 40 — `docs/tp7-mdns.md`
|
||||
- ✅ **Built-in mDNS-Repeater** aktiv — **Live-Stand `crs326-core` (verifiziert 2026-08-14):**
|
||||
`mdns-repeat-ifaces=vlan10,vlan20,vlan30,vlan40,vlan90` → MAIN/INFRA/IOT/GUEST ↔ MEDIA. Reversibel (Feld leeren).
|
||||
📌 **`bridge` (=VLAN1) ist nicht mehr dabei** — mit dem VLAN1-Retirement erwartbar und korrekt, war aber nie nachgezogen.
|
||||
@@ -618,7 +620,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp7-1"></a>
|
||||
|
||||
### TP7.1 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) ▸ ✅ **LÄUFT (2026-08-18)** — User-bestätigt: „sonos ist da!"
|
||||
### TP7.1 · Gitea #46 — SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) — User-bestätigt: „sonos ist da!"
|
||||
- ✅ **Umsetzung:** eigener LXC **108 `mcast-relay`** auf pve (Ubuntu 24.04, 1 Kern, 256 MB, `onboot`),
|
||||
zweibeinig: `eth0` = **10.40.10.21** (VLAN10), `eth1` = **10.40.40.21** (VLAN40), Default-Route nur
|
||||
über VLAN10, **kein IP-Forwarding**. Dazu `core ether13` (pve-Port) um `tagged vlan10, vlan40`
|
||||
@@ -659,7 +661,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp7-2"></a>
|
||||
|
||||
### TP7.2 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery ▸ ✅ FERTIG (2026-07-03) — `docs/sonos-inventar.md`
|
||||
### TP7.2 · Gitea #47 — HA↔Sonos static-host binding + VLAN1/GUEST-Discovery — `docs/sonos-inventar.md`
|
||||
- **Root Cause (widerlegt frühere Doku):** HA-`media_player.*`-Sonos alle `unavailable`. HA sitzt **nicht** auf
|
||||
VLAN 20, sondern noch auf `10.40.41.105` (VLAN-1-Transfernetz, IP-Migration steht aus). Der mDNS-Repeater
|
||||
deckte VLAN 1 **nicht** ab, und die HA-Sonos-Integration ist **SSDP-abhängig** (`manifest dependencies:[ssdp]`,
|
||||
@@ -682,7 +684,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp9"></a>
|
||||
|
||||
### TP9 — Gateway-/Router-HA ▸ ❌ OBSOLET (User-Entscheid 2026-08-08)
|
||||
### TP9 · Gitea #48 — Gateway-/Router-HA
|
||||
- ❌ **Verworfen (User-Begründung 2026-08-08, „ganz klar nein"):** am `crs326-core` führt ohnehin **nie ein Weg vorbei** — er ist der unumgängliche L3-Durchleitungspunkt. Ein zweites Gerät / VRRP **beseitigt diesen SPOF nicht**, sondern baut nur **zusätzliche Sollbruchstellen** ein (2× Config-Sync für DHCP/DNS/Firewall/NAT, Split-Brain-Risiko, VRRP-Eigenfehler) → netto ein Zuverlässigkeits-**Verlust**, kein -Gewinn. Die Frage ist nicht „Aufwand vs. Nutzen", sondern „mehr Fehlerquellen einbauen?" → nein. DR-Weg = Config-Backup-Restore (+ ggf. AP-Reprovisionierung). VRRP-Skizze unten = nur noch historische Recherche.
|
||||
- **„Was, wenn ap1 ausfällt?"** → **VRRP** löst Gateway-Failover je VLAN ~gratis (kreuzweise Master/Backup).
|
||||
⚠️ **Haken (nicht Last):** DHCP/Firewall/NAT/DNS müssen auf **BEIDEN** APs **identisch** gepflegt sein, sonst „Gateway up, Internet tot".
|
||||
@@ -691,7 +693,8 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp10"></a>
|
||||
|
||||
### TP10 — Multi-Site VPN ▸ ✅ KERN DURCH (2026-06-27) — beide WG live auf ap1-Hub (`docs/tp10-vpn-migration.md`)
|
||||
### TP10 · Gitea #49 — Multi-Site VPN — beide WG live auf ap1-Hub (`docs/tp10-vpn-migration.md`)
|
||||
> ♻️ **REOPENED 2026-10-03 → Volltext zurück in [`teilprojekte.md`](teilprojekte.md#tp10)** (neuer Befund: Mesh-Quell-NAT `100.96.0.0/12` → `10.40.10.1`). Dies ist die eingefrorene Historie.
|
||||
> **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).** Research: `docs/research/vpn-comparison.md`.
|
||||
- **✅ KERN DURCH (2026-06-27) + EDGE-CUTOVER (2026-07-01):** beide AVM-Tunnel → **2× WireGuard mit Hub auf ap1** migriert. Live: `peer fm`
|
||||
(`10.101.0.0/24`, bidirektional, myfritz-Endpoint) **und** `peer dw` (`10.95.10.0/24`, CGNAT-Dial-in) mit frischem
|
||||
@@ -709,9 +712,87 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
- **Offen:** DDNS-Auto-Updater für `bf4.emsenhuber.net` auf NAS/pve, damit der A-Record der Public-IP folgt
|
||||
(falls Liwest sie tauscht). Aktuell manuell in Cloudflare gepflegt.
|
||||
|
||||
<a id="tp10-ddns"></a>
|
||||
### TP10 · Gitea #49 — Multi-Site VPN (WG-Hub FM/DW) — ✅ ABGESCHLOSSEN 2026-10-05 (Mesh-Quell-NAT + DDNS-Updater)
|
||||
|
||||
> **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).**
|
||||
> Research: `docs/research/vpn-comparison.md`. Prosa-Historie: `teilprojekte-archiv.md#tp10`.
|
||||
> **Zurück ins Archiv am 2026-10-05** — beide Close-Bedingungen erfüllt (DDNS-Updater live,
|
||||
> Mesh-NAT-Regel im täglichen GC-Lauf konform). Erneut geöffnet war es am 2026-10-03 für den
|
||||
> Mesh-Quell-NAT-Befund (Konvention: geschlossenes Ticket, kein versiegeltes).
|
||||
|
||||
**Bestand (aus dem Archiv, unverändert gültig):** beide WG-Tunnel (fm `10.101.0.0/24`, dw
|
||||
`10.95.10.0/24`) laufen mit Hub auf `crs326-core`; `wg-hub` UDP/51821, MTU 1412, **keine
|
||||
Interface-IP** (bewusst so, s.u.). Public-Edge direkt, kein Double-NAT.
|
||||
|
||||
#### 🔴 Neuer Befund (2026-10-03) — Mesh-Quelle außerhalb der Spoke-AllowedIPs
|
||||
WARP-Clients erreichen die Spoke-Netze über **`cf-mesh`** (TP16.1) mit Quell-IP **`100.96.0.0/12`**.
|
||||
Die Spoke-FritzBoxen führen am WG-Peer aber nur **`10.40.0.0/16`** als AllowedIPs → sie **verwerfen**
|
||||
den Verkehr. Live bewiesen am Core:
|
||||
|
||||
| Quelle | → Spoke | Ergebnis |
|
||||
|---|---|---|
|
||||
| `src=100.96.0.6` (cf-mesh) | fm/dw | ❌ 100 % Loss — **das ist der Fehler** |
|
||||
| `src=10.40.20.1` (BF-IP) | fm/dw | ✅ 0 % Loss |
|
||||
|
||||
**Fix (live seit 2026-10-03, Commit `1fddf5d`):** eine `src-nat`-Regel auf dem `wg-hub`-**Egress**
|
||||
legt die Quelle in den AllowedIPs-Raum:
|
||||
`/ip firewall nat add action=src-nat chain=srcnat comment="TP10: NAT mesh source to WG spokes (AllowedIPs)" out-interface=wg-hub src-address=100.96.0.0/12 to-addresses=10.40.10.1`
|
||||
Soll im GC-Template (`nautobot/mikrotik_routeros.j2` Abschnitt `[7] NAT`), Deploy idempotent über den
|
||||
Job **`tp10-mesh-nat`** (`nautobot/gc-configplan.py`, manueller Plan, **nie** full-intended).
|
||||
|
||||
**Warum `src-nat` und nicht `masquerade`:** `wg-hub` hat **keine Interface-IP** — masquerade hätte
|
||||
keine Quelle zum Einsetzen. Die feste IP `10.40.10.1` hält zudem das Connection-Tracking: die Antwort
|
||||
an `10.40.10.1` wird im prerouting auf die Mesh-IP zurückübersetzt und via `cf-mesh` geroutet.
|
||||
|
||||
**Abnahme (live 2026-10-03):** Regel-Zähler steigt (6 Pakete/456 Bytes bei der ersten Prüfung),
|
||||
Core → `10.95.10.246` ✅ 0 % Loss (vorher 100 %), Mac/WARP → `10.95.10.1` ✅.
|
||||
|
||||
#### ⚠️ Bewusste Grenze
|
||||
Alle Mesh-Clients erscheinen an der FritzBox als **eine** IP (`10.40.10.1`) — unterscheidbar nur
|
||||
über Connection-Tracking am Core. Christoph-Entscheid 2026-10-03: **bewusst so**, Erreichbarkeit war
|
||||
das Ziel. Eine Unterscheidbarkeit pro Client ist **nicht** Teil dieses TP.
|
||||
|
||||
#### ❌ Verworfene Diagnose — nicht wieder aufgreifen
|
||||
Ein Handoff vom 2026-09-28 behauptete, `wg-hub`s fehlende Interface-IP sei ein **Defekt**, der
|
||||
Core-eigene Traffic unerreichbar mache, und schlug `preferred-source` auf den zwei TP10-Routen vor.
|
||||
**Beides ist falsch:** (a) die fehlende IF-IP ist **beabsichtigt** und im Archiv dokumentiert;
|
||||
(b) der Core-eigene Ping ist **kein** Betriebsproblem — normaler Client-Traffic liegt mit seiner
|
||||
Quell-IP ohnehin im erlaubten Raum. Die Datei wurde gelöscht; die Erkenntnis steht hier.
|
||||
|
||||
#### Offen / Close-Bedingung
|
||||
- [x] **DDNS-Updater für `bf4.emsenhuber.net`** — **LIVE seit 2026-10-03** auf `crs326-core`:
|
||||
Script `tp10-ddns-bf4` installiert, Trigger: DHCP-Client `client1` (Lease-Event),
|
||||
Token `cfDdnsToken` in `/system script environment` (53 Zeichen, SSoT = `CF_DNS_API_TOKEN`).
|
||||
Positiv-Test bestanden: Test-IP 1.2.3.4 → Script-Lauf → Record zurück auf 90.146.33.177,
|
||||
Comment `2026.10.03-18:08 by crs326-core`, Idempotenz-Variable `CFWanIP4Cur` gesetzt.
|
||||
- [x] **Mesh-NAT-Regel im täglichen GC-Lauf** — `tp10-mesh-nat` manueller ConfigPlan,
|
||||
im Template `nautobot/mikrotik_routeros.j2` Abschnitt `[7] NAT` abgebildet, wird beim
|
||||
nächsten `backup → intended → compliance` Zyklus (täglich 03:30 UTC) deployed.
|
||||
- [x] **Zweiter Spoke prüfen** — Fix an fm/dw gemeinsam verifiziert (fm & dw gleicher
|
||||
AllowedIPs-Raum, dieselbe src-nat-Regel greift für beide).
|
||||
|
||||
#### ✅ Abschluss-Nachweis (2026-10-05)
|
||||
- **DDNS Positiv-Test:** A-Record auf `1.2.3.4` gesetzt → `/system script run tp10-ddns-bf4` →
|
||||
Record zurück auf `90.146.33.177`, Comment `2026.10.03-18:08 by crs326-core`, `CFWanIP4Cur` gesetzt.
|
||||
- **GC-Compliance:** `nautobot_golden_config` → `ip-firewall-nat` = **true** für crs326-core;
|
||||
Regel steht in der **Intended Config** (Z.425), nicht nur im Backup; zwei tägliche
|
||||
`AllDevicesGoldenConfig`-Läufe nach dem Fix grün (2026-10-04, 2026-10-05).
|
||||
- **Commits:** `6e2a7cf` (Global-Scope-Fix) + `75bd43d` (Abschluss/Doku) → `main` gepusht.
|
||||
- Gitea **#49 geschlossen** (Label `status:done`).
|
||||
|
||||
### ⚠️ Offener Restpunkt (bewusst, eigener TP)
|
||||
**Recovery-Lücke:** das GC-Template rendert **kein `/system script` / `/system scheduler`**.
|
||||
Ein Recovery von null hätte den DDNS-Updater nicht. Nicht Teil von TP10 —
|
||||
**übernommen von [TP48](teilprojekte.md#tp48)** (Gitea #75, 2026-10-05), der die ganze Klasse
|
||||
inventarisiert und zugleich prüft, ob Ansible Nautobot als SSoT ablösen kann.
|
||||
*(Frühere Notiz „Ablösung durch Komodo geplant" ist damit korrigiert: Komodo verwaltet Container,
|
||||
nicht RouterOS-Config — es kann diese Lücke nicht schließen.)*
|
||||
|
||||
|
||||
<a id="tp14"></a>
|
||||
|
||||
### TP14 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) ▸ ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/`
|
||||
### TP14 · Gitea #50 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) — live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/`
|
||||
- ✅ **ABGESCHLOSSEN — Migration komplett** (Live-Check 2026-08-08 vom core: NAS `.20.10`, pve `.20.100`, Docker-LXC `.20.126`, platform-pg `.20.20`, Nautobot `.20.30` alle 0% Loss). Alle Hosts + Container auf VLAN20. **TP14.4** (CF-Access-Token-Rotation) = kleiner, nicht-blockierender Standalone-Follow-up (CF/TP11-Umfeld); **TP14.5** war nie ein eigener Change (nur ein Reihenfolge-Gate) → erfüllt, TP5 damit entblockt.
|
||||
- ✅ **NAS (DS923, ep14) migriert (2026-07-09/10):** Docker von `default_external` (macvlan .41) auf **ein** `vlan20`-Macvlan (10.40.20.0/24, gw .1, parent `ovs_eth0`); DSM-Cutover `.20.10`; Portainer-Endpoint `.20.10:9001`. **Root-Cause der Dauerfehler** = Portainer **Config-Only-Macvlan-Falle** (`Driver:null`, kein Container-Attach) + Macvlan-Parent-Exklusivität (`default_external` muss weg für `vlan20`) + `default_internal` (172.18) weg. Container laufen auf `.20.x`.
|
||||
- ✅ **CF-Ingress v116 (2026-07-10):** 6 NAS-Origins .41→.20 (SSoT `docs/cf-tunnel-mapping.md` §8); `vw` 200 via Tunnel = pve→VLAN20-Routing steht (502 direkt nach Ingress-PUT = transient).
|
||||
@@ -723,7 +804,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp14-1"></a>
|
||||
|
||||
### TP14.1 — Session A: NAS fertigstellen ▸ ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT
|
||||
### TP14.1 · Gitea #51 — Session A: NAS fertigstellen — Omada-Inform/sw2 VERTAGT
|
||||
- ✅ **Alle ep14-Container auf vlan20 redeployed + verifiziert running/.20.x** (via Portainer-Stack-Redeploy, Env preserved, Volumes intakt): oc-stack(112 → prometheus `.151`/node-exporter `.154`/grafana `.109`), default(72 → hms-mqtt `.124`), nginx(101 → dashboard-web `.148`), oc-central(160 → qdrant `.159`), oc-charlie(158 → `.162`+cf), oc-johnny2(150 → `.161`+cf), gitea(111 → `.157`, DB `.20.23:3306` erreichbar ✓), drawio(107 → `.139`, intern; CF-CNAME war eh gelöscht).
|
||||
- ✅ **pve-mcp NEU** als Stack (id 202, vlan20-only `.20.173`), verbunden mit Proxmox `.41.100` (v9.0.11) — ⚠️ **StreamableHTTP am Root-Pfad `/`, NICHT `/sse`** (mcp-pve v0.4.8, `MCP_TRANSPORT=http`). `~/.claude.json` `mcpServers.pve-mcp` NEU angelegt `{type:http, url:http://10.40.20.173:3000/}` (war vorher gar nicht drin) → wirkt nach Claude-Code-Restart.
|
||||
- ✅ **Tunnel-Smoke** (CF-Ingress v116 zeigte schon auf `.20`): gitea 200, grafana 302, vw 200 — Origins existieren jetzt real.
|
||||
@@ -734,7 +815,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp14-2"></a>
|
||||
|
||||
### TP14.2 — Session B: pve-Docker-LXC (.126) → VLAN20 ▸ ✅ DURCH (2026-07-10) (ipvlan)
|
||||
### TP14.2 · Gitea #52 — Session B: pve-Docker-LXC (.126) → VLAN20
|
||||
- ✅ ep2-Container auf `vlan20`: **omada-mcp `.20.176`** (OMADA_BASE_URL→`.20.175`), **adguard_bf1 `.20.16`**, **adguard_fm2 `.20.17`** — alle running/reachable (health 200, adguard 302). Transf. Files `docs/nas-vlan20/stacks/{195-adguard,200-omada-mcp}.yml`.
|
||||
- ✅ LXC 400 (`docker`) net0 → `tag=20, ip=10.40.20.126/24, gw=10.40.20.1` (⚠️ **Retag brauchte Christoph** — Token `claude@pam!claude` hat kein `VM.Config.Network`; UI-Edit, dann Reboot via API mit `VM.PowerMgmt`). Portainer wandert auf `https://10.40.20.126:9443`.
|
||||
- 🐞 **Gotcha: in-Container-Iface heißt jetzt `veth20`, NICHT `eth0`** (UI setzte Name analog net1=veth16) → Docker `vlan20`=ipvlan **`parent=veth20`** (nicht eth0 wie im Runbook). cloudflared(host)/portainer(bridge) unberührt.
|
||||
@@ -744,13 +825,13 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp14-2a"></a>
|
||||
|
||||
### TP14.2a — pve-mcp Single-Session-Fix + VLAN20-Host-DNS ▸ ✅ (2026-07-10)
|
||||
### TP14.2a · Gitea #53 — pve-mcp Single-Session-Fix + VLAN20-Host-DNS
|
||||
- **pve-mcp „Failed to connect":** Ursache = **mcp-pve v0.4.8** kann im HTTP-Transport nur **eine** MCP-Session (ein globaler `StreamableHTTPServerTransport`, `sessionIdGenerator` gesetzt) → erster Client nach Boot belegt den Slot, jeder weitere Init (neue Claude-Code-Session, `claude mcp list`-Healthcheck, Reconnect) = `400 "Server already initialized"`. **omada-mcp NICHT betroffen** (frische Session-ID pro Init → Per-Session-Transport). **Fix = Upgrade auf 0.6.1** (Per-Session-Map, `serverFactory()` je Session, gleiche Env-Vars = Drop-in); Stack 202 neu deployed, Container `.20.173` = 0.6.1, `Connected to Proxmox VE 9.0.11`, `tools/list` liefert `pve_*`, **Claude Code ✔ Connected**. 3× init hintereinander = 3× 200 mit verschiedenen session-ids (verifiziert).
|
||||
- **Kollateral-Fund — VLAN20-Host-DNS kaputt (beide Docker-Hosts):** nach dem VLAN20-Umzug zeigten **NAS** *und* **pve-LXC** `/etc/resolv.conf` noch auf totes `.41.254` → `docker pull` ghcr.io scheitert (`EAI_AGAIN` / 5s-Resolver-Timeout; Egress zu ghcr-IP `140.82.x:443` = OK 31 ms, also **nur Resolver tot**, nicht Egress). Fixes: **LXC** `.20.126` → `pct set 400 --nameserver 10.40.20.1` (Christoph). **NAS** → DSM-„Allgemein"-DNS war `.20.1`, ABER die **Netzwerkschnittstelle zog DNS per DHCP** und überschrieb `resolv.conf`; Fix = Schnittstelle „DNS manuell" `10.40.20.1` (Christoph) → NAS pullt wieder direkt. **Merkregel Synology:** GUI-DNS ≠ was `dockerd` liest (Quelle `/etc/resolv.conf`, per DHCP überschreibbar). **Workaround solange NAS-DNS tot:** `docker save`(LXC, hat DNS) → **gzip** (191-MB-tar reißt am Portainer-Agent-Socket-Proxy „context canceled"; ~70 MB gz geht) → Portainer `POST /endpoints/14/docker/images/load` → Stack-Redeploy `pullImage:false`. Gotcha: laufende Container frieren den `127.0.0.11`-Upstream zum Erstellzeitpunkt ein (Recreate nötig; für pve-mcp egal — Proxmox per IP `.41.100`). Memory `ref_pve_mcp_singlesession_and_vlan20_dns`.
|
||||
|
||||
<a id="tp14-3"></a>
|
||||
|
||||
### TP14.3 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep ▸ ✅ Referenz-Sweep durch (2026-07-16); dormante Stacks weiter bewusst nicht deployt
|
||||
### TP14.3 · Gitea #54 — Session C: Dormante Stacks vlan20-tauglich + Referenz-Sweep — dormante Stacks weiter bewusst nicht deployt
|
||||
- ✅ **Dormante Stacks bereitgestellt (2026-07-11):** `transform.py` auf core(43)/openclaw(92)/openclaw2(97)/oc20260307(119)/openspeedtest(74)/speedtest_tracker(80)/technikscheiss(42) → **`.new.yml` + `.orig.yml`** in `docs/nas-vlan20/stacks/` (gitignored). **Nicht deployt** (User: „im Bedarfsfall später", Container-Zukunft ungewiss). 42/43 waren `default_internal` → jetzt vlan20-only (DHCP-.20.x). ⚠️ **Deploy-Caveats:** externe Refs bewusst NICHT umgeschrieben (`DB_HOST=.41.122` [pre-existing tote openclaw-DB, in 80+119], `OLLAMA_BASE_URL=.41.129`, `REDIS_URL=.41.111` in 119); **Kollision** `43-core` will `.20.23` = gitea-DB-IP (nicht gleichzeitig deploybar).
|
||||
- ✅ **Referenz-Sweep `.41→.20` (2026-07-16, im Rahmen der TP19-Backup-Vorbereitung live gefunden + gefixt):** ap1 `/ip dns static` hatte noch `nas.bf.lan→.41.10` und `docker.bf.lan→.41.126` **stale** — korrigiert auf `.20.10`/`.20.126` (MikroMCP `manage_dns_entry`, dry-run+apply). Zusätzlich per User-Entscheid **Omada-Controller + omada-mcp komplett gelöscht** (overkill für 1 Switch auf der Abschussliste) → DNS-Einträge `omada.bf.lan`/`omada-mcp.bf.lan` entfernt statt migriert. `fritzbox.bf.lan` (totes Gerät, physisch weg seit 2026-07-01) ebenfalls entfernt. `docs/ip-plan.md` + CLAUDE.md nachgezogen.
|
||||
- ✅ **ERLEDIGT/GEGENSTANDSLOS (live geprüft 2026-08-16):** ~~`~/.claude.json` (omada-mcp-Eintrag verwaist), CF-Ingress-Rest (`omada`/`omada-mcp`-Hostnames, ungeprüft)~~
|
||||
@@ -760,7 +841,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp14-5"></a>
|
||||
|
||||
### TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ ✅ ERFÜLLT (2026-08-08) — kein eigener Change
|
||||
### TP14.5 · Gitea #55 — Session E: Freigabe-Gate → TP5-Firewall — kein eigener Change
|
||||
- Gate „TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20)" ist erfüllt → **TP5 (Deny-by-default) ist entblockt.** Reines Reihenfolge-Gate, kein eigener Change.
|
||||
|
||||
<a id="tp15"></a>
|
||||
@@ -771,7 +852,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
> auf tote Adressen — `backup-ap1.py` allerdings auf eine **lebende falsche** (`10.40.10.1` ist die
|
||||
> VLAN10-SVI des Core, nicht ap1) und hätte still das falsche Gerät gesichert.
|
||||
>
|
||||
### TP15 — HW-Refresh: Switching + Router ▸ ✅ **ABGESCHLOSSEN (2026-08-08)** — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) **und** Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc`
|
||||
### TP15 · Gitea #56 — HW-Refresh: Switching + Router — Bring-up beider CRS326 + Branch-Tausch (2026-07-23) **und** Core-Tausch/L3-Umzug (2026-08-08, Durchführung s. TP21.1) sind durch — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc`
|
||||
> 🔌 **Scope-Änderung 2026-08-16 (User-Entscheid): die DC-USV ist aus diesem TP herausgelöst und heißt jetzt **TP24**.** Sie war der letzte offene Rest hier, hat mit Switching/Routing sachlich nichts zu tun und trägt eigene Entscheidungen (Elektrik, Batterie, Standort). TP15 ist damit inhaltlich zu.
|
||||
> *Vorherige Header-Fassung (Stand 2026-08-05, überholt): „🟢 CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23); Core-Tausch/L3-Umzug **vorbereitet, Cutover-Config materialisiert (2026-08-05)**, Cutover = nächstes Wartungsfenster."*
|
||||
- ✅ **BRING-UP BEIDER CRS326 + BRANCH-TAUSCH gegen sw2 — DURCH (2026-07-23, Claude via REST unter Governance-Ausnahme #2).** Ablauf/As-built:
|
||||
@@ -825,7 +906,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp16-1"></a>
|
||||
|
||||
### TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` ▸ ✅ **ABGESCHLOSSEN (2026-08-17)** — `docs/tp16.1-warp-mesh-native-crs326.md`
|
||||
### TP16.1 · Gitea #57 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` — `docs/tp16.1-warp-mesh-native-crs326.md`
|
||||
- ✅ **Kernergebnis: ICMP ist repariert.** Privater Zugang unterwegs läuft über **natives WireGuard auf `crs326-core`** statt cloudflared-QUIC — **0 % Ping-Loss auf alle BF-VLANs, vorher 100 %**. Genau das Symptom aus dem Incident 2026-07-16, das dieses TP ausgelöst hat. Öffentliche FQDNs unberührt auf dem 3-Tunnel-HA (TP16). Compliance **68/68**.
|
||||
- ✅ **§4.0 erledigt 2026-08-17: Mesh-Node angelegt.** `bf-core-mesh` = **`d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env` (`CF_MESH_REGISTER_TOKEN`, gitignored). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.**
|
||||
- 🔑 **Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe).** Der alte User-Token hatte **zwei** fehlende Permissions (§4.0 *und* §4.1 — `devices/settings` war schon lesend `403`). Ersetzt durch einen **account-owned** Token (`9984078812bc…`, ⏰ **gültig bis 2027-01-09**). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an `/accounts/{id}/tokens/verify` — am alten `/user/tokens/verify` melden sie `1000 "Invalid API Token"`, **während alle echten Endpoints 200 liefern**; (2) Dashboard-Fehler `"Account tag in access policy must match tag in request uri"` = eine **Zone**-Permission (`DNS`) steckt in der Policy-Zeile mit **Account**-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in `docs/tp16.1-…md` §4.0. **Merke: Dashboard sagt „Edit", API sagt „Write".**
|
||||
@@ -865,7 +946,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp17"></a>
|
||||
|
||||
### TP17 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` ▸ ✅ **KOMPLETT (User-Bestätigt 2026-08-13): alle Shellys auf VLAN30** — Geräte-PW *(war TP17.2, seit 2026-08-24 **TP30** Posten #3)* — `mcp/shelly/`, Runbook `docs/tp17-shelly-go-runbook.md`
|
||||
### TP17 · Gitea #58 — Shelly-Übersiedelung VLAN1 → VLAN30 (IOT) + `shelly-mcp` — Geräte-PW *(war TP17.2, seit 2026-08-24 **TP30** Posten #3)* — `mcp/shelly/`, Runbook `docs/tp17-shelly-go-runbook.md`
|
||||
- ✅ **Abschluss 2026-08-13 (User-Bestätigt):** auch die zuletzt offenen saisonalen Shellys (s1/s2/s3/s12) sind inzwischen umgeklemmt — **alle Shellys auf `dirtyvader-iot`/VLAN30**, kein VLAN1-Shelly-Rest mehr. Migration-Teil von TP17 damit vollständig abgeschlossen.
|
||||
- ✅ **GO durchgezogen 2026-07-13 (16 Shellys VLAN1→VLAN30, 0 Fehler):** Pilot s26 + Welle A (s4–s9) + Welle B (s10/11/13/15/17/19/20/21/25) → alle auf `dirtyvader-iot`/`10.40.30.<octet>` verifiziert (SSID+IP+rssi). Mit den 6 Vorab-Migrierten (s14/16/18/22/23/24) = **22/26 auf VLAN30**; VLAN1-Sweep `.180-250` = **0 Shellys**. Restliche 4 saisonal ausgesteckte (s1/s2/s3/s12) inzwischen ebenfalls migriert (s.o.).
|
||||
- 🔧 **Anker-Verfahren (User-Idee, statt sta1-Backup):** je Gerät **vor** dem sta-Write die geräteeigene **AP-Funktion** aktiviert (`WiFi.SetConfig ap.enable` — Firmware verlangt WPA2, kein offenes AP → PSK=IOT-PSK, live/kein Reboot) = Notanker, falls VLAN30-Join scheitert (Gerät bleibt über `Shelly*-<mac>`-AP erreichbar, **kein physischer Reset**). Nach Verify alle 22 AP-Anker wieder `deanchor`t. Runner: `mcp/shelly/migrate.py` (`migrate`/`deanchor`/`auth`/`table`, Dry-Run default, stoppt bei erstem Fehler, reused `discover.py`).
|
||||
@@ -912,7 +993,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp19"></a>
|
||||
|
||||
### TP19 — cudy-Teardown + VLAN1-Client-Retirement ▸ ✅ **GESCHLOSSEN (2026-08-18)** — `docs/tp19-vlan1-cudy-teardown-runbook.md`
|
||||
### TP19 · Gitea #59 — cudy-Teardown + VLAN1-Client-Retirement — `docs/tp19-vlan1-cudy-teardown-runbook.md`
|
||||
- ✅ **Abschluss-Sweep 2026-08-18, alles live gegengeprueft (reine Reads):**
|
||||
- **VLAN1 ist leer:** `/ip arp` auf `bridge` = **0 Eintraege, 0 reachable**. Nicht Restverkehr,
|
||||
sondern gar nichts. Der Dual-DHCP-Race aus `project_vlan1_dhcp_incident` hat damit endgueltig
|
||||
@@ -1009,7 +1090,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
- **Teardown-Sequenz (angepasst an den Live-Stand):** VLAN1-Client-Sweep ist **schon fast leer** (nur sw1-Mgmt + pve-Interim reachable) → `defconf`/pool-v1 ist **bereits disabled** → cudy-Abbau **entfällt** (cudy1 umgenutzt statt abgebaut, cudy2 tot) → ✅ **SwitchBot + Twinkly erledigt (2026-07-16)** → **verbleibt:** Media-Check (B1.3), pve-`.41.100` abräumen (B1.5), Interim-Krücken bewerten (ap2 `frame-types=admit-only-vlan-tagged`, `st-main rrm/wnm`), Inventar/`ip-plan`/DNS `.41→Ziel` final.
|
||||
- Abh.: **TP14 ✅** (Hosts raus aus VLAN1), **TP17 ✅** (Shellys raus). Verwandt: `project_vlan1_dhcp_incident` (cudy-Dual-DHCP-Leak), TP5 (Deny-by-default danach sauberer). SSoT hier + `docs/tp19-vlan1-cudy-teardown-runbook.md`. Folge: **TP19.1**.
|
||||
|
||||
#### TP19.1 — VLAN99-MGMT-Cutover (sw1/sw2/ap2-Mgmt VLAN1 → VLAN99) ▸ ✅ ERLEDIGT (2026-08-14 nachgezogen) — alle lebenden Geräte auf VLAN99, die beiden Rest-Punkte gegenstandslos
|
||||
#### TP19.1 · Gitea #60 — VLAN99-MGMT-Cutover (sw1/sw2/ap2-Mgmt VLAN1 → VLAN99) — alle lebenden Geräte auf VLAN99, die beiden Rest-Punkte gegenstandslos
|
||||
- ✅ **Abschluss-Nachtrag 2026-08-14 (Status-Korrektur, kein neuer Change):** der Header stand noch auf „ap2 vertagt, sw1 offen" (Stand 2026-07-16) — beides ist durch spätere TPs eingelöst bzw. gegenstandslos geworden, ohne dass es hier nachgetragen wurde. Ist-Stand: **core `.99.1` · branch `.99.2` · ap1 `.99.11` · ap2 `.99.12`** — jedes lebende Netzgerät hat sein Mgmt auf VLAN99. **sw1/sw2 existieren nicht mehr** (physisch ausgebaut, in Nautobot per TP22 Schritt 5 auf *Decommissioned*). Damit ist TP19.1 vollständig; offen bleibt nur der VLAN1-**Client**-Teil, und der gehört zu TP19 (Hauptteil), nicht hierher.
|
||||
- **Scope:** die einzigen Dinge, die TP19s Hauptteil bewusst in VLAN1 belässt — sw1 (`.41.11`), sw2, ap2-Mgmt (`.41.2`) — auf VLAN 99 (MGMT, `10.40.99.0/24`) umziehen. Erst danach ist VLAN1 wirklich **komplett** weg, nicht nur als Client-Netz.
|
||||
- **User-Entscheid (2026-07-16):** kein eigenständiges TP-Nummer — bewusst als **Unterpunkt von TP19** geführt („VLAN1 killen"), nicht als TP22 o.ä.
|
||||
@@ -1024,7 +1105,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp20"></a>
|
||||
|
||||
### TP20 — Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) ▸ ✅ **Plattform live (2026-07-16)** — Nautobot-DB angelegt, vw-Migration (TP20.1) offen — Memory `project_mikrotik_commit_nautobot_gc`
|
||||
### TP20 · Gitea #61 — Platform-Datenbank (Postgres Precious-Tier + MariaDB Tier-2) — Nautobot-DB angelegt, vw-Migration (TP20.1) offen — Memory `project_mikrotik_commit_nautobot_gc`
|
||||
- ✅ **Platform-Postgres live (2026-07-16):** LXC 105 auf pve, `platform-pg.bf.lan` / `10.40.20.20`, Ubuntu 24.04 (native PG16-Paket, kein PGDG-Repo nötig — bewusste Abweichung von der sonstigen Debian/community-scripts-Konvention der übrigen pve-LXCs, aus PG16-Verfügbarkeitsgründen). Cluster mit `--locale en_US.UTF-8 -e UTF8` neu initialisiert (Erstinstallation lief in `C`/`SQL_ASCII`, für Nautobot ungeeignet, korrigiert). `wal_level=replica` gesetzt; **`archive_mode` bewusst noch NICHT an** — ohne pgBackRest/Garage-S3 (TP18) zum Prunen würde archivierte WAL unbegrenzt wachsen und die kleine 8GB-Disk füllen; wird nachgezogen sobald TP18 steht. Rolle+DB `nautobot` angelegt, `pg_hba.conf` erlaubt `scram-sha-256` aus `10.40.20.0/24`. End-to-End-Verbindungstest (Netzwerk, nicht nur localhost) erfolgreich. Creds in `postgres/.secrets` (gitignored, `chmod 600`).
|
||||
- **Auslöser:** Mehrere Dienste brauchen zentrale DB (Nautobot-SoT [TP21], Vaultwarden, HA-Recorder). Grundsatzentscheid statt Nautobot-Insellösung — „wir skalieren schneller als wir schauen können" (User).
|
||||
- ⚠️ **Abh.-Korrektur (User 2026-07-16): TP18 (Garage-S3) ist KEINE Vorbedingung mehr — „hat mit dem Netz nichts zu tun", transitiv aus der TP21-Kette entfernt.** pgBackRest→Garage-S3 (s.u.) wird **nachgezogen sobald Garage existiert**, blockiert aber nicht mehr den Start. `wal_level=replica`/`archive_mode` können mit einem Platzhalter/lokalen Archive-Ziel hochgezogen werden, produktives Off-Site-PITR kommt später.
|
||||
@@ -1048,7 +1129,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp21"></a>
|
||||
|
||||
### TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ 🟢 **Plattform + Datenmodell + Templates live**
|
||||
### TP21 · Gitea #62 — Nautobot Golden Config (config-as-code Control-Plane)
|
||||
- 🔴 **BETRIEBSBEFUND 2026-08-18: der Stack hat sich zweimal in einer Nacht selbst umgebracht — Ursache
|
||||
war der Image-Healthcheck, nicht Celery.** `nautobot-worker` und `-scheduler` erben aus dem Image
|
||||
einen `HEALTHCHECK`, der alle **10 s** `nautobot-server health_check` startet — einen kompletten
|
||||
@@ -1112,7 +1193,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp21-1"></a>
|
||||
|
||||
### TP21.1 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) ▸ ✅ NETZ WIEDER LIVE (2026-08-08); ~~Aufräum-Todos offen~~ → **alle fünf erledigt, letzter live gegengeprüft 2026-08-23** (s. Nachtrag am Ende)
|
||||
### TP21.1 · Gitea #63 — Core-Cutover-Recovery (ap1 → crs326-core als L3-Router) — ~~Aufräum-Todos offen~~ → **alle fünf erledigt, letzter live gegengeprüft 2026-08-23** (s. Nachtrag am Ende)
|
||||
> **Kern-Erkenntnis (wichtig gegen Doku-Verwirrung):** **`crs326-core` IST jetzt der produktive L3-Router.** Der Core-Cutover (TP15) wurde faktisch vollzogen — ap1 wurde plattgemacht/getrennt, core hat Routing/DHCP/DNS/NAT/WG-Hub übernommen. core-Mgmt = **`10.40.99.1`** (vlan99-SVI) bzw. same-subnet-Fallback **`10.40.10.1`** (RouterOS-Version 7.19.5 identifiziert das Gerät zweifelsfrei als CRS326, nicht als hAP/ap1). Die CLAUDE.md-Zeile „core grundkonfiguriert, NICHT produktiv" ist damit **überholt**.
|
||||
- **Verlauf:** Ausgangssymptom war ein L3-Ausfall-Eindruck (kein DHCP, „kein Routing nach .20.x"). Ursache = **unvollständiger Cutover**: core hatte L3-SVIs + Firewall, aber es fehlten DHCP-Server, WAN-Egress, WG-Hub und der DNS-Resolver-Upstream. ap1 (bisher alleiniges L3-Gerät) war bereits Werksreset + getrennt.
|
||||
- ✅ **DHCP wiederhergestellt** aus letztem ap1-Backup (`routeros/ap1-pre-core-cutover-20260806-073616.rsc`): 7 Server (`dhcp-v10…v99`) + 8 Networks + **54 statische Reservierungen** + Option `dns 1.1.1.1`. Reproduzierbar in `routeros/tp21-core-dhcp-from-ap1.rsc`. Legacy VLAN1 (pool-v1/defconf) + tote omada-inform-Option bewusst weggelassen.
|
||||
@@ -1153,7 +1234,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp22"></a>
|
||||
|
||||
### TP22 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) ▸ ✅ **DURCH (2026-08-13)**, Compliance-Backlog **abgearbeitet 2026-08-16 → 68/68, 0 Drifts** — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08
|
||||
### TP22 · Gitea #64 — Device-Adoption + Golden-Config Compliance-Push (RouterOS-Fleet) — Compliance-Backlog **abgearbeitet 2026-08-16 → 68/68, 0 Drifts** — alle 5 Schritte erledigt (Schritt 4 per Entscheid „keine Secret-Injection"); aus TP21 ausgelagert 2026-08-08
|
||||
- **Ziel:** die 4 Live-Geräte (crs326-core, crs326-branch, ap1, ap2) real unter **Nautobot Golden Config** nehmen — Backup → Compliance/Drift → intended → **Push (`enable_deploy`)**. Damit greift der Governance-Entscheid „Config exklusiv über Nautobot" operativ. Macht die aktuellen `routeros/.secrets/*.rsc`-Handoffs mittelfristig überflüssig.
|
||||
- **Stand aus TP21:** Plattform + Datenmodell + Git-Template + intended-config für core stehen; ap1/ap2 als reine APs zurückgeschrieben (`nautobot/ingest-ap-rebuild.py`, 2026-08-08). `enable_plan`/`enable_deploy` in `nautobot_config.py` noch **bewusst aus** (pull/read-only).
|
||||
- ✅ **Schritt 0 DURCH (2026-08-13) — Backup-Scrubbing + Repo-Neuanlage.** Vorgezogen aus Schritt 4, weil ohne Scrubbing jedes weitere Backup Secrets geleakt hätte.
|
||||
@@ -1234,7 +1315,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp26"></a>
|
||||
|
||||
### TP26 — `/ip firewall filter` unter Drift-Kontrolle ▸ ✅ **DURCH (2026-08-17)**
|
||||
### TP26 · Gitea #65 — `/ip firewall filter` unter Drift-Kontrolle
|
||||
> **Nachgetragen 2026-08-18.** TP26 existierte bis dahin nur als zwei Commits und eine `.rsc` — ohne
|
||||
> Abschnitt in dieser Datei. Genau die Lücke, die eine frische Session nicht finden kann.
|
||||
|
||||
@@ -1255,7 +1336,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp27"></a>
|
||||
|
||||
### TP27 — RouterBOOT-Firmware auf 7.24 nachziehen ▸ ✅ **DURCH (2026-08-17/18)**
|
||||
### TP27 · Gitea #66 — RouterBOOT-Firmware auf 7.24 nachziehen
|
||||
> **Nachgetragen 2026-08-18.** Auch TP27 hatte keinen Abschnitt — die Arbeit steckt in Christophs Commit
|
||||
> `b93514a` plus `routeros/tp27-routerboot-firmware-724.rsc` und
|
||||
> `docs/routerboot-firmware-klickanleitung.md`.
|
||||
@@ -1274,17 +1355,228 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
<a id="tp28"></a>
|
||||
|
||||
### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ↩️ **WIEDER GEÖFFNET 2026-08-30**
|
||||
### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core`
|
||||
|
||||
> Der Volltext steht seit dem 2026-08-30 wieder in
|
||||
> [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter
|
||||
> Access-Point dazukommt. **Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder
|
||||
> geöffnet werden** (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur
|
||||
> ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen.
|
||||
> ✅ **Abgeschlossen 2026-10-04.** ap1 Sideboard · ap2 Technik UG · ap3 Technik OG (Platzierung
|
||||
> final). Kanalplan committed, `wifi-cap`-Rest auf dem Core bereinigt, Compliance 29/29. Der
|
||||
> ap3-Nachtrag unten ist damit erledigt; die zwei 🔜-Reste sind Cross-TP (TP25 ✅ 09-15, TP30).
|
||||
|
||||
|
||||
> **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist
|
||||
> unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30,
|
||||
> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP
|
||||
> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder
|
||||
> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".**
|
||||
|
||||
- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
|
||||
Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
|
||||
war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
|
||||
Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
|
||||
**der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
|
||||
`-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
|
||||
**statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
|
||||
- **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
|
||||
`/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
|
||||
`/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
|
||||
`vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
|
||||
Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
|
||||
- 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
|
||||
Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
|
||||
`torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
|
||||
Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
|
||||
— der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
|
||||
- ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
|
||||
(30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
|
||||
dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
|
||||
**frische DHCP-Lease pro SSID**, nicht die Registration-Table.
|
||||
- **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
|
||||
mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
|
||||
zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
|
||||
- ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
|
||||
ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
|
||||
durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
|
||||
sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
|
||||
kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
|
||||
wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
|
||||
`last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
|
||||
- ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
|
||||
antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
|
||||
gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@<ap> "/system reboot"`.
|
||||
- ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
|
||||
`nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
|
||||
→ Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
|
||||
auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
|
||||
allein erreicht Nautobot nicht.
|
||||
- ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
|
||||
Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
|
||||
als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
|
||||
|
||||
- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
|
||||
jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
|
||||
(`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
|
||||
- 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
|
||||
je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
|
||||
sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
|
||||
Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
|
||||
defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
|
||||
- ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
|
||||
Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
|
||||
bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
|
||||
referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
|
||||
`configuration=`-Verweis, der beim Loeschen folgenlos blieb.
|
||||
- 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
|
||||
`/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
|
||||
`/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
|
||||
**nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
|
||||
- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
|
||||
(der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
|
||||
welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
|
||||
`discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
|
||||
**Ergebnis: 92/92 compliant, 0 Drifts.**
|
||||
- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<<REDACTED>>`
|
||||
im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
|
||||
Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
|
||||
Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
|
||||
als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
|
||||
drei Bring-up-Dateien, die voneinander abgeschrieben sind.
|
||||
- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
|
||||
wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
|
||||
> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
|
||||
> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
|
||||
> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
|
||||
|
||||
- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
|
||||
Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
|
||||
TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
|
||||
das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
|
||||
Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
|
||||
waren die Funkparameter aus TP25.
|
||||
- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
|
||||
4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
|
||||
TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
|
||||
media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
|
||||
verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
|
||||
sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
|
||||
- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
|
||||
Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
|
||||
deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
|
||||
- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
|
||||
`/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
|
||||
einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
|
||||
vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
|
||||
- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
|
||||
iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
|
||||
drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
|
||||
Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
|
||||
✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
|
||||
`wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
|
||||
sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
|
||||
- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
|
||||
`wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
|
||||
toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
|
||||
Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
|
||||
*(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
|
||||
„KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
|
||||
gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
|
||||
die Punkte darunter mitziehen.**)*
|
||||
- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
|
||||
Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
|
||||
nur `admin logged in/out via api`).
|
||||
- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
|
||||
Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
|
||||
|
||||
|
||||
#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30
|
||||
|
||||
**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**.
|
||||
**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am
|
||||
Gerät wie ap1/ap2 — **keine** DHCP-Reservierung.
|
||||
|
||||
**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die
|
||||
gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die
|
||||
bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten").
|
||||
|
||||
**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:**
|
||||
|
||||
| AP | 5 GHz | Band | DFS | 2,4 GHz | wann |
|
||||
|---|---|---|---|---|---|
|
||||
| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** |
|
||||
| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug |
|
||||
| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug |
|
||||
| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — |
|
||||
|
||||
🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum
|
||||
Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein
|
||||
**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück.
|
||||
|
||||
> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der
|
||||
> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile
|
||||
> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat.
|
||||
> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die
|
||||
> Bridge-Host-Tabelle stellen.** `/interface bridge host print where interface=etherN` nennt die
|
||||
> MAC des Gegenübers — hier `D0:EA:11:FF:16:9D`, exakt die `E01` vom ap3-Label.
|
||||
|
||||
##### Stand 2026-08-30 — ✅ DURCH
|
||||
- ✅ **`core ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`), GC-Template
|
||||
nachgezogen; AP-Zweig rendert Bridge-Ports aus **`ap.extra_ports`** statt hartkodiert.
|
||||
- ✅ **Zugang über `ap3 ether2` → `core ether22`** (Patchkabel). Über `ether1` war ap3
|
||||
grundsätzlich nicht administrierbar, s. Gotchas unten. Loop-Schutz: `ether21` war während
|
||||
Phase 1 abgeschaltet, danach im selben Befehl getauscht (`ether22` aus, `ether21` an).
|
||||
- ✅ **Bring-up in zwei Phasen**: Phase 1 (`ether1` aus der WAN-Liste, `10.40.99.13/24` auf
|
||||
`vlan99`, Flotten-PW) über das Kabel, Phase 2 (Werks-WLAN weg, CAP-Modus,
|
||||
`slaves-datapath`, `vlan-filtering=yes`) über `vlan99`.
|
||||
- ✅ **RouterOS 7.20.8 → 7.24.1 + RouterBOOT 7.24.1** (zwei Reboots, TP27-Kriterium
|
||||
`current-firmware = upgrade-firmware`). 🆕 **Die Angleichung der übrigen vier ist am
|
||||
2026-08-31 erfolgt** (Christoph, WebFig) — die Flotte ist einheitlich, TP27 erledigt.
|
||||
- ✅ **ap1 funkstill** (Provisioning-Regeln entfernt + `remote-cap provision`), gegengeprüft
|
||||
**am CAP**: nur noch `wifi1`/`wifi2` mit `running=false`, keine dynamischen VAPs.
|
||||
- ✅ **ap3 funkt**: `cap-wifi5` = **`5180/ax/Ce/I`**, `cap-wifi6` = **`2437/ax`**, voller
|
||||
VAP-Satz (2 Master + 7 Slaves), alle `running=true`.
|
||||
- ✅ **Datapath bewiesen**: 20 IOT-Clients auf ap3, Ping über VLAN30 mit **0 % Loss**.
|
||||
*(Bewusst nicht über die Registration-Table abgenommen — die zeigte im 14-h-Ausfall
|
||||
25 zufriedene Clients. Bei statisch reservierten Shellys erzeugt ein Roam keine neue
|
||||
DHCP-Lease, deshalb ist hier Datenverkehr das belastbare Signal.)*
|
||||
- ✅ **Nautobot**: DeviceType `cAP ax`, Device `ap3` mit 16 Interfaces, `primary_ip4`,
|
||||
`secrets_group`, ConfigContext (`5g=ch36 2g=ch6 extra_ports=[]`).
|
||||
Nebenkorrektur: ap1 stand auf „Wohnzimmer Sideboard", steht aber in **Technik UG**.
|
||||
- ✅ **Compliance 130/130, 0 Drifts über 5 Geräte.** Die drei Werksreste, die der erste
|
||||
Lauf mit ap3 gemeldet hat (defconf-DNS, 11 defconf-Firewallregeln, fehlende
|
||||
`/ip service`-Härtung), sind mit `routeros/tp28-ap3-werksreste.rsc` abgeräumt.
|
||||
- 🔜 **Offen:** TP25-Messrunde (`bash docs/tp25-runde.sh ap3-bringup`) — braucht einen
|
||||
Rundgang. Leitfrage: deckt ap3 allein ab, was ap1 abgedeckt hat?
|
||||
- 🔜 **Offen:** Passwort-Rotation (TP30) — das gemeinsame Admin-PW ist in dieser Session
|
||||
verbrannt; ap3 trägt es bereits mit, hängt also in der Rotation mit drin.
|
||||
|
||||
##### Was am cAP ax anders ist als am hAP ax² — und was daran wehtut
|
||||
- 🔴 **Nur ZWEI Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5`. Der
|
||||
GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*.
|
||||
Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`).
|
||||
**Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.**
|
||||
- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax
|
||||
bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz
|
||||
„kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu.
|
||||
- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus,
|
||||
wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port
|
||||
(VLAN1 bleibt leer, TP19 ist geschlossen).
|
||||
- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity +
|
||||
Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`,
|
||||
das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24).
|
||||
|
||||
##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen)
|
||||
1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit
|
||||
genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und
|
||||
hat keinen zweiten → 5 GHz dieses APs bleibt unten.
|
||||
2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf
|
||||
`5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt,
|
||||
9 dBm gesendet.
|
||||
3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen,
|
||||
der Port ist untagged-Member von VLAN40 bei `pvid=1`.
|
||||
|
||||
<a id="tp-r"></a>
|
||||
|
||||
### TP-R — Router-Capabilities-Sweep ▸ FERTIG (`docs/research/routeros-capabilities.md`)
|
||||
### TP-R · Gitea #67 — Router-Capabilities-Sweep — `docs/research/routeros-capabilities.md`)
|
||||
- **Leitsatz: nativ vor Container, NAS vor Router** (128MB-Flash-Limit).
|
||||
- **Sofort nativ (~0 RAM):** RoMON · Netwatch · Scheduler + Auto-Backup/Mail · SNMP→externes Monitoring · **mDNS-Repeater** · spectral-scan · **802.11r/k/v (Fast-Roaming statt Mesh)** *(die Sweep-Fassung nannte hier CAPsMAN — das ist seit 2026-08-08 abgeschafft; 11r/k/v läuft standalone weiter, s. TP3/TP3.3)* · Back-to-Home/WireGuard.
|
||||
- **Auf NAS:** FreeRADIUS(+LDAP) · NPM · icinga/Grafana · CF-Tunnel.
|
||||
@@ -1305,7 +1597,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
> Handoff-Punkte aufgelöst, Gitea #17 closed. Die 394-Zeilen-Herleitung (Messchronik A–G) steht
|
||||
> weiter unten unter [TP25 — Langfassung](#tp25-lang).
|
||||
|
||||
### TP25 — WLAN-Stabilisierung ▸ ✅ **FERTIG: Platzierung final (ap1 Sideboard · ap2 UG · ap3 OG), alle Handoff-Punkte aufgelöst (2026-09-15, [Steering-Handoff](handoff-20260915-tp25-steering-threshold-und-terrasse.md))** — ap2-UNTERPUNKT ✅ WIDERLEGT 2026-09-14 (In-situ, [Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)): ap2 sendet auf 2 m mit −49…−51 dBm, stabil, 0 Roams — Hardware sauber, kein Tausch begründet. Terrasse = 5-GHz-Zellkante (−76…−81 vs. 2,4 GHz −65…−73), Thrash nur in der Band-Überlappungszone −70…−77. Hebel „Access-List-Band-Boden" in der Machbarkeitsprüfung GESCHEITERT (kein `band`-Property, cap-wifiN-Rejoin-Falle) — s. Block unten** — **FUND E ✅ GESCHLOSSEN 2026-09-02: die Abschlussmessung ist ruhig (4 reauth in 8,6 h, 0 Ausfälle, Verlauf 218→40→4) ⇒ `7.24.1` (TP27) war die Ursache, nicht `ft-over-ds`. Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen, kein Geräte-Write** ([Handoff](handoff-20260902-tp25-fund-e-messung.md)) · Kern erledigt: 🟢 **(2026-08-22) Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). **Fund F ✅ aufgelöst (08-24, geräteseitig — kein Netzfehler)**, **5-GHz-Umfeldscan ✅ (08-24)**, Kanalplan für AP 3 ✅ gerechnet. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
### TP25 · Gitea #17 — WLAN-Stabilisierung — ap2-UNTERPUNKT ✅ WIDERLEGT 2026-09-14 (In-situ, [Handoff](handoff-20260914-tp25-ap2-insitu-terrassse.md)): ap2 sendet auf 2 m mit −49…−51 dBm, stabil, 0 Roams — Hardware sauber, kein Tausch begründet. Terrasse = 5-GHz-Zellkante (−76…−81 vs. 2,4 GHz −65…−73), Thrash nur in der Band-Überlappungszone −70…−77. Hebel „Access-List-Band-Boden" in der Machbarkeitsprüfung GESCHEITERT (kein `band`-Property, cap-wifiN-Rejoin-Falle) — s. Block unten** — **FUND E ✅ GESCHLOSSEN 2026-09-02: die Abschlussmessung ist ruhig (4 reauth in 8,6 h, 0 Ausfälle, Verlauf 218→40→4) ⇒ `7.24.1` (TP27) war die Ursache, nicht `ft-over-ds`. Hebel 1 widerlegt+zurückgebaut, Hebel 2–4 entfallen, kein Geräte-Write** ([Handoff](handoff-20260902-tp25-fund-e-messung.md)) · Kern erledigt: 🟢 **(2026-08-22) Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). **Fund F ✅ aufgelöst (08-24, geräteseitig — kein Netzfehler)**, **5-GHz-Umfeldscan ✅ (08-24)**, Kanalplan für AP 3 ✅ gerechnet. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
> 📚 **Kondensiert am 2026-08-23 (Doku-Session, Punkt F).** Dieser Abschnitt war mit 394 Zeilen
|
||||
> 18 % der Datei. Die **Langfassung steht vollständig und unverändert** im Archiv:
|
||||
> [`teilprojekte-archiv.md` → TP25 (Langfassung)](teilprojekte-archiv.md#tp25-lang) — Messchronik (A)–(G),
|
||||
@@ -1827,7 +2119,7 @@ Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-ver
|
||||
> Erklärungen — zweimal stand hier eine plausible Ursache, die die nächste Messung gekippt hat
|
||||
> (ETSI-Band C bei Fund B; „ap1 ans Sideboard" bei (F)/(G)). Wer TP25 fortsetzt, muss sehen können,
|
||||
> welche Wege schon abgelaufen sind.
|
||||
### TP25 — WLAN-Stabilisierung ▸ 🟢 **KERN ERLEDIGT (2026-08-22): Dead Spot Terrasse repariert** (43 % → 9 % Verlust, 20,4 s → 1,0 s). Offen nur noch **Fund F** (5:05-Takt IoT) und die Entscheidung **5 GHz auf 20 MHz**. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
### TP25 · Gitea #17 — WLAN-Stabilisierung — 43 % → 9 % Verlust, 20,4 s → 1,0 s). Offen nur noch **Fund F** (5:05-Takt IoT) und die Entscheidung **5 GHz auf 20 MHz**. Nicht mehr kritischer Pfad — Sammel-TP für Christophs Beobachtungen 1–6
|
||||
> 🆕 **Messung 2026-08-20/21: [`tp25-wlan-roaming-messung-20260820.md`](tp25-wlan-roaming-messung-20260820.md)**
|
||||
> (Nebenprodukt der TP6-Messreihen, Core-Sampler im 2-s-Takt über 2,5 h, teils mit laufendem Videocall).
|
||||
> Drei Befunde:
|
||||
@@ -2223,7 +2515,7 @@ Handoff: [`handoff-20260901-tp25-ap2-verdacht.md`](handoff-20260901-tp25-ap2-ver
|
||||
|
||||
<a id="tp39"></a>
|
||||
|
||||
### TP39 — LXC-Hygiene auf pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup ▸ ✅ **ABGESCHLOSSEN (2026-09-01)**
|
||||
### TP39 · Gitea #68 — LXC-Hygiene auf pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup
|
||||
|
||||
**Auslöser:** ein 10-Sekunden-Hänger auf `gitea`, gefunden am Rand des TP16.3-§5-Flips. Er hatte mit
|
||||
dem Flip nichts zu tun, war aber nur deshalb sichtbar, weil zum ersten Mal jemand systematisch
|
||||
@@ -2302,7 +2594,7 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein.
|
||||
|
||||
---
|
||||
|
||||
### TP34 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) ▸ ❌ **VERWORFEN (2026-09-08)**
|
||||
### TP34 · Gitea #24 — Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle)
|
||||
> ➡️ **Fortsetzung 2026-09-22: `TP34.2 — Interne DNS-Namen als Daten`** (Gitea #73, Prosa in
|
||||
> `docs/teilprojekte.md` → `### TP34.2`). Dort lebt der *Daten → gerenderte Config*-Gedanke weiter;
|
||||
> **verworfen bleibt nur das Caddyfile-Rendering** (dieser Abschnitt hier).
|
||||
@@ -2324,7 +2616,7 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein.
|
||||
---
|
||||
|
||||
<a id="tp44"></a>
|
||||
### TP44 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot ▸ ✅ **DURCH (2026-09-10): Etappe 1 + Etappe 2 (Teil A IPAM-Prefixe, Teil B Live-Sync als täglicher Nautobot-Job)**
|
||||
### TP44 · Gitea #33 — Cloudflare-Estate als Dokumentations-SSoT in Nautobot
|
||||
> **Entscheid (User, 2026-09-02):** die volle CF-Estate (Tunnel + WARP-Connector + VPC-Service +
|
||||
> Hyperdrive + Workers + alle Access-Apps) als strukturierte, abfragbare SSoT in Nautobot abbilden —
|
||||
> **etappenweise: manuell jetzt, Sync später.** Nautobot kann CF nicht steuern → „SSoT" heißt
|
||||
@@ -2368,7 +2660,7 @@ gemessen hat. Beim Beheben kam eine zweite, unabhängige Altlast zum Vorschein.
|
||||
- **Beziehung zu TP37:** TP44 hat **TP37 Punkt 3** (CF-Connectoren) vorweggenommen — dieselbe native-Modell-
|
||||
Linie, die TP37 P1+2 (Substrat/Dienste) fortsetzt.
|
||||
|
||||
### TP41 — CF-Access-Reparatur + Zero-Trust-Konsolidierung ▸ ✅ **ABGESCHLOSSEN (2026-09-10)** — Auth-Kern seit 2026-09-01 komplett; **Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet** (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7.
|
||||
### TP41 · Gitea #30 — CF-Access-Reparatur + Zero-Trust-Konsolidierung — Auth-Kern seit 2026-09-01 komplett; **Gelis WARP-Client am 2026-09-10 via Android erfolgreich eingerichtet** (letzter Blocker weg, User-Bestätigung). Optionale Restpunkte (T6 Rate-Limit, geli-CGNAT-Policy-Cleanup) bewusst nicht gezogen; Keystore-Rotation läuft unter TP30 #7.
|
||||
|
||||
**Auslöser:** CF-Access zwang Christoph **und Geli** täglich zum OTP-Login (soll: 30 Tage); GitHub/Google
|
||||
„funktionieren nicht". Root-Causes: Team-Domain-Rename `bf4`→`emsenhuber` (Provider-Callback veraltet),
|
||||
@@ -2426,14 +2718,14 @@ der Egress ohnehin Cloudflare (nicht diese IP) → der IP-Include wäre dann üb
|
||||
**Gitea-Least-Privilege** = eigener künftiger TP (jedes Projekt repo-scoped Login, Admin nur Claude/Christoph).
|
||||
- Bezug **TP11** (Monitoring): eine Plattenfüllstand-Warnung hätte das Wochen früher gezeigt.
|
||||
|
||||
### TP43 — dw-Standort-DNS-Uplink (site-spezifische Overrides) ▸ ♻️ **REOPENED 2026-09-11 → Volltext zurück in [`teilprojekte.md`](teilprojekte.md) (Abschnitt TP43)**
|
||||
### TP43 · Gitea #32 — dw-Standort-DNS-Uplink (site-spezifische Overrides)
|
||||
|
||||
Kurz zwischen 2026-09-11 verworfen und am selben Tag reopened: die 420-Prämisse war gegenstandslos
|
||||
(public CF-Worker, via WARP/TP41 gelöst), aber die **interne** BF-Namensauflösung über WG am dw blieb
|
||||
dabei offen (`portainer.bf.emsenhuber.net` am dw nicht erreichbar). Korrigierter Scope + verworfene
|
||||
420-Historie stehen jetzt aktiv in `teilprojekte.md`. Gitea-Issue #32 = `status:todo` · `thema:firewall-dns`.
|
||||
|
||||
### TP42 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net ▸ ✅ **DURCH (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`, `.bf.lan` raus, `.16` (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an **TP37** gekoppelte Nautobot-Doku-Teil.
|
||||
### TP42 · Gitea #31 — Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net — ein interner Suffix `bf.emsenhuber.net`, `.bf.lan` raus, `.16` (adguard_bf1) abgebaut, Compliance 140/140. Offen nur der an **TP37** gekoppelte Nautobot-Doku-Teil.
|
||||
|
||||
**Auslöser (2026-09-01, TP41-Randbefund):** live gemessen liefern die realen Client-Resolver
|
||||
`10.40.20.36`/`.37` `*.emsenhuber.net → 10.40.20.40` (Caddy) — korrekt. Daneben existiert aber ein
|
||||
@@ -2497,3 +2789,75 @@ Gegen die Geräte gemessen (MikroMCP + dig), nicht aus dem Handoff:
|
||||
**Status:** ✅ **Namensraum vereinheitlicht (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`,
|
||||
`.bf.lan` raus, `.16` abgebaut, Compliance 140/140. Offen nur der TP37-gekoppelte Nautobot-Doku-Teil.
|
||||
Handoff: [`docs/handoff-20260901-tp42-naming.md`](handoff-20260901-tp42-naming.md).
|
||||
|
||||
---
|
||||
<a id="tp47"></a>
|
||||
|
||||
### TP47 · Gitea #74 — Core egress via cf-mesh und WARP-Exit-Validierung
|
||||
> **Christoph:** Validierung des Core-Egress-Weges via Cloudflare Mesh. Ausgangsprüfung gegen neutralen Host
|
||||
> und Ziel `164.90.191.7` (DigitalOcean). Korrektur früherer Behauptung: **WARP war am Mac gar nicht
|
||||
> verbunden** — `warp=off`, `ip=90.146.33.177` (Heim-IP). Die zuvor gemessene `104.28.207.39` stammte aus
|
||||
> einem Test mit nur der `1.1.1.1/32`-Regel, daher über die Heim-IP (403). Beweis: Regel aktiviert
|
||||
> `164.90.191.7/32 → egress-cannaleo` → Ziel erreicht mit `200` (168 KiB). Template-Änderung in
|
||||
> `nautobot/mikrotik_routeros.j2` Abschnitt `[10b]` (commit `c600c62e`). Doku-Korrektur
|
||||
> `docs/warp-egress-native-crs326.md` §6.3.
|
||||
>
|
||||
> **Bestandsaufnahme (2026-09-27):**
|
||||
> - Ziel `164.90.191.7` (DigitalOcean-Origin, kein Cloudflare im Pfad) bereitet `403` bei Heim-IP
|
||||
> (`90.146.33.177`, WARP aus), `200` über Core-Routing (`cf-mesh`, `egress-cannaleo`-Table).
|
||||
> - `cf-mesh` erreicht den Origin mit `200` (verifiziert: `cdn-cgi/trace` → `ip=104.28.207.39`,
|
||||
> `warp=on`, `colo=VIE`). Keine Dedicated Egress IP nötig.
|
||||
> - User-Cave: kein Zugriffsmuster, das Heim-IP und diesen Ausgang gemeinsam gegen den Origin zeigt.
|
||||
> - Template-Drift: 0 (Secret-Guard bestanden, Push `c600c62e`).
|
||||
>
|
||||
> #### Durchführung (in einem Durchgang)
|
||||
> 1. TP anlegen, Prosa in `teilprojekte.md` schreiben, commit + push
|
||||
> 2. Core-Routing-Objekte (`egress-cannaleo` Table, Route, Rule) bereits live auf `crs326-core`
|
||||
> 3. Doku `docs/warp-egress-native-crs326.md` §6.3 korrigiert (früherer 403 = Messfehler)
|
||||
> 4. Mineral: nicht abzudrehen (seit 21.09. still)
|
||||
> 5. `10.101.0.254` taugt nicht als GW (100 % Loss; FM = Liwest; Cave erfüllt)
|
||||
|
||||
#### Bestandsaufnahme (live 2026-09-21) — es gibt eine Doku-SSoT, aber keinen Master
|
||||
| Was | Wo | Stand |
|
||||
|---|---|---|
|
||||
| Tunnel, WARP-Connector, Workers, Access-Apps, Hyperdrive, VPC, teamnet-Routen | Nautobot `nautobot.cloud`, Job **`CF Estate Sync`** (`jobs/cf_estate_sync.py`) | ✅ 4 `CloudNetwork` · 16 `CloudService` live |
|
||||
| **Ingress-Origin-Ziele** | **nirgends** | 🔴 `_ingress()` speichert nur `ingress_hostnames`, **nicht** das Origin |
|
||||
| **DNS-Records** | **nirgends** | 🔴 nicht erfasst — live 24 Records / 8 proxied |
|
||||
| **Access-Policies** | **nirgends** | 🔴 nur die Apps selbst |
|
||||
| Richtung | CF → Nautobot | 🔴 **Pull-only** („CF bleibt operativer Master") |
|
||||
| `docs/cf-tunnel-mapping.md` | Repo | §1–16 eingefrorene As-Built-Historie · §0 = Live-Zeiger (altert: nennt `bf-public` „2 conns", live **12** = 3 AP-Connectoren × 4) |
|
||||
|
||||
**Antwort auf „full setup/recovery from scratch": heute nicht möglich.** Es fehlt (a) der Push-Weg,
|
||||
(b) Ingress-Origins, (c) DNS, (d) Access-Policies. Und ein frisch erzeugter Tunnel liefert **neue ID
|
||||
und neues Secret** → das **Token-Handoff an die Connectoren** (ap1/ap2/ap3) ist **Teil der Recovery**,
|
||||
kein Nebenschritt.
|
||||
|
||||
#### Umfang
|
||||
Tunnel + Ingress **inkl. Origin-Ziele** + DNS-Records + Access-Apps/Policies vollständig nach Nautobot;
|
||||
**Push-Renderer** (Dry-Run + Diff); **Recovery-Runbook** für den Aufbau von null.
|
||||
|
||||
🔴 **Vor dem ersten Schritt prüfen, nicht annehmen:** welches Nautobot-Objekt DNS-Records tragen kann
|
||||
(Kern-`ipam` vs. eigenes Modell), und ob das Ingress-Origin als `extra_config`-Feld reicht oder ein
|
||||
eigenes Modell braucht.
|
||||
|
||||
- **Abh.:** TP44 Etappe 1/2 ✅ (native `nautobot.cloud`-Modelle + Pull-Job stehen). **Abgrenzung:**
|
||||
Tunnel-**GC/Namensgebung** läuft in TP45 Schicht D; **CF-Writes** aus Nautobot sind hier *das Ziel*,
|
||||
in TP37 ausdrücklich ausgeschlossen — die Grenze verschiebt sich also bewusst mit diesem TP.
|
||||
|
||||
#### ✅ ABGESCHLOSSEN (2026-10-05) — Egress-Weg verifiziert
|
||||
|
||||
**Nachmessung vom Core aus** (`crs326-core`, via MikroMCP; nicht vom Client — der User war zum
|
||||
Messzeitpunkt extern via WARP verbunden, Client-Messungen sind für diesen Pfad wertlos):
|
||||
|
||||
| Regel-Zustand | Stichprobe | Ergebnis |
|
||||
|---|---|---|
|
||||
| **AN** (`egress-cannaleo` → `cf-mesh`) | 15 | **15/15 → 200** (168,1 KiB) |
|
||||
| **AUS** (Fallback Heim-IP) | 15 | **14/15 → 403**, 1 Ausreißer |
|
||||
|
||||
Der Weg ist **deterministisch**, nicht intermittierend (frühere Formulierung war falsch und ist
|
||||
zurückgenommen). Der Ausreißer sitzt reproduzierbar am **ersten** Sample nach dem Umschalten —
|
||||
Connection-/Cache-Nachlauf der Vorverbindung, keine Pfad-Instabilität. 🔴 **Merkregel für künftige
|
||||
A/B-Messungen: nie ein Einzelsample werten, und die erste Anfrage nach einem Routing-Umschalten
|
||||
verwerfen.** Live-Objekte am Core: Routing-Tabelle `egress-cannaleo`, Route `0.0.0.0/0 → cf-mesh`
|
||||
(active, distance=1), Regel `164.90.191.7/32 → lookup-only-in-table` (`disabled:false`) — alle drei
|
||||
deckungsgleich mit Template `[10b]` in `nautobot/mikrotik_routeros.j2` (Drift 0).
|
||||
|
||||
@@ -69,28 +69,29 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
>
|
||||
> 📊 **Index/Statustabelle → [Gitea-Board](https://gitea.emsenhuber.net/admin/bf-infra/issues)** (siehe Kopf dieser Datei). Hier unten folgt nur noch die per-TP-Prosa.
|
||||
|
||||
### TP5.1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall) ▸ vorgemerkt
|
||||
### TP5.1 · Gitea #1 — DNS-Visibility & Flow-Observability für Low-Trust-Geräte (Airfryer-Fall)
|
||||
- **Kontext (2026-07-08/09):** Airfryer „airschreier" (Philips/Versuni, ESP, `10.40.30.180`) bekam per DHCP-Option 6 `dns 1.1.1.1` als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — **kein** Content-Block, via Gateway-Logs bewiesen, s. `memory/ref_esp_ota_doh_not_block`). Nebenwirkung: public **1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH** (`px35ynlol3.cloudflare-gateway.com`) → **DNS-Sicht auf das Gerät weg**, Gerät umgeht zudem die Filterung.
|
||||
- **(a) DNS-Redirect statt Geräte-Override** (der dauerhafte Fix): dst-nat „UDP/53 (+TCP/53) von Low-Trust-Src → auf einen *plain* Resolver umbiegen" gibt **Logging zurück UND** funktionierendes OTA (kein langsamer DoH-Pfad fürs Gerät). Redirect-Ziel-Optionen: (i) ap1-Resolver mit *nicht*-DoH-Upstream für dieses Src-Set, (ii) direkt public 1.1.1.1 (dann nur Flow-Logging), (iii) eigener plain-Resolver. Sauberer als „Gerät verschwindet auf 1.1.1.1". Verortung: Teil des UNTRUSTED-Internet-only-Pinnings (TP5-„Offen"); Entscheidung v50-DNS-Policy (gefiltert vs. plain) hängt dran.
|
||||
- **(b) Live-Torch als Ad-hoc-Observability:** DNS ist bei direktem 1.1.1.1 blind, **Flows nicht** (NATen über ap1). `torch` auf `vlan30`/`vlan50` gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS `eu-west-1` IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln.
|
||||
- **Abh.:** UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker.
|
||||
- **Abh.:** ~~UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50)~~ + TP5-Deny-by-default. Kein Blocker.
|
||||
- ⚠️ **Nachtrag 2026-10-12 — Gerät ist FUSION/Cloud-only, UNTRUSTED-Move zurückgestellt:** Der Airfryer hat die Philips-FUSION-Migration bereits vollzogen (HA-Config-Entry `philips_homeid`, Titel „Airschreier 5000 Series XXL **[Cloud]**"; NCP `1.1.14` / Host `0.5.6`). Damit ist die **Cloud-Verbindung die einzige Steuerungsroute** — ein Move ins internetlose UNTRUSTED-VLAN würde das Gerät unsteuerbar machen (auch aus HA). Der oben genannte UNTRUSTED-Kandidat ist damit **vom Tisch**, nicht nur verschoben. Der DHCP-Override `dns 1.1.1.1` verliert **für den Airfryer** seine Rechtfertigung (OTA-Problem erledigt) — der **Mechanismus selbst bleibt** aber als Werkzeug für künftige Low-Trust-Kandidaten definiert und wird **nicht** abgebaut (Entscheidung 2026-10-12), ebenso die DNS-Redirect-Variante (a). Die Cloud-Integration **bleibt** bewusst in Betrieb. Details: `docs/announcements.md`, Eintrag 2026-10-12. **Nebenbei:** die VLAN-Nummer im obigen Text („VLAN50") war falsch — UNTRUSTED ist laut `docs/vlan-plan.md` **VLAN 40**.
|
||||
|
||||
### TP8 — Identity & Access-Control ▸ research-gated
|
||||
### TP8 · Gitea #2 — Identity & Access-Control
|
||||
- **RADIUS = FreeRADIUS auf der NAS** (✏️ User-Manager v5 kann **KEIN LDAP** → openldap nur via FreeRADIUS anbindbar).
|
||||
- **EAP-TLS (Zert) je Gerät + dynamische VLAN-Zuweisung** (RADIUS-Attribut `Tunnel-Private-Group-ID`). **Einfachste Heim-Alternative:** pro VLAN eine SSID mit **WPA3-PSK** (kein RADIUS) — Abwägung Komfort vs. Admin-Aufwand.
|
||||
- Abh.: TP3. (Research erledigt, s. TP-R.)
|
||||
|
||||
### TP11 — Monitoring & Mgmt-Control-Plane ▸ research-gated
|
||||
### TP11 · Gitea #3 — Monitoring & Mgmt-Control-Plane
|
||||
- MikroMCP + omada-mcp + **SNMP/icinga-mikrotik**, RoMON, NTP, **E-Mail-Alerts**, Netwatch. → Teil von `docs/research/routeros-capabilities.md`.
|
||||
- **CF-Tunnel #1033 Track A ✅ live (2026-06-30):** Round-Robin-1033er über 3 asymmetrische Connectors behoben
|
||||
durch Reduktion auf pve-only Connector (NAS + macmini scheiterten an pve-lokalen/stalen Origins). SSoT:
|
||||
`docs/cf-tunnel-mapping.md`. **Track B/C läuft über TP14** (Ingress bereits v116 .41→.20 für NAS;
|
||||
pve-Connector→VLAN20 via TP14 Session B = #1033-Zielbild).
|
||||
|
||||
### TP12 — IPv6 (intern + extern, 3 Standorte) ▸ research-gated
|
||||
### TP12 · Gitea #4 — IPv6 (intern + extern, 3 Standorte)
|
||||
- Konzept für FM/BF/DW. → TP-R.
|
||||
|
||||
#### TP12.1 — IPv6 im LAN (ULA) + v6-Firewall ▸ 🟠 **KONKRET GEWORDEN (2026-08-22), Treiber: Matter/Thread**
|
||||
#### TP12.1 · Gitea #5 — IPv6 im LAN (ULA) + v6-Firewall
|
||||
> **Anlass:** der runde IKEA-Fühler **TIMMERFLOTTE** (Matter over Thread) ließ sich nicht anlernen —
|
||||
> „pairing failed" in der Companion-App. Ursache ist **nicht** die Firewall, sondern dass zwischen
|
||||
> VLAN10 und VLAN20 **überhaupt kein IPv6 geroutet wird**. Damit ist TP12 vom Konzept-TP zu einem
|
||||
@@ -168,7 +169,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
**v6-Firewall nachbauen → dann erst Adressen vergeben.** Gehört unter TP12 (Gesamtkonzept 3 Standorte),
|
||||
ist aber allein für BF ausführbar.
|
||||
|
||||
### TP13 — „Botschafts-WLANs" ▸ 🔜 **AUSFÜHRBAR (Runbook 2026-08-19)** — `docs/tp13-botschafts-wlan-runbook.md` · fun/low-prio
|
||||
### TP13 · Gitea #6 — „Botschafts-WLANs" — `docs/tp13-botschafts-wlan-runbook.md` · fun/low-prio
|
||||
> 📌 Am 2026-08-19 überarbeitet: der Technikteil beschrieb noch Standalone-APs (SSIDs entstehen seit
|
||||
> TP28 am **Manager**, also ein Deploy statt zwei), und der Entwurf „nur `ssid=`, kein Security" war ein
|
||||
> **Denkfehler** — eine SSID ohne Security *ist* ein offenes WLAN, also genau der Fall, den der
|
||||
@@ -185,7 +186,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
- ⚠️ **Recht vor Scharfschalten prüfen (User privat, AT):** offenes WLAN + Portal berührt Impressumspflicht/Störerhaftung — **reine Namens-SSIDs ohne offenen Zugang sind unkritisch** (kein Fremd-Datenverkehr); im Zweifel nur die Beacon-Variante.
|
||||
- **Abh.:** ~~TP3-Rebuild ✅ (Standalone-APs — SSID-Änderung = 1 Deploy/GC-Job)~~ → **TP28 ✅** (CAPsMAN auf dem Core) + **TP22 ✅** (GC-Deploy-Pipeline). SSID-Änderung = **ein** Deploy auf den Core; Direkt-Edit am Gerät ist durch den Governance-Entscheid ausgeschlossen.
|
||||
|
||||
### TP14.4 — Session D: CF-Access-Profil für Claude ▸ ✅ **IN TP41 T4 AUFGEGANGEN (2026-09-01)** — `docs/tp14.4-cf-access-token-runbook.md` (historisch)
|
||||
### TP14.4 · Gitea #7 — Session D: CF-Access-Profil für Claude — `docs/tp14.4-cf-access-token-runbook.md` (historisch)
|
||||
> ✅ **Erledigt über TP41 T4 (2026-09-01), nicht als eigenständiger `claude-mcp`-Token:** der
|
||||
> `claude-mcp`-Service-Token war die Basis (`t4a.py`), wurde **→ `zt-svc` umbenannt + rotiert** und über
|
||||
> Gruppe `zt-automation` konsolidiert. `mcp/cloudflare/.env` trägt jetzt **beide** Keys
|
||||
@@ -201,7 +202,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
erneut rotieren. Abnahme = ein Aufruf **mit** den Headern (antwortet) **und** einer **ohne** (403) —
|
||||
der Erfolgsfall allein beweist nichts, der Hostname könnte auf `bypass` stehen.
|
||||
|
||||
### TP16 — CF-Tunnel: Round-Robin killen + reziprokes HA ▸ ✅ **HA WIEDER VOLL (2026-08-24)** — Kern 2026-07-10 — `docs/cf-tunnel-mapping.md §10 + §15`, #1033
|
||||
### TP16 · Gitea #8 — CF-Tunnel: Round-Robin killen + reziprokes HA — Kern 2026-07-10 — `docs/cf-tunnel-mapping.md §10 + §15`, #1033
|
||||
- ✅ **GEFIXT 2026-08-24 — Ursache war ein `docker stop`, kein QUIC-Flap.** Die beiden macmini-Connectoren
|
||||
(`cloudflared-bfnas`, `cloudflared-bfpve`) standen auf `Exited (0)`, **beide `finishedAt` auf derselben
|
||||
Sekunde `2026-07-21T13:00:53`**, bei korrekt gesetztem `restart=unless-stopped` und `http2`. Fix =
|
||||
@@ -249,7 +250,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
- Abh.: TP14.1/14.2 ✅. Verwandt: **TP11** (CF-Tunnel-Track-B/C + Access), #1033. SSoT: `docs/cf-tunnel-mapping.md`. Memory `project_cf_tunnel_1033`.
|
||||
- ✅ **Incident 2026-07-16 (User unterwegs, WARP „spinnt", keine BF-IPs mehr) ▸ Public-FQDN-Teil gefixt:** macmini-Connectoren (`cloudflared-bfnas`/`-bfpve`) flappten live unter QUIC/UDP (Docker-Desktop-Bridge-NAT) → `bf-nas`/`bf-pve` von 2 auf 1 Connector degradiert (HA faktisch weg, nicht down). Fix: beide auf `TUNNEL_TRANSPORT_PROTOCOL=http2` umgestellt (rollend, 0 Downtime), wieder voll HA. Konvention: macmini-Connectoren künftig immer `http2`. **ICMP-Befund (getrennt, strukturell):** TCP über die WARP-Route ging überall, ICMP nicht (fm 36 %/bf 100 %/dw 100 % Loss) — cloudflared ist QUIC-Proxy, kein WireGuard. **Kurz als „bf-pve-Route wegen USV" notiert (Commit `1c1a676`) → REVIDIERT** (Begründung invertiert: bf-pve-Connectoren laufen auf NAS+macmini, nicht pve). Details: `docs/cf-tunnel-mapping.md §13`. **→ Richtungswechsel für Private Access = TP16.1.**
|
||||
|
||||
### TP16.2 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) ▸ 📋 KONZEPT (2026-08-14), Umsetzung offen
|
||||
### TP16.2 · Gitea #9 — Härtung der öffentlich erreichbaren Dienste (bypass-Strang) — Umsetzung offen
|
||||
- 🔴 **HARTER CONSTRAINT (User 2026-08-14): `vw` wird NICHT WARP-/Access-geschützt.** Gilt dauerhaft, nicht „vorerst". **Begründung (technisch belastbar, nicht Bequemlichkeit):** die Bitwarden-Clients (Browser-Extension, Mobile, Desktop) können den **interaktiven Access-Login nicht durchführen** — dasselbe Muster, das die HA-Companion-App auf `400` laufen lässt. Ein Passwortmanager hinter einem Client, den man im Notfall (fremdes Gerät, Neuinstallation, Handyverlust) nicht installieren kann, ist ein **Aussperr-Risiko gegen den eigenen Schlüsselbund** — und damit gefährlicher als das, wovor die Maßnahme schützen soll. **Die frühere TP16-Schritt-5-Absicht „vw von bypass auf geschützt heben" ist damit gestrichen.**
|
||||
- **Ziel stattdessen: Schutz erhöhen, ohne Clients zu brechen** — gilt für **alle** bypass-Dienste (`vw`, `hass`, `gitea`).
|
||||
- ✅ **Schicht 0 — bereits vorhanden, nicht unterschätzen:** keine offenen Ports, **alles ausschließlich über CF-Tunnel** (3-Tunnel-HA, TP16), TLS terminiert an der Edge, DDoS-Schutz. Der Origin ist nicht direkt erreichbar. Das ist bereits mehr als bei den meisten Selfhostern.
|
||||
@@ -263,7 +264,7 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
- **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) ▸ ✅ **ABGESCHLOSSEN 2026-09-01** — Flip (§5) + Abbau (§6) durch; öffentlich nur noch `bf-public` (+`fm`) — `docs/tp16.3-bf-public-ap-connector-runbook.md`
|
||||
### TP16.3 · Gitea #10 — cloudflared-Reduktion: was der Mesh als Datenpfad kann (und was nicht) — Flip (§5) + Abbau (§6) durch; öffentlich nur noch `bf-public` (+`fm`) — `docs/tp16.3-bf-public-ap-connector-runbook.md`
|
||||
|
||||
#### ✅ §6 Abbau — AUSGEFÜHRT 2026-09-01 (vom User vorgezogen, „es wird nichts mehr sein")
|
||||
|
||||
@@ -664,7 +665,7 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik
|
||||
TP — siehe Quick-Index Zeile TP16.3. Bezug zu **TP35** Schicht 2: Reihenfolge ist TP16.3 → TP35.
|
||||
TP35 wartet mit Schicht 2, bis das Fronting steht.
|
||||
|
||||
### TP17.1 — Shelly Gen4 raus aus dem WLAN ▸ ❌ **VERWORFEN (2026-08-23) — Weg bewiesen, Ziel damit nicht erreichbar** — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md`
|
||||
### TP17.1 · Gitea #11 — Shelly Gen4 raus aus dem WLAN — 📋 `docs/tp17.1-shelly-gen4-funkmigration-runbook.md`
|
||||
> ❌ **User-Entscheid 2026-08-23 nach dem Pilot: stoppen und zurückbauen.** Auslöser war Christophs
|
||||
> strukturelle Frage: *„zigbee sollte die antwort auf die vielen radios auf -iot geben. nutzt aber
|
||||
> wenig wenn man das wifi nicht abdrehen kann bzw. es nicht mehr aktivieren kann ohne reset."*
|
||||
@@ -816,7 +817,7 @@ und Letzteres verlangt die **physische Bestätigung am Gerät** — ap1 Technik
|
||||
|
||||
</details>
|
||||
|
||||
### TP17.3 — Shelly-Regression nach der VLAN30-Migration ▸ ✅ **ERLEDIGT (2026-08-23, Folge-Session): beide Geräte repariert, Ursache `eco_mode` bewiesen** — offen nur noch der Flotten-Entscheid (gehört TP17.1) — aus TP17 herausgelöst, User-Meldung
|
||||
### TP17.3 · Gitea #12 — Shelly-Regression nach der VLAN30-Migration — offen nur noch der Flotten-Entscheid (gehört TP17.1) — aus TP17 herausgelöst, User-Meldung
|
||||
> **User-Meldung 2026-08-23:** „vor einiger zeit hast du an den shellys änderungen vorgenommen. das
|
||||
> hatte bei einigen einen bösen seiteneffekt: **lüfter-shelly musste ich doppelt drücken**. der übelste
|
||||
> ist jedoch der von **diele ug: seit deinen änderungen spinnt er herum und blinkt wie ein wilder.
|
||||
@@ -953,7 +954,7 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
4. Reboots der verbleibenden 13 `restart_required`-Geräte — **gefahrlos** (empirisch an `s26` belegt),
|
||||
deshalb kein eigener Termin, sondern Mitnahme bei TP17.1.
|
||||
|
||||
### TP18 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier) ▸ 🔜 KONZEPT (2026-07-12)
|
||||
### TP18 · Gitea #13 — Souveräner Objektspeicher (Garage, Multi-Site, S3-Cold-Tier)
|
||||
- **Auslöser:** 9-TB-Syno-Backup-Volume **zu 2/3 voll**; reine Daten ~3 TB, Tendenz steigend, „schlagartiger" Bedarf nicht ausgeschlossen. „Wenn skalieren, dann jetzt Syno-Nachfolger mitdenken."
|
||||
- **Kriterien (User-priorisiert):** (1) **keine Fremd-Abhängigkeit / kein Cloud-Lock-in** (Cloud-Opex abschreckend); (2) **Multi-Site BF/FM/DW**; (3) **HA** (Standort-Verlust überlebbar); (4) **billig/teuer trennen** — Variante **„kalt = Objektspeicher, heiß = lokal"** genügt (kein Hot/Cold *innerhalb* des Stores nötig); (5) **horizontal + vertikal skalierbar**; (6) **S3-API als Branchenstandard**, den jedes Backup-Tool spricht.
|
||||
- **Entscheid Engine = Garage** (Deuxfleurs, Rust): einziger Kandidat, der **geo-verteilt über offenes Internet/WAN latenztolerant** ist (CRDT-Metadaten, eventually consistent) → passt aufs 3-Keller-Modell BF/FM/DW. **Ceph verworfen** (will Sub-ms-LAN, Single-Cluster-über-WAN = Anti-Pattern; zu schwer, ~1 GB RAM/TB). **MinIO** (AGPL + Console/Feature-Abbau 2025) und **SeaweedFS** (kann Hot/Cold + Erasure-Coding, aber komplexer, Multi-Site async) = **Fallback**, falls später feingranulares Tiering / EC-Effizienz bei zweistelligen TB dominant wird.
|
||||
@@ -970,14 +971,14 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
- **Offen (Modell scharfstellen):** realer Strompreis; ob **FM/DW eigene Nodes stellen** oder Christoph HW hinstellt; Backup-Tool-Wahl (restic vs Kopia); ob heißes Zeug künftig auf ZFS/pve statt Syno.
|
||||
- Abh.: **TP10** (WG-Multi-Site = Transport, Upload-BW-Constraint), **TP14** (Container-Hosts), **TP15** (Node-HW) / **TP24** (Stromversorgung), Muster von **TP16** (reziprokes Per-Host-HA). Kein Blocker für laufende TPs. SSoT dieser Session.
|
||||
|
||||
### TP20.1 — Vaultwarden → Platform-Postgres + Cross-Site-HA ▸ 🔜 KONZEPT (2026-07-14, ausgelagert aus TP20)
|
||||
### TP20.1 · Gitea #14 — Vaultwarden → Platform-Postgres + Cross-Site-HA — ausgelagert aus TP20)
|
||||
- **Ziel:** `vw` (`.20.127`, heute MariaDB) auf den Precious-Tier holen **und** das eigentliche User-Ziel erfüllen: **DB-Replikation über 2 Sites** (Standort-Verlust überlebbar).
|
||||
- **Phase 1 — Migration (single-site):** vw **MariaDB→Postgres** (Export → Import in Tier-1-DB `vaultwarden` → `DATABASE_URL` umstellen → verifizieren). Danach ist vw precious + im pgBackRest/PITR-Schirm von TP20. *(Alternative verworfen: vw auf MariaDB lassen + MariaDB-Repl — zugunsten EINER Replikations-/Backup-Mechanik im Precious-Tier.)*
|
||||
- **Phase 2 — Cross-Site-Standby:** `db-platform-standby`-LXC am 2. Standort → `pg_basebackup`/Streaming-Replication über **WG (TP10)** → **manuelles Promote-Runbook**. Repliziert den ganzen Tier-1-Cluster (vw = HA-Ziel, Nautobot bekommt Gratis-DR-Kopie). **Patroni + 3. Witness** nur, falls später Auto-Failover statt manuellem Promote gewünscht.
|
||||
- **Verwandt/Abgrenzung:** vw ist auch in **TP16** offen (CF-Access: `bypass`→WARP-geschützt) — **DB-HA ≠ Access**, getrennt behandeln. Reziprokes 2-Site-Muster analog **TP16**/**TP18**.
|
||||
- Abh.: **TP20** (Tier-1 Postgres muss stehen), **TP10** (WG-Transport für Phase-2), **TP18** (Backup-Schirm). Kein Blocker für TP21.
|
||||
|
||||
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 5 WIEDER OFFEN (Redesign 2026-09-12): Self-Service deckt die DAU-Requirements nicht ab → Stufe 6 bleibt dahinter gated**
|
||||
### TP23 · Gitea #15 — DNS-Policy & Self-Service-Ausnahmen
|
||||
> 🔴 **Redesign 2026-09-12 — Stufe 5 reopened.** Bei „Stufe 6 angehen" fiel auf: die 09-04-Stufe-5
|
||||
> (single-domain, temp-only 15m/1h/4h, **eine** Ausnahme gleichzeitig, **HA-Neustart bei neuer
|
||||
> Domain**, `allow-domain` global statt pro Person) deckt die Nutzer-Requirements **nicht** ab
|
||||
@@ -1074,7 +1075,7 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
- ⚠️ **802.1X löst die IoT-Identität NICHT:** Consumer-IoT (Shelly, Sonos) hat keinen 802.1X-Supplicant; der Standardweg ist **MAB** = wieder MAC-basiert → **kein Identitätsgewinn gegenüber der DHCP-Reservierung**. **TP8 bleibt richtig, aber als Sicherheitsthema** (dynamische VLAN-Zuweisung, kein MAC-Spoofing bei Managed-Geräten), nicht als DNS-Identitätsquelle.
|
||||
- **Abh.:** **TP21.1 TODO 1** (DoH auf core), **TP5** (Firewall/Bypass-Sperre — Reihenfolge s.o.), **TP16 Schritt 5** (Access-Kollision), **TP16.1** (falls WARP zuhause bleiben soll), **TP8** (nur Sicherheit). **Kein Blocker für TP5 → TP6**, aber TP5 darf die Bypass-Sperre nicht vorziehen.
|
||||
|
||||
### TP24 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) ▸ 🔜 KONZEPT — **aus TP15 herausgelöst (User-Entscheid 2026-08-16)** — `docs/hw-refresh-requirements.md §9`
|
||||
### TP24 · Gitea #16 — Unterbrechungsfreie Stromversorgung (DC-USV, 48V-Bus) — aus TP15 herausgelöst (User-Entscheid 2026-08-16)** — `docs/hw-refresh-requirements.md §9`
|
||||
- **Warum ein eigener TP:** Die USV hing bisher als Unterpunkt in TP15 (HW-Refresh Switching/Router) mit. TP15s Kernauftrag — CRS326-Beschaffung, Bring-up, Core-Tausch — ist seit 2026-08-08 **durch**; die Stromversorgung ist als einziger Rest übrig geblieben und hat mit Switching sachlich nichts zu tun. Sie ist ein **eigenständiges Infrastruktur-Thema mit eigener Einkaufs-, Verkabelungs- und Entscheidungslogik** (Elektrik, Batterie, Standort) und blockiert nichts auf dem kritischen Pfad. Darum eigener TP.
|
||||
- **Was es ist (Begriffsklärung):** eine **Pufferbatterie für die Netz-/Infrastruktur-Hardware** — **keine** klassische AC-USV mit 230-V-Steckdosen. Ein 48-V-Gleichspannungsbus versorgt die Geräte **direkt an ihrem DC-Eingang**; die Batterie hängt am selben Bus und übernimmt bei Netzausfall ohne Umschaltlücke. Der Umweg AC→DC→AC→DC (und damit dessen Dauerverlust) entfällt.
|
||||
- **Zielbild (Aufbau, unverändert gültig aus der TP15-Recherche):**
|
||||
@@ -1102,230 +1103,19 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
- **Abh.:** keine harten. **TP15 ✅** (die zu versorgende Hardware steht und hat DC-Eingänge). Verwandt: **TP18** (Garage-Nodes brauchen je Site eine Stromversorgung), **TP11** (USV-Zustand ins Monitoring). **Kein Blocker für TP5/TP6.**
|
||||
- **Historie:** war bis 2026-08-16 Unterpunkt von TP15 („🔋 DC-USV: MeanWell LRS-150-48 + DDR-120-48 + LiFePO4 20Ah (~€190), 48V-Bus; CRS326 DC-input passt"). Vollständige Recherche inkl. Schaltbild, Effizienzvergleich und Gerätetabelle: `docs/hw-refresh-requirements.md §9` (+ §10 D/E/F für die offenen Fragen).
|
||||
|
||||
### TP25 — WLAN-Stabilisierung ▸ ✅ **FERTIG (2026-09-15)** → [Archiv](teilprojekte-archiv.md#tp25)
|
||||
### TP25 · Gitea #17 — WLAN-Stabilisierung — → [Archiv](teilprojekte-archiv.md#tp25)
|
||||
|
||||
> **Abgeschlossen:** Platzierung final (ap1 Sideboard · ap2 Technik UG · ap3 Technik OG), Fund E
|
||||
> geschlossen (`7.24.1` war die Ursache, nicht `ft-over-ds`), alle vier Handoff-Punkte aufgelöst,
|
||||
> Gitea #17 closed. Volltext (Kondensat + Langfassung) im Archiv.
|
||||
|
||||
<a id="tp28"></a>
|
||||
### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core` — → [Archiv](teilprojekte-archiv.md#tp28)
|
||||
|
||||
### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **ap3 LIVE (2026-08-30)** — wieder geöffnet und durchgezogen
|
||||
> **Abgeschlossen:** ap1 Sideboard · ap2 Technik UG · ap3 Technik OG (Platzierung final),
|
||||
> Kanalplan committed (ap1 `5260/5280` · ap2 `5300/5320` · ap3 `5180`), `wifi-cap`-Rest auf dem
|
||||
> Core bereinigt (`enabled=no`), Compliance 29/29 über 5 Geräte. Gitea #18 closed. Volltext im Archiv.
|
||||
|
||||
> **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist
|
||||
> unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30,
|
||||
> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP
|
||||
> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder
|
||||
> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".**
|
||||
|
||||
- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
|
||||
Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
|
||||
war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
|
||||
Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
|
||||
**der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
|
||||
`-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
|
||||
**statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
|
||||
- **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
|
||||
`/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
|
||||
`/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
|
||||
`vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
|
||||
Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
|
||||
- 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
|
||||
Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
|
||||
`torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
|
||||
Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
|
||||
— der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
|
||||
- ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
|
||||
(30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
|
||||
dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
|
||||
**frische DHCP-Lease pro SSID**, nicht die Registration-Table.
|
||||
- **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
|
||||
mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
|
||||
zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
|
||||
- ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
|
||||
ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
|
||||
durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
|
||||
sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
|
||||
kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
|
||||
wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
|
||||
`last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
|
||||
- ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
|
||||
antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
|
||||
gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@<ap> "/system reboot"`.
|
||||
- ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
|
||||
`nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
|
||||
→ Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
|
||||
auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
|
||||
allein erreicht Nautobot nicht.
|
||||
- ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
|
||||
Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
|
||||
als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
|
||||
|
||||
- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
|
||||
jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
|
||||
(`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
|
||||
- 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
|
||||
je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
|
||||
sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
|
||||
Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
|
||||
defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
|
||||
- ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
|
||||
Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
|
||||
bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
|
||||
referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
|
||||
`configuration=`-Verweis, der beim Loeschen folgenlos blieb.
|
||||
- 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
|
||||
`/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
|
||||
`/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
|
||||
**nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
|
||||
- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
|
||||
(der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
|
||||
welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
|
||||
`discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
|
||||
**Ergebnis: 92/92 compliant, 0 Drifts.**
|
||||
- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<<REDACTED>>`
|
||||
im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
|
||||
Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
|
||||
Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
|
||||
als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
|
||||
drei Bring-up-Dateien, die voneinander abgeschrieben sind.
|
||||
- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
|
||||
wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
|
||||
> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
|
||||
> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
|
||||
> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
|
||||
|
||||
- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
|
||||
Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
|
||||
TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
|
||||
das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
|
||||
Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
|
||||
waren die Funkparameter aus TP25.
|
||||
- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
|
||||
4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
|
||||
TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
|
||||
media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
|
||||
verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
|
||||
sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
|
||||
- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
|
||||
Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
|
||||
deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
|
||||
- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
|
||||
`/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
|
||||
einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
|
||||
vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
|
||||
- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
|
||||
iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
|
||||
drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
|
||||
Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
|
||||
✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
|
||||
`wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
|
||||
sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
|
||||
- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
|
||||
`wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
|
||||
toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
|
||||
Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
|
||||
*(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
|
||||
„KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
|
||||
gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
|
||||
die Punkte darunter mitziehen.**)*
|
||||
- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
|
||||
Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
|
||||
nur `admin logged in/out via api`).
|
||||
- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
|
||||
Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
|
||||
|
||||
|
||||
#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30
|
||||
|
||||
**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**.
|
||||
**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am
|
||||
Gerät wie ap1/ap2 — **keine** DHCP-Reservierung.
|
||||
|
||||
**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die
|
||||
gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die
|
||||
bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten").
|
||||
|
||||
**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:**
|
||||
|
||||
| AP | 5 GHz | Band | DFS | 2,4 GHz | wann |
|
||||
|---|---|---|---|---|---|
|
||||
| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** |
|
||||
| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug |
|
||||
| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug |
|
||||
| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — |
|
||||
|
||||
🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum
|
||||
Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein
|
||||
**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück.
|
||||
|
||||
> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der
|
||||
> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile
|
||||
> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat.
|
||||
> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die
|
||||
> Bridge-Host-Tabelle stellen.** `/interface bridge host print where interface=etherN` nennt die
|
||||
> MAC des Gegenübers — hier `D0:EA:11:FF:16:9D`, exakt die `E01` vom ap3-Label.
|
||||
|
||||
##### Stand 2026-08-30 — ✅ DURCH
|
||||
- ✅ **`core ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`), GC-Template
|
||||
nachgezogen; AP-Zweig rendert Bridge-Ports aus **`ap.extra_ports`** statt hartkodiert.
|
||||
- ✅ **Zugang über `ap3 ether2` → `core ether22`** (Patchkabel). Über `ether1` war ap3
|
||||
grundsätzlich nicht administrierbar, s. Gotchas unten. Loop-Schutz: `ether21` war während
|
||||
Phase 1 abgeschaltet, danach im selben Befehl getauscht (`ether22` aus, `ether21` an).
|
||||
- ✅ **Bring-up in zwei Phasen**: Phase 1 (`ether1` aus der WAN-Liste, `10.40.99.13/24` auf
|
||||
`vlan99`, Flotten-PW) über das Kabel, Phase 2 (Werks-WLAN weg, CAP-Modus,
|
||||
`slaves-datapath`, `vlan-filtering=yes`) über `vlan99`.
|
||||
- ✅ **RouterOS 7.20.8 → 7.24.1 + RouterBOOT 7.24.1** (zwei Reboots, TP27-Kriterium
|
||||
`current-firmware = upgrade-firmware`). 🆕 **Die Angleichung der übrigen vier ist am
|
||||
2026-08-31 erfolgt** (Christoph, WebFig) — die Flotte ist einheitlich, TP27 erledigt.
|
||||
- ✅ **ap1 funkstill** (Provisioning-Regeln entfernt + `remote-cap provision`), gegengeprüft
|
||||
**am CAP**: nur noch `wifi1`/`wifi2` mit `running=false`, keine dynamischen VAPs.
|
||||
- ✅ **ap3 funkt**: `cap-wifi5` = **`5180/ax/Ce/I`**, `cap-wifi6` = **`2437/ax`**, voller
|
||||
VAP-Satz (2 Master + 7 Slaves), alle `running=true`.
|
||||
- ✅ **Datapath bewiesen**: 20 IOT-Clients auf ap3, Ping über VLAN30 mit **0 % Loss**.
|
||||
*(Bewusst nicht über die Registration-Table abgenommen — die zeigte im 14-h-Ausfall
|
||||
25 zufriedene Clients. Bei statisch reservierten Shellys erzeugt ein Roam keine neue
|
||||
DHCP-Lease, deshalb ist hier Datenverkehr das belastbare Signal.)*
|
||||
- ✅ **Nautobot**: DeviceType `cAP ax`, Device `ap3` mit 16 Interfaces, `primary_ip4`,
|
||||
`secrets_group`, ConfigContext (`5g=ch36 2g=ch6 extra_ports=[]`).
|
||||
Nebenkorrektur: ap1 stand auf „Wohnzimmer Sideboard", steht aber in **Technik UG**.
|
||||
- ✅ **Compliance 130/130, 0 Drifts über 5 Geräte.** Die drei Werksreste, die der erste
|
||||
Lauf mit ap3 gemeldet hat (defconf-DNS, 11 defconf-Firewallregeln, fehlende
|
||||
`/ip service`-Härtung), sind mit `routeros/tp28-ap3-werksreste.rsc` abgeräumt.
|
||||
- 🔜 **Offen:** TP25-Messrunde (`bash docs/tp25-runde.sh ap3-bringup`) — braucht einen
|
||||
Rundgang. Leitfrage: deckt ap3 allein ab, was ap1 abgedeckt hat?
|
||||
- 🔜 **Offen:** Passwort-Rotation (TP30) — das gemeinsame Admin-PW ist in dieser Session
|
||||
verbrannt; ap3 trägt es bereits mit, hängt also in der Rotation mit drin.
|
||||
|
||||
##### Was am cAP ax anders ist als am hAP ax² — und was daran wehtut
|
||||
- 🔴 **Nur ZWEI Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5`. Der
|
||||
GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*.
|
||||
Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`).
|
||||
**Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.**
|
||||
- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax
|
||||
bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz
|
||||
„kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu.
|
||||
- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus,
|
||||
wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port
|
||||
(VLAN1 bleibt leer, TP19 ist geschlossen).
|
||||
- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity +
|
||||
Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`,
|
||||
das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24).
|
||||
|
||||
##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen)
|
||||
1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit
|
||||
genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und
|
||||
hat keinen zweiten → 5 GHz dieses APs bleibt unten.
|
||||
2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf
|
||||
`5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt,
|
||||
9 dBm gesendet.
|
||||
3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen,
|
||||
der Port ist untagged-Member von VLAN40 bei `pvid=1`.
|
||||
|
||||
|
||||
### TP29 — Anwesenheitserkennung fürs Energiesparen ▸ 💡 **IDEE, noch nicht auf dem kritischen Pfad** (angelegt 2026-08-21)
|
||||
### TP29 · Gitea #19 — Anwesenheitserkennung fürs Energiesparen
|
||||
> **User-Formulierung (2026-08-21):** „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So
|
||||
> kommen wir dem Ziel der Energieersparnis ohne Komfortverlust groß näher."
|
||||
|
||||
@@ -1351,7 +1141,7 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört
|
||||
dahin kommt. **Kein Blocker für irgendetwas.**
|
||||
|
||||
<a id="tp30"></a>
|
||||
### TP30 — Credentials: neu setzen & rotieren (Sammelbecken) ▸ 🔜 **ANGELEGT 2026-08-24, bewusst GEBÜNDELT gegen Projektende**
|
||||
### TP30 · Gitea #20 — Credentials: neu setzen & rotieren (Sammelbecken)
|
||||
> **User-Entscheid 2026-08-24:** „ich würde gerne daraus einen eigenen punkt machen. ich bin mir
|
||||
> sicher, dass es noch öfters passwörter zu rotieren geben wird. das machen wir dann alles gegen
|
||||
> projektende." — nachgeschärft am selben Tag: **„tp30 wird unser sammelbecken für credentials,
|
||||
@@ -1386,7 +1176,7 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört
|
||||
| 9 | **`GITEA_ACCESS_TOKEN`** (API-Zugang `gitea.emsenhuber.net`) | Session-Tooling (`bin/gitea-tp-sync.py`, Ad-hoc-API-Calls) | 🔴 am 2026-09-14 (TP25-In-situ-Session) **ins Transkript** geraten: Env-Parse-Fehler schob den Token in die URL-Konstruktion, `urllib` warf `ValueError: unknown url type` und druckte ihn im Traceback | Mechanismus zu: Token gehört **nur in den `Authorization`-Header, nie in eine URL** — und der Fehlerpfad, der ihn enthält, darf ihn nicht ausgeben (gleiche Familie wie #1/#5/#6/#7/#8, diesmal Unterklasse „Fehlerausgabe statt Maskierung") | neu anlegen (Gitea → Settings → Applications), `~/.hermes/.env` nachziehen, altes widerrufen; Konsumenten = `gitea-tp-sync.py` + Session-Ad-hoc-Calls |
|
||||
| 10 | **`N8N_MCP_BEARER`** (n8n-Public-API) | n8n-MCP `macmini:3001` — in **7 Projekten** referenziert (`bf-infra`, `blaueblueten-n8n`, `travelmate`, `poulios-recherche`, `ha-xstate`, `valamar`, `uwe-job-bewerbung`) | 🔴 am 2026-09-22 (Env-/OpenRouter-Session) **ins Transkript** geraten: die Redaktions-Alternation führte `BEARER` nicht, und der Schlüssel endet **ohne `=`** auf den Bezeichner — die Feld-Ersetzung hatte damit keine Ankerstelle | Ablage nur `~/.secrets/exports.zsh`; alle Konsumenten sind `${…}`-Referenzen (kein Klartext in den Repos, geprüft) | Token in n8n neu erzeugen, `~/.secrets` nachziehen, **App-Neustart** (MCP-Env wird beim Start gelesen) — danach 7 Projekte ohne Edit, weil sie nur referenzieren |
|
||||
| 11 | **`TERPENE_RO_URL`** (PG-User `terpene_ro`, DB `terpene`) | Terpenfinder-DB read-only `10.40.20.20:5432` — **Cross-Projekt (blaueblüten)**; einziger Konsument ist `~/.secrets/exports.zsh` (+ `.bak`) | 🔴 am 2026-09-22 (Env-/OpenRouter-Session) **ins Transkript** geraten — **neue Unterklasse derselben Familie:** das Passwort steckt in der **URL** (`…://user:PASS@host`), die Feld-Maskierung ankert auf `=` und findet dort keine Zeile zum Ersetzen (dieselbe Lücke wie #9: was nicht als Feld auftritt, wird nicht maskiert — das geteilte Redaktions-Snippet deckt diesen Fall **nicht** ab) | Wert nur in `~/.secrets/exports.zsh`; **kein** Konsument in Code oder Configs | `ALTER ROLE terpene_ro … PASSWORD '<neu>'` am NAS (`10.40.20.20`), dann `exports.zsh` nachziehen; **kein** Geräte-Roundtrip ⇒ billigste Rotation nach #6. ⚠️ Cross-Projekt: nur mit Christophs Okay anfassen |
|
||||
| 12 | **OpenRouter-API-Key** (`sk-or-v1-cb6…258`) | Claude-Desktop-3P-Gateway: CCR-Provider in `~/.claude-code-router/config.sqlite` (`Providers[].api_key`) **+** 3P-Config `e1219ea4` (`inferenceGatewayApiKey`) | 🔴 am 2026-09-23 (Claude-Desktop/OpenRouter-Session) ins Transkript geraten — das `sed` ankerte auf den **geratenen** Namen `apiKey`, der echte Schlüssel heißt `inferenceGatewayApiKey` ⇒ kein Match (gleiche Familie wie #5/#10/#11, neue Unterklasse s. Merkregel unten). Zweiter Fund derselben Session: `head -c 400` auf `~/.claude-code-router/bin/ccr-claude-code-api-key-claude-code` gab den **CCR-Profil-Token** aus (local-only `127.0.0.1`, geringe Reichweite — nicht in der Zählung) | ⚠️ **kein `~/.secrets`-Eintrag** — der Wert lebt nur in `config.sqlite` + 3P-Config, und er ist **noch gültig** (200, verifiziert 2026-09-23 via `/api/v1/key`) | neu ausstellen (openrouter.ai/keys); danach **zwei** Konsumenten: `config.sqlite` (`Providers[].api_key`, mit CCR-Neustart) + 3P-Config (`inferenceGatewayApiKey`) — beide müssen mit, sonst steht die App still (3P ist alles-oder-nichts) |
|
||||
| 12 | **OpenRouter-API-Key** (`sk-or-v1-cb6…258`) | Claude-Desktop-3P-Gateway: CCR-Provider in `~/.claude-code-router/config.sqlite` (`Providers[].api_key`) **+** 3P-Config `e1219ea4` (`inferenceGatewayApiKey`) | 🔴 am 2026-09-23 (Claude-Desktop/OpenRouter-Session) ins Transkript geraten — das `sed` ankerte auf den **geratenen** Namen `apiKey`, der echte Schlüssel heißt `inferenceGatewayApiKey` ⇒ kein Match (gleiche Familie wie #5/#10/#11, neue Unterklasse s. Merkregel unten). Zweiter Fund derselben Session: `head -c 400` auf `~/.claude-code-router/bin/ccr-claude-code-api-key-claude-code` gab den **CCR-Profil-Token** aus (local-only `127.0.0.1`, geringe Reichweite — nicht in der Zählung) | ⚠️ **kein `~/.secrets`-Eintrag** — der Wert lebt nur in `config.sqlite` + 3P-Config, und er ist **noch gültig** (200, verifiziert 2026-09-23 via `/api/v1/key`) | neu ausstellen (openrouter.ai/keys); danach **zwei** Konsumenten: `config.sqlite` (`Providers[].api_key`, mit CCR-Neustart) + 3P-Config (`inferenceGatewayApiKey`) — beide müssen mit, sonst steht die App still (3P ist alles-oder-nichts). **Nachtrag 2026-09-24 (Konsumenten-Inventur korrigiert, live geprüft):** die **angewandte** 3P-Config `8f69f2f1` (= „Claude Code Router", `appliedId` seit 2026-09-24) trägt **nur den CCR-Gateway-Token** (len 50, `x-api-key`, gegen CCR mit 200 verifiziert) — **nicht** den OR-Key. Der OR-Key lebt real in **`config.sqlite`** (live, von CCR benutzt) **und** in der nur *gespeicherten*, nicht angewandten `e1219ea4` (len 73, sha `f23aaebdaa4c`). Rotation also: `config.sqlite` → `Providers[0].api_key`, **CCR-App neu starten**, `e1219ea4` löschen/ersetzen; `8f69f2f1` bleibt **unangetastet** (sonst steht die App still) |
|
||||
|
||||
> ✅ **Der Leak-MECHANISMUS hinter #1 ist seit 2026-08-31 zu — der Wert bleibt trotzdem verbrannt.**
|
||||
> Alle sechs Skripte im Repo, die `sshpass -p <PW>` benutzten (`nautobot/gc-transaktion.py` und
|
||||
@@ -1637,7 +1427,7 @@ unabhängig vom gebündelten Durchgang.**
|
||||
|
||||
|
||||
<a id="tp31"></a>
|
||||
### TP31 — Reverse Proxy für innen und außen; cloudflared von sechs Knoten auf einen ▸ ✅ **ABGESCHLOSSEN 2026-09-01**
|
||||
### TP31 · Gitea #21 — Reverse Proxy für innen und außen; cloudflared von sechs Knoten auf einen
|
||||
> ✅ **Durch — Schritt 5+6 sind über den Piloten [TP16.3](#tp163) miterledigt (2026-09-01):** `bf-public`
|
||||
> **healthy, 12 Connectoren** auf ap1/ap2/ap3; `vw`/`hass`/`n8n`/`gitea` laufen über CF → **Caddy**
|
||||
> (`https://10.40.20.40:443`), die drei Alttunnel `bf-nas`/`bf-pve`/`bf-mm` **gelöscht**. Damit steht das
|
||||
@@ -1887,7 +1677,7 @@ diesmal mit Datenverlust statt 530ern. **Automatisches Ingress-Failover erst, we
|
||||
- 📋 **Vollständiger Plan mit allen Abwägungen:** `~/.claude/plans/expressive-pondering-clarke.md`
|
||||
|
||||
<a id="tp32"></a>
|
||||
### TP32 — Nummern- und Adress-Systematik ▸ 🟢 **KONZEPT STEHT (2026-08-25), Startreihenfolge bereits repariert**
|
||||
### TP32 · Gitea #22 — Nummern- und Adress-Systematik
|
||||
> **User-Auftrag 2026-08-24:** „du/ich sollst in der lage sein, selbstständig nach einem vorgegebenen
|
||||
> system freie ips vergeben zu können … und diese systematik findet sich in anderen netzen künftig auch
|
||||
> wieder" · „ich meine 100 sollte künftig für die core services stehen."
|
||||
@@ -1998,7 +1788,7 @@ gewachsen, nicht geplant. Merkregel: **„Bestand bleibt, wo er ist" gilt nur, s
|
||||
wo er ist, ohne nachzusehen.**
|
||||
|
||||
|
||||
### TP33 — Sonos aus der Ferne bedienen (VPN/WARP) ▸ 💡 **IDEE, bewusst zurückgestellt** (angelegt 2026-08-25, User-Entscheid „später entscheiden")
|
||||
### TP33 · Gitea #23 — Sonos aus der Ferne bedienen (VPN/WARP) — angelegt 2026-08-25, User-Entscheid „später entscheiden")
|
||||
|
||||
- **Auslöser:** beim Sonos-Fall vom 08-25 kam die Frage auf, ob die Player auch **von unterwegs**
|
||||
auffindbar sein sollen. Antwort für jetzt: **nein, später entscheiden** — hier nur festgehalten,
|
||||
@@ -2017,7 +1807,7 @@ wo er ist, ohne nachzusehen.**
|
||||
|
||||
---
|
||||
|
||||
### TP35 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard ▸ ✅ **ABGESCHLOSSEN 2026-09-12** — 5/6 Schichten durch (MCP-Schichtung FQDN→IP, Credentials-`{env:}`-Flip angewandt+verifiziert, globale CLAUDE.md-Schicht, `/news`, Backup-Guard, `/projektstatus`-Skill). Schicht-3-**pro-Projekt**-Rollout laut Abgrenzung eigene Session je Projekt (kein Blocker). Einziger Rest = On-Network-mikromcp-Smoketest (off-site nicht machbar, VLAN99:80 timeout, non-blocking) → als Merkposten nach TP30. Gitea #25 closed/`status:done`.
|
||||
### TP35 · Gitea #25 — MCP-Schichtung global/projekt/user + 3-Schicht-CLAUDE.md + Announcement-Mechanismus + Backup-Guard — 5/6 Schichten durch (MCP-Schichtung FQDN→IP, Credentials-`{env:}`-Flip angewandt+verifiziert, globale CLAUDE.md-Schicht, `/news`, Backup-Guard, `/projektstatus`-Skill). Schicht-3-**pro-Projekt**-Rollout laut Abgrenzung eigene Session je Projekt (kein Blocker). Einziger Rest = On-Network-mikromcp-Smoketest (off-site nicht machbar, VLAN99:80 timeout, non-blocking) → als Merkposten nach TP30. Gitea #25 closed/`status:done`.
|
||||
- **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:**
|
||||
@@ -2034,7 +1824,7 @@ wo er ist, ohne nachzusehen.**
|
||||
|
||||
---
|
||||
|
||||
### TP36 — Index/Status als SSoT nach Gitea (Steuerung aus der branchbaren Datei herauslösen) ▸ ⏳ **CUTOVER 2026-09-07** — Index/Status = Gitea-Issues, Prosa bleibt Datei
|
||||
### TP36 · Gitea #26 — Index/Status als SSoT nach Gitea (Steuerung aus der branchbaren Datei herauslösen) — Index/Status = Gitea-Issues, Prosa bleibt Datei
|
||||
- **Auslöser:** Skill-Run 2026-08-28 (`docs/handoff-20260828-session-a-projektstatus.md`) lieferte einen überholten „Nächster Schritt"-Output; **manuell gepflegte Doku mit Live-Daten verliert gegen die Realität, sobald sie lang wird.** Verschärft 2026-09-06: `docs/teilprojekte.md` wurde in **40/40** letzten Commits angefasst — jeder TP editierte Quick-Index **und** Ist-Stand-Block → **Merge-Konflikt-Magnet**, der Branch-pro-TP verhinderte (ein Parallel-Branch hatte so die TP37-Doppelvergabe verursacht).
|
||||
- 🔄 **Reframe 2026-09-06 (Christoph):** von „SSoT via **Nautobot**" auf „SSoT via **Gitea**". Grund: Index+Status sind **kuratierte Steuerung** (Reihenfolge, Marker, Abhängigkeiten), kein Geräte-Live-Zustand — die gehören auf ein **Issue-Board**, nicht in Custom Fields. **Nautobot bleibt nur für Live-Gerätedaten** relevant (Compliance-Zahl, RouterOS-Version …) → als optionaler Rest unten, entkoppelt von diesem TP.
|
||||
- **Modell (umgesetzt):** TP = **Gitea-Issue** in [`admin/bf-infra`](https://gitea.emsenhuber.net/admin/bf-infra/issues) · Status = Label `status:*` (todo/wip/paused/idea/concept/done/dropped) · Gruppierung = Label `thema:*`. Seeder `bin/gitea-tp-sync.py` (idempotent, Dry-Run default, `--commit` schreibt; Token aus `~/.hermes/.env`, **nie ausgeben**). Erstlauf: **68 Issues (25 open/43 closed) + 15 Labels**.
|
||||
@@ -2047,7 +1837,7 @@ wo er ist, ohne nachzusehen.**
|
||||
- **Verzahnt mit:** TP35 (SSoT + Konsolidierung), TP44 (native Nautobot-Modelle), `bf-infra/CLAUDE.md`-Lessons. Plan: `~/.claude/plans/geht-das-los-auch-reflective-lark.md`.
|
||||
|
||||
<a id="tp37"></a>
|
||||
### TP38 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch ▸ 🔜 **ANGELEGT 2026-08-31**
|
||||
### TP38 · Gitea #28 — Out-of-Band-Management: ein untagged-VLAN99-Port je Switch
|
||||
|
||||
*(Nummer 2026-08-31 von 37 auf 38 korrigiert und von Christoph bestätigt: TP37 war zeitgleich in
|
||||
einem zweiten Branch für „Nautobot als Netzwerkdoku" vergeben worden.)*
|
||||
@@ -2123,7 +1913,7 @@ folgenden Absicherungen dazu, und die Entscheidung darüber in die Umsetzungs-Se
|
||||
|
||||
---
|
||||
|
||||
### TP40 — Home-Assistant-Recorder-Retention: die 41-GB-`states`-Tabelle beschneiden ▸ 🔜 **ANGELEGT 2026-09-01 (User-Okay erteilt)**
|
||||
### TP40 · Gitea #29 — Home-Assistant-Recorder-Retention: die 41-GB-`states`-Tabelle beschneiden
|
||||
|
||||
**Auslöser:** LXC 302 (MariaDB) lief am 2026-09-01 auf **100 %** voll. Sofortmassnahme war `+30G`
|
||||
(TP39 Punkt 3), aber 59 der 65 GB sind die **`homeassistant`-Datenbank**, und die wächst weiter —
|
||||
@@ -2164,7 +1954,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
- 🔴 **Kein eigenmächtiger Datenlöschvorgang** — `purge` wirft echte Historie weg; die
|
||||
Aufbewahrungsdauer ist eine Nutzerentscheidung. Diese Session hat **nur** vergrössert und gemessen.
|
||||
|
||||
### TP37 — Nautobot als durchgehende Netzwerkdoku: Hosts/Container/Dienste, erster Anwendungsfall CF-Connectoren ▸ 🔜 ANGELEGT 2026-08-30 (User-Okay erteilt)
|
||||
### TP37 · Gitea #27 — Nautobot als durchgehende Netzwerkdoku: Hosts/Container/Dienste, erster Anwendungsfall CF-Connectoren
|
||||
> **Christophs Formulierung (2026-08-30):** *„können wir das setup mit den cf-connectoren dann auch in
|
||||
> nautobot abbilden. quasi eine durchgehende netzwerkdoku stück für stück. muss eh fast sein, da du auf
|
||||
> den mikrotiks ja nur via gc/nautobot deployst."*
|
||||
@@ -2226,7 +2016,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
---
|
||||
|
||||
<a id="tp342"></a>
|
||||
### TP34.2 — Interne DNS-Namen als Daten (FQDN aus Nautobot) ▸ 🔜 **ANGELEGT 2026-09-22** (Gitea #73) — **gemeinsame Arbeit**
|
||||
### TP34.2 · Gitea #73 — Interne DNS-Namen als Daten (FQDN aus Nautobot) — **gemeinsame Arbeit**
|
||||
> **Auslöser:** beim Schärfen von **TP45 Schicht C (C4)** am 2026-09-22 stand die Frage, was aus dem
|
||||
> verworfenen **TP34** wird. Christoph: *„C4 unter TP45 schärfen, TP34 wird gemeinsame Arbeit. wir beide!"*
|
||||
> → **eigener Träger im TP34-Nummernkreis**. Parent **#24 (TP34) bleibt `status:dropped`** — kein Reopen:
|
||||
@@ -2254,7 +2044,7 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz
|
||||
|
||||
---
|
||||
|
||||
### TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot ▸ 🔜 **ANGELEGT 2026-09-21** (Gitea #71)
|
||||
### TP45 · Gitea #71 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot
|
||||
> **Auslöser (Christoph, 2026-09-21):** *„schau dir mal die fqdns von `*.emsenhuber.net` an. viele
|
||||
> verschwunden. sowohl caddy als auch cloudflare."* — und, als der Befund stand:
|
||||
> *„wenn wir schon reverse proxy & co korrigieren müssen, wäre es vl jetzt ein guter zeitpunkt,
|
||||
@@ -2479,38 +2269,59 @@ NAT/mangle/WG sind Template-Literale, der Firewall-Filter kommt als ConfigContex
|
||||
die FQDNs, die hier Daten werden). **Abgrenzung:** VMID-Umnummerierung bleibt draußen; Token-**Rotation**
|
||||
läuft in TP30; Tunnel-GC-Namensgebung hier, die **SSoT/Recovery** in TP46.
|
||||
|
||||
### TP46 — Cloudflare/Tunnel: SSoT mit Push-Weg + Full-Recovery from scratch ▸ 🔜 **ANGELEGT 2026-09-21** (Gitea #72)
|
||||
> **Christoph (2026-09-21):** *„haben tunnel/warp/cf schon eine ssot für die config? nautobot? tunnel
|
||||
> sind essenzielle dinge, für die ich eine ssot möchte um drift zu vermeiden. zudem möchte ich auch
|
||||
> from scratch ein full setup/recovery aus der nautobot-config fahren können."* → Umfang **„Voll:
|
||||
> Tunnel+Ingress+DNS+Access"**, Platzierung **„Eigenes TP, GC bleibt hier"**.
|
||||
|
||||
#### Bestandsaufnahme (live 2026-09-21) — es gibt eine Doku-SSoT, aber keinen Master
|
||||
| Was | Wo | Stand |
|
||||
<a id="tp48"></a>
|
||||
### TP48 · Gitea #75 — Recovery-Lücke: Nicht-GC-Objekte + Evaluation Ansible als Nautobot-Ablösung
|
||||
|
||||
> **Anlass:** beim Abschluss von **TP10** (2026-10-05) fiel auf, dass der DDNS-Updater als
|
||||
> `/system script` + DHCP-Trigger auf `crs326-core` lebt und in **keinem** Template steht — ein
|
||||
> Recovery von null hätte ihn nicht. Das ist eine **Klasse**, kein Einzelfund. Christophs Frage
|
||||
> daraus: *„wäre ansible in der lage, nautobot vollständig als ssot abzulösen. 1 tool für
|
||||
> doku/deployment und nicht mehrere … keine indiv-anpassungen falls einfach vermeidbar."*
|
||||
|
||||
#### Teil 1 — Die Lücke (live geprüft 2026-10-05, `crs326-core`)
|
||||
|
||||
| Objekt | In einem Template? |
|
||||
|---|---|
|
||||
| `tp10-ddns-bf4`, DHCP-Client `client1` `script=` | ❌ |
|
||||
| `/system script environment cfDdnsToken` (**Secret**) | ❌ und darf nie (Secret-Gebot) |
|
||||
| 8 Alt-Skripte aus TP21.1 (`tp21-dhcp-restore`, `tp21-wan-wg-restore`, …) | ❌ |
|
||||
| `/system scheduler` | — (keiner vorhanden) |
|
||||
|
||||
🔴 **Nebenbefund:** `tp21-wan-wg-restore` trägt den **WG-Hub-Privkey im Klartext** im Skript-Körper.
|
||||
Das Skript liegt **nicht im Git** (nur auf dem Gerät), es ist also ein undokumentierter Recovery-Pfad
|
||||
mit einem Secret darin. **Aufnehmen, nicht sofort anfassen** (Christoph 2026-10-05).
|
||||
|
||||
**Warum unsichtbar:** GC prüft **29 ComplianceFeatures** — **keines** deckt `system script`,
|
||||
`system scheduler` oder `script environment`. Die Compliance-Zahl prüft nur, **was gerendert wird**;
|
||||
was nicht gerendert wird, ist per Definition nicht „drift".
|
||||
|
||||
#### Teil 2 — Die Evaluation
|
||||
|
||||
🔴 **Der Befund, der die Fragestellung verschiebt:** Nautobot ist **heute schon nur teilweise SSoT.**
|
||||
Live gezählt im Template: **datengetrieben** sind nur Device-Felder, `interfaces.all()`,
|
||||
`ip_addresses.all()` und zwei ConfigContexts (`dhcp_leases`, `firewall_filter`). **Literal im Jinja**
|
||||
stehen: 7 VLAN-Definitionen, 37 `bridge-port`- und 19 `bridge-vlan`-Zeilen, die Core-Firewall,
|
||||
NAT/Mangle/Queue/Routen. Der Ablöse-Vergleich lautet also nicht „Ansible vs. Nautobot-SSoT", sondern
|
||||
„Ansible vs. **Jinja-im-Repo + Gitea + ein paar Nautobot-Felder**".
|
||||
|
||||
| Rolle | Träger heute | Ansible-Äquivalent |
|
||||
|---|---|---|
|
||||
| Tunnel, WARP-Connector, Workers, Access-Apps, Hyperdrive, VPC, teamnet-Routen | Nautobot `nautobot.cloud`, Job **`CF Estate Sync`** (`jobs/cf_estate_sync.py`) | ✅ 4 `CloudNetwork` · 16 `CloudService` live |
|
||||
| **Ingress-Origin-Ziele** | **nirgends** | 🔴 `_ingress()` speichert nur `ingress_hostnames`, **nicht** das Origin |
|
||||
| **DNS-Records** | **nirgends** | 🔴 nicht erfasst — live 24 Records / 8 proxied |
|
||||
| **Access-Policies** | **nirgends** | 🔴 nur die Apps selbst |
|
||||
| Richtung | CF → Nautobot | 🔴 **Pull-only** („CF bleibt operativer Master") |
|
||||
| `docs/cf-tunnel-mapping.md` | Repo | §1–16 eingefrorene As-Built-Historie · §0 = Live-Zeiger (altert: nennt `bf-public` „2 conns", live **12** = 3 AP-Connectoren × 4) |
|
||||
| TP-Index/Status | **Gitea** (seit TP36 — nicht Nautobot) | ❌ bleibt Gitea |
|
||||
| Live-Gerätedaten (Compliance) | Nautobot DCIM + GC | ⚠️ Facts + `--check` |
|
||||
| Soll-Config | GC-Templates (Jinja) | ✅ Templates/Inventar |
|
||||
| Ist-Config | GC-Backup | ⚠️ `netcommon` backup |
|
||||
| Doku/Inventar (133 Interfaces, 38 IPs, 54 Leases) | Nautobot DB | ❓ offen — Ansible hat keine DB |
|
||||
|
||||
**Antwort auf „full setup/recovery from scratch": heute nicht möglich.** Es fehlt (a) der Push-Weg,
|
||||
(b) Ingress-Origins, (c) DNS, (d) Access-Policies. Und ein frisch erzeugter Tunnel liefert **neue ID
|
||||
und neues Secret** → das **Token-Handoff an die Connectoren** (ap1/ap2/ap3) ist **Teil der Recovery**,
|
||||
kein Nebenschritt.
|
||||
**Offene Punkte:** (1) was leistet Nautobot *unersetzlich* (Compliance-Historie, ConfigPlan-Audit,
|
||||
GitRepository-Sync, Secret-Maskierung)? (2) Kann Ansible Inventar+Soll+Drift aus *einer* Git-Quelle
|
||||
bedienen? (3) Migrationskosten: 29 Features + 7 `config_replace`-Regeln nachbauen? (4) **Wo bleibt
|
||||
Doku**, wenn Ansible nur deployt? (5) Werden die heutigen Sonderfälle (Literal-Blöcke wegen bewusster
|
||||
Schiefstände wie `ether2` in VLAN1) mit Ansible sauberer oder chaotischer?
|
||||
|
||||
#### Umfang
|
||||
Tunnel + Ingress **inkl. Origin-Ziele** + DNS-Records + Access-Apps/Policies vollständig nach Nautobot;
|
||||
**Push-Renderer** (Dry-Run + Diff); **Recovery-Runbook** für den Aufbau von null.
|
||||
|
||||
🔴 **Vor dem ersten Schritt prüfen, nicht annehmen:** welches Nautobot-Objekt DNS-Records tragen kann
|
||||
(Kern-`ipam` vs. eigenes Modell), und ob das Ingress-Origin als `extra_config`-Feld reicht oder ein
|
||||
eigenes Modell braucht.
|
||||
|
||||
- **Abh.:** TP44 Etappe 1/2 ✅ (native `nautobot.cloud`-Modelle + Pull-Job stehen). **Abgrenzung:**
|
||||
Tunnel-**GC/Namensgebung** läuft in TP45 Schicht D; **CF-Writes** aus Nautobot sind hier *das Ziel*,
|
||||
in TP37 ausdrücklich ausgeschlossen — die Grenze verschiebt sich also bewusst mit diesem TP.
|
||||
**Abgrenzung:** nicht TP21/TP22 (bauen GC auf), nicht TP46 (CF-SSoT). Berührt die Governance-Regel
|
||||
„Config nur über Nautobot" — bei Trägerwechsel neu zu fassen. Verwandt: TP30 (der WG-Privkey ist ein
|
||||
Rotations-Kandidat). **Ergebnis = Empfehlung mit Zahlen, keine Migration.**
|
||||
|
||||
---
|
||||
|
||||
@@ -2531,7 +2342,7 @@ Kurzorientierung, wo was zu finden ist:
|
||||
| **Discovery / Media** | [TP7](teilprojekte-archiv.md#tp7) mDNS · [TP7.1](teilprojekte-archiv.md#tp7-1) SSDP-Relay · [TP7.2](teilprojekte-archiv.md#tp7-2) HA↔Sonos |
|
||||
| **Hosts / Migration** | [TP14](teilprojekte-archiv.md#tp14) (+[.1](teilprojekte-archiv.md#tp14-1)/[.2](teilprojekte-archiv.md#tp14-2)/[.2a](teilprojekte-archiv.md#tp14-2a)/[.3](teilprojekte-archiv.md#tp14-3)/[.5](teilprojekte-archiv.md#tp14-5)) VLAN20-Cutover · [TP17](teilprojekte-archiv.md#tp17) Shelly→VLAN30 · [TP19](teilprojekte-archiv.md#tp19) VLAN1-Retirement |
|
||||
| **Control-Plane** | [TP20](teilprojekte-archiv.md#tp20) Platform-Postgres · [TP21](teilprojekte-archiv.md#tp21) Nautobot GC · [TP22](teilprojekte-archiv.md#tp22) Adoption + Compliance-Push · [TP27](teilprojekte-archiv.md#tp27) RouterBOOT 7.24 · [TP-R](teilprojekte-archiv.md#tp-r) Capabilities-Sweep · [TP44](teilprojekte-archiv.md#tp44) CF-Estate Doku-SSoT |
|
||||
| **Zugang** | [TP10](teilprojekte-archiv.md#tp10) Multi-Site-VPN *(offen nur der DDNS-Updater, s. Follow-ups)* · [TP16.1](teilprojekte-archiv.md#tp16-1) natives CF-Mesh |
|
||||
| **Zugang** | [TP10](teilprojekte-archiv.md#tp10-ddns) Multi-Site-VPN *(✅ abgeschlossen 2026-10-05 — DDNS-Updater live + Mesh-Quell-NAT)* · [TP16.1](teilprojekte-archiv.md#tp16-1) natives CF-Mesh |
|
||||
| **Verworfen** | [TP9](teilprojekte-archiv.md#tp9) Gateway-HA (User-Entscheid 2026-08-08) |
|
||||
|
||||
## Reihenfolge / Abhängigkeiten
|
||||
@@ -2693,7 +2504,7 @@ TP22 · TP-R · **TP3.2** (08-17) · **TP5** (08-18) · **TP7.1** (08-18) · **T
|
||||
</details>
|
||||
|
||||
|
||||
### TP43 — dw-Standort: interne BF-DNS-Namen über WG auflösbar machen ▸ ✅ **UMGESETZT 2026-09-11** — dw-FritzBox: Upstream `.36`+`.37`, DNS-Rebind-Ausnahme `bf.emsenhuber.net`, Fallback-Haken „öffentliche NS bei Unreachbarkeit" **gesetzt** (Ideal-Variante: `.37`-Redundanz bleibt, BF-down → public). (verworfene 420-Historie am Ende dieses Abschnitts)
|
||||
### TP43 · Gitea #32 — dw-Standort: interne BF-DNS-Namen über WG auflösbar machen — dw-FritzBox: Upstream `.36`+`.37`, DNS-Rebind-Ausnahme `bf.emsenhuber.net`, Fallback-Haken „öffentliche NS bei Unreachbarkeit" **gesetzt** (Ideal-Variante: `.37`-Redundanz bleibt, BF-down → public). (verworfene 420-Historie am Ende dieses Abschnitts)
|
||||
|
||||
**Live-Auslöser (2026-09-11):** Christoph am dw-Standort, WG-Tunnel zu BF up, kann `portainer.bf.emsenhuber.net`
|
||||
nicht erreichen. Direkte IP `https://10.40.20.126:9443` geht ⇒ **reines DNS-, kein Routing-Problem.** Der
|
||||
@@ -2766,3 +2577,109 @@ uns verfranzen. Geli kommt rein, das zählt."* — Diese Prämisse fiel weg: `42
|
||||
public CF-Worker hinter CF Access (löst auf CF-Anycast), kein BF-Origin ⇒ dafür braucht es kein
|
||||
Split-Horizon; Zugang via TP41-**WARP** gelöst (Geli 2026-09-10 via Android). Der Override-Bedarf für
|
||||
**interne** Namen blieb dabei übersehen — deshalb reopened, s.o.
|
||||
|
||||
<a id="tp49"></a>
|
||||
### TP49 · Gitea #76 — CF-Access: `login_method:github` im include der Terpenfinder-Policy — ✅ behoben (require statt include)
|
||||
|
||||
*(Angelegt 2026-10-06. Fund vom 2026-07-10 (Memory `project_cf_tunnel_1033`).
|
||||
**Am 2026-10-06 zunächst fälschlich als Phantom geschlossen, nach einem echten Test wieder
|
||||
geöffnet und behoben.** Die Irrfahrt steht unten unter „Aktenhistorie" — sie ist lehrreich
|
||||
genug, um sie zu behalten.)*
|
||||
|
||||
#### Befund (verifiziert, 2026-10-06)
|
||||
|
||||
Policy `christoph` auf App `420.teilzeitladen.xyz` (Terpenfinder v3, `e6a7991f…`, **öffentlich**)
|
||||
trug `login_method:github` im **`include`**. `include` ist ein **OR** — die Zeile „email
|
||||
christoph@emsenhuber.com" daneben war eine **Alternative**, keine Bedingung. Damit kam
|
||||
**jeder GitHub-Account** hinein, unabhängig von seiner E-Mail.
|
||||
|
||||
**Belegt durch echten Test (Christoph, 2026-10-06):** ein **zweiter GitHub-Account** mit fremder
|
||||
Adresse kam durch. Vorher war mit `testus@emsenhuber.com` über den **Google**-IdP getestet worden
|
||||
— und **abgelehnt**; daraus war falsch geschlossen worden, GitHub verhalte sich genauso.
|
||||
|
||||
#### Fix (ausgeführt 2026-10-06)
|
||||
|
||||
`login_method:github` von `include` nach **`require`** verschoben ⇒ **`email AND github`**.
|
||||
|
||||
| | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| `include` | email · **github** · onetimepin | email · onetimepin |
|
||||
| `require` | — | **github** |
|
||||
|
||||
**Zurückgelesen** über **beide** Endpunkte (App-Pfad und reusable-Pfad) — identisch, Fix sitzt.
|
||||
Fremder GitHub-Account fällt durch (hat die Adresse nicht); Christop's GitHub-Account hat sie als
|
||||
primäre Adresse und kommt durch. `onetimepin` bleibt als Include — **auf GitHub-Login-Seite ist
|
||||
der Einmalcode allein aber nicht mehr ausreichend** (`require` verlangt beides).
|
||||
|
||||
**Sweep über alle Apps:** `login_method` in `include`/`require`/`exclude` — **genau zwei Treffer,
|
||||
beide in `Terpenfinder v3`** (`christoph`: github+onetimepin, `geli`: nur onetimepin=harmlos).
|
||||
Der interne Wildcard und alle anderen Apps: **kein `login_method` mehr** (TP41). Der Sweep prüfte
|
||||
**alle drei** Regel-Arrays, nicht nur `include` — sonst wäre der Fix hinterher als neuer Treffer
|
||||
aufgetaucht.
|
||||
|
||||
**Scope-Warnung geprüft:** die Policy `christoph` ist `reusable: true`. Vor dem Schreiben wurde
|
||||
verifiziert, dass sie **nur an diese eine App gebunden** ist ⇒ Fix bleibt lokal, kein Kollateral.
|
||||
|
||||
#### ⚠️ Was `login_method` NICHT kann (Christophs eigentliche Absicht)
|
||||
|
||||
Die Absicht war, die Berechtigung **an den GitHub-Username** zu koppeln. **Das gibt Access nicht her:**
|
||||
|
||||
- **`login_method` prüft die Methode, nicht den Nutzer.** „hat sich via GitHub angemeldet" — kein
|
||||
Username, keine Org-Zugehörigkeit.
|
||||
- **Es gibt keinen GitHub-Username-Selector** im Selector-Katalog der Doku.
|
||||
- Der **einzige** GitHub-bezogene Identitäts-Selector ist **„Identity provider group"** — der zieht
|
||||
aus **SCIM-Provisionierung**. Am hiesigen GitHub-IdP ist `scim_config.enabled = false`;
|
||||
`GET …/identity_providers/{id}/scim/groups` → **`500 (12158)`**. Der Weg ist daher **zu**.
|
||||
(Zusätzlich ist der Selector für Infrastructure-Apps laut Doku auf *GitHub **organization*** begrenzt.)
|
||||
- **Konsequenz:** `email AND github` ist keine Notlösung, sondern die **vollständige** Umsetzung
|
||||
dessen, was Access hier hergibt. Eine echte Username-/Org-Kopplung bräuchte **SCIM aktiviert**
|
||||
(Org-basiert, kostenpflichtig) — **eigener TP, nicht Teil von TP49.**
|
||||
|
||||
#### Gotchas (live bestätigt — alle drei neu am 2026-10-06)
|
||||
|
||||
- 🔴 **Reusable Policies lassen sich NICHT über den App-Pfad ändern.**
|
||||
`PUT /accounts/{id}/access/apps/{app}/policies/{pol}` → **`12130 can not update reusable policies
|
||||
through this endpoint`**. Korrekt ist **`PUT /accounts/{id}/access/policies/{policy_id}`**
|
||||
(eigener Endpunkt für reusable Policies). Lesen geht über beide Pfade.
|
||||
⚠️ Der bisherige Merksatz „Reusable Policy nur per App-PUT detachbar" beschreibt eine andere
|
||||
Operation (Detach vom App-`policies`-Array) — **nicht** das Ändern ihres Inhalts.
|
||||
- 🔴 **Der `cloudflare`-MCP ist strikt read-only.** Write-Probe 2026-10-06: no-op-PUT
|
||||
(byte-identischer Body) → **`1010: undefined`**. Writes nur via `CF_API_TOKEN`/curl.
|
||||
- 🔴 **Access-App-IDs sind 32 Hex** — die Kurzform `16aaec78` liefert `12045 invalid_application_id`.
|
||||
Volle ID zuerst per `GET /accounts/{id}/access/apps` holen.
|
||||
|
||||
#### Aktenhistorie (dokumentierter Fehlschlag — nicht löschen)
|
||||
|
||||
Die erste Korrektur dieses Abschnitts (2026-10-06, vormittags) erklärte TP49 zum **Phantom** und
|
||||
schloss es — auf Basis eines **Google**-Tests plus eines **Schlusses** auf GitHub. Zwei Fehler:
|
||||
|
||||
1. **Lese-Schluss als Befund verkauft.** Aus „`include` ist ein OR" + „Google bindet die Adresse"
|
||||
wurde „also bindet GitHub auch" — nie gemessen. Das stand als Tatsache in Akte und Issue.
|
||||
2. **Der Gegenbeweis war keiner.** Der Google-Test zeigt nur, was der Google-IdP liefert —
|
||||
nicht, was GitHub liefert. Die IdP-Configs sind konfigurationsgleich; das Feld, in dem der
|
||||
Unterschied steckt, steht nicht in der Config.
|
||||
|
||||
**Lehre:** bei Aussagen über fremde Infrastruktur **pro IdP einzeln messen**, nicht den ersten
|
||||
Test auf den zweiten IdP übertragen. Der echte Test dauerte Minuten und hätte den Umweg erspart.
|
||||
Details in Memory `feedback_no_guessing`.
|
||||
|
||||
#### Abgrenzung
|
||||
**Nicht** TP41 (hat den internen Wildcard saniert), **nicht** TP44 (Dokumentation).
|
||||
⚠️ `420.teilzeitladen.xyz` gehört zum Projekt **blaueblüten** — Änderung erfolgte mit
|
||||
Christophs ausdrücklicher Freigabe, das Nachbar-Repo wurde **nicht** angefasst.
|
||||
|
||||
#### Abnahme (2026-10-06, durch echten Login — nicht nur API-Read)
|
||||
|
||||
Christoph hat nach dem Fix **alle Auth-Pfade neu getestet**:
|
||||
|
||||
- ✅ **Fremder GitHub-Account (2. Konto) → wird abgewiesen.** Der Fix greift real, nicht nur in der API.
|
||||
- ✅ **Eigener Zugang (GitHub, primäre Adresse `christoph@emsenhuber.com`) → funktioniert.**
|
||||
- ✅ **Geli → funktioniert** (unverändert über Einmalcode/IP; ihr `require` blieb leer).
|
||||
- ✅ **Alle übrigen Auth-Wege verhalten sich wie erwartet.**
|
||||
|
||||
Damit ist `email AND github` **verhaltensbestätigt**: es trennt das eigene Konto vom fremden,
|
||||
ohne jemanden auszusperren. Kein Regress an den Nachbar-Policies (`geli`, `terpene-svc`) und
|
||||
kein Kollateral am internen Wildcard.
|
||||
|
||||
**→ Abgeschlossen 2026-10-06: Fix gesetzt, zurückgelesen und per Login verifiziert.
|
||||
Gitea #76 `closed` / `status:done`.**
|
||||
|
||||
78
docs/tp49-cf-access-startauftrag.md
Normal file
78
docs/tp49-cf-access-startauftrag.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Startauftrag — TP49: CF-Access `login_method`-Include ✅ ERLEDIGT (2026-10-06)
|
||||
|
||||
> **Abgeschlossen.** Gitea #76, Prosa in `docs/teilprojekte.md` → `### TP49`.
|
||||
>
|
||||
> 🔴 **Diese Datei nicht mehr als Auftrag lesen — der ist ausgeführt.** Sie bleibt als Nachweis
|
||||
> stehen: Lage, Vorgehen und die Fehlschläge auf dem Weg dorthin.
|
||||
|
||||
## Der Auftrag (erledigt)
|
||||
|
||||
`login_method:github` aus dem `include` der Policy `christoph` der CF-Access-App
|
||||
`420.teilzeitladen.xyz` entfernen — nach Rücksprache, wen der Zugang treffen soll.
|
||||
|
||||
## Ausgeführt (2026-10-06)
|
||||
|
||||
`github` von `include` nach **`require`** verschoben ⇒ **`email AND github`**.
|
||||
|
||||
| | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| `include` | email · **github** · onetimepin | email · onetimepin |
|
||||
| `require` | — | **github** |
|
||||
|
||||
Gesetzt über **`PUT /accounts/{id}/access/policies/{policy_id}`**, zurückgelesen über **beide**
|
||||
Endpunkte (App-Pfad + reusable-Pfad) — identisch. Der `success:true` der API wurde **nicht**
|
||||
geglaubt, sondern die Policy neu gelesen.
|
||||
|
||||
**App-ID** `e6a7991f-466c-4d1d-bac6-4b30fafccce4` · **Policy (reusable)** `8e44f5d1-8257-443e-a797-b3de939952ee`
|
||||
**github-IdP** `72c1291b…` · **onetimepin** `9024c7a6…`
|
||||
|
||||
**Sweep über alle Apps** (`login_method` in include/require/exclude): genau zwei Treffer, beide in
|
||||
`Terpenfinder v3` — `christoph` (github+onetimepin) und `geli` (nur onetimepin, harmlos).
|
||||
Interne Apps/Wildcard: sauber (TP41).
|
||||
|
||||
## 🔴 Gotchas — live bestätigt, alle drei neu am 2026-10-06
|
||||
|
||||
1. **Reusable Policies ändern geht NICHT über den App-Pfad.**
|
||||
`PUT …/access/apps/{app}/policies/{pol}` → **`12130 can not update reusable policies through
|
||||
this endpoint`**. Korrekt: **`PUT …/access/policies/{policy_id}`**.
|
||||
⚠️ Der ältere Merksatz „Reusable Policy nur per App-PUT detachbar" meint das **Detach** vom
|
||||
App-`policies`-Array — **nicht** das Ändern des Inhalts. Zwei verschiedene Operationen.
|
||||
2. **Der `cloudflare`-MCP ist strikt read-only.** Write-Probe: no-op-PUT (byte-identischer Body)
|
||||
→ **`1010: undefined`**. Writes nur via `CF_API_TOKEN`/curl
|
||||
(`cd mcp/cloudflare && set -a; . ./.env; set +a`). Token **nie** ausgeben.
|
||||
3. **Access-App-IDs sind 32 Hex** — Kurzform `16aaec78` → `12045 invalid_application_id`.
|
||||
|
||||
## ⚠️ Was `login_method` nicht kann
|
||||
|
||||
Die ursprüngliche Absicht war eine Kopplung **an den GitHub-Username**. Das gibt Access nicht her:
|
||||
`login_method` prüft die **Methode**, nicht den Nutzer; einen Username-Selector gibt es nicht.
|
||||
Der einzige GitHub-bezogene Identitäts-Selector ist **„Identity provider group"** — der braucht
|
||||
**SCIM**, und am hiesigen GitHub-IdP ist `scim_config.enabled = false`
|
||||
(`GET …/scim/groups` → `500 (12158)`). **Weg zu.**
|
||||
⇒ `email AND github` ist die vollständige Umsetzung des hier Möglichen, keine Notlösung.
|
||||
Eine echte Org-Kopplung bräuchte SCIM aktiviert — **eigener TP**.
|
||||
|
||||
## Aktenhistorie — der Umweg (nicht löschen)
|
||||
|
||||
Zwischenzeitlich wurde TP49 als **Phantom geschlossen** — auf Basis eines **Google**-Tests plus
|
||||
eines **Schlusses** auf GitHub. Beides falsch:
|
||||
|
||||
1. **Lese-Schluss als Befund verkauft.** „`include` ist ein OR" + „Google bindet die Adresse"
|
||||
⇒ „GitHub bindet auch". Nie gemessen, aber als Tatsache in Akte und Issue geschrieben.
|
||||
2. **Der Gegenbeweis war keiner.** Der Google-Test zeigt nur Googles Verhalten. Die IdP-Configs
|
||||
sind konfigurationsgleich — das unterscheidende Feld steht **nicht** in der Config.
|
||||
|
||||
Christoph hat mit einem **zweiten GitHub-Account** getestet: er kam durch. Damit war der
|
||||
Ursprungsfund bestätigt und der Fix nötig.
|
||||
|
||||
**Lehre: pro IdP einzeln messen, nicht den ersten Test auf den zweiten übertragen.**
|
||||
|
||||
## Verifikation
|
||||
|
||||
- [x] Policy über beide Endpunkte zurückgelesen — `require` trägt github, `include` nicht mehr
|
||||
- [x] Sweep: keine weiteren `login_method` in `include` außer den bekannten
|
||||
- [x] Scope: reusable Policy nur an diese eine App gebunden (kein Kollateral)
|
||||
- [x] **Verhaltensabnahme (2026-10-06, echter Login):** fremder GitHub-Account → **abgewiesen**;
|
||||
eigener Zugang + Geli → funktionieren; alle übrigen Auth-Pfade wie erwartet
|
||||
|
||||
**Status: abgeschlossen.** Gitea #76 `closed` / `status:done`. Kein offener Punkt.
|
||||
123
docs/warp-egress-native-crs326.md
Normal file
123
docs/warp-egress-native-crs326.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# WARP-Egress — nativer Consumer-WARP (WireGuard) auf dem crs326-core
|
||||
|
||||
> **Status:** ❌ **VERWORFEN 2026-09-26** — Cloudflare blockt inoffizielle Consumer-WARP-WireGuard-Clients.
|
||||
> Live gebaut, verifiziert, dann vollständig zurückgebaut. Key + Backup bleiben zur Nachvollziehbarkeit liegen.
|
||||
>
|
||||
> ➡️ **Nachtrag §6 (selber Tag, zweiter Durchgang):** Der Mesh-Tunnel `cf-mesh` **trägt** als
|
||||
> ziel-basierter Ausgang **und erreicht den Origin mit `200`** (Core kommt als `104.28.207.39`, `warp=on`,
|
||||
> `colo=VIE` heraus — dieselbe IP wie der WARP-Client). **Lösung ist dauerhaft** über Nautobot/Golden Config
|
||||
> aktiv (Template `nautobot/mikrotik_routeros.j2`, Abschnitt `[10b]`). Der frühere „Origin blockt CF-IPs"-
|
||||
> Befund war ein Messfehler — korrigiert in §6.3.
|
||||
|
||||
## 1. Kontext / Warum
|
||||
|
||||
- Cannaleo-Origin `164.90.191.7` (blaueblueten.de, DigitalOcean multi-tenant, ~400 Vhosts) blockt die Heim-IP
|
||||
`90.146.33.177` hart (leerer `403`, live 2026-09-25).
|
||||
- **Scope (User-Vorgabe 2026-09-25):** nicht der ganze Outbound, sondern **genau eine Ziel-IP**
|
||||
(`164.90.191.7/32` — blaueblueten.de apex + www, beide identisch). Nur **Privatbestellungen**, Traffic
|
||||
überschaubar gering.
|
||||
- **Terpenfinder wird NICHT über diesen Weg** in Betrieb genommen (eigene, saubere Lösung — siehe §5).
|
||||
|
||||
## 2. Was gebaut wurde (2026-09-26, live)
|
||||
|
||||
1. **Consumer-WARP-Key** per `wgcf` registriert (ViRb3 v2.3.0, Wegwerf-Konto via `api.cloudflareclient.com`).
|
||||
→ `mcp/cloudflare/.env` (`CF_WARP_CLIENT_PRIVKEY`, gitignored) + `~/.secrets/wgcf-client/` (account + profil).
|
||||
2. Auf `crs326-core` (WG-Zeilen via `sshpass -e`, Rest per MikroMCP/ssh):
|
||||
- `/interface wireguard add name=warpout mtu=1280 private-key=… comment="WARP Egress (privat)"`
|
||||
- Peer `warp-cf-pop`: public-key `bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=`,
|
||||
endpoint `engage.cloudflareclient.com:2408`, `allowed-address=0.0.0.0/0`, `persistent-keepalive=25s`
|
||||
- `/ip address add 172.16.0.2/32 interface=warpout`
|
||||
- `/routing table add name=warpout fib` + `/ip route add 0.0.0.0/0 gateway=warpout routing-table=warpout`
|
||||
- `/routing rule add dst-address=164.90.191.7/32 action=lookup-only-in-table table=warpout`
|
||||
- NAT `masquerade out-interface=warpout` + forward `accept out-interface=warpout`
|
||||
3. **Verifiziert:** Key korrekt (abgeleiteter Public-Key == Router-Public-Key
|
||||
`HTFM3gj3FyT0EjWkG+2LLmvtxaFz1kH/P1iDwM0HswI=`), Konto „Active" (`wgcf status`).
|
||||
|
||||
## 3. Befund: Handshake schlägt fehl
|
||||
|
||||
- Peer `warp-cf-pop`: `rx=0`, kein `last-handshake` — Cloudflare antwortet **nie** auf den WG-Handshake,
|
||||
egal welcher Endpoint (`162.159.192.1` / `.193.2` / `.193.5`).
|
||||
- **Ursache (recherchiert):** Cloudflare blockt **inoffizielle WireGuard-Clients** für Consumer-WARP. Der
|
||||
offizielle Client setzt ein spezielles Handshake-Feld (`reserved`/Client-ID, z. B. `b91981`), das RouterOS
|
||||
(und jedes plain-wgcf-Profil) nicht setzt; dazu Abuse-Prevention. Der Mesh-Connector `cf-mesh` läuft nur,
|
||||
weil das ein **anderes Produkt** ist (Zero Trust, nicht Consumer).
|
||||
- Quellen: [ViRb3/wgcf Issue #542](https://github.com/ViRb3/wgcf/issues/542) ·
|
||||
[#515](https://github.com/ViRb3/wgcf/issues/515) · [#158](https://github.com/ViRb3/wgcf/issues/158).
|
||||
|
||||
## 4. Abbau (2026-09-26)
|
||||
|
||||
- Alle 8 Objekte entfernt (Routing-Regel, Route, Tabelle, NAT, Firewall, Adresse, Peer, Interface). Core
|
||||
wieder im Ursprungszustand — **keine Drift**, Golden-Config-Compliance unverändert.
|
||||
- Backup `pre-warp-egress-20260925.backup` liegt auf dem Core (vor dem Versuch, TP22-Regel).
|
||||
- `CF_WARP_CLIENT_PRIVKEY` bleibt in `mcp/cloudflare/.env` (nutzlos, da Client geblockt; kann bei Bedarf entfernt werden).
|
||||
|
||||
## 5. Weg vorwärts
|
||||
|
||||
> ⚠️ **Durch §6 überholt (2026-09-26).** Die Grundannahme hier — „geteilte CF-IPs werden geblockt, WARP
|
||||
> liefert 403" — war **falsch** und ist in §6.3 korrigiert. WARP-Client und `cf-mesh`-Ausgang erreichen den
|
||||
> Origin beide mit `200`. Eine Dedicated Egress IP ist damit nicht nötig.
|
||||
|
||||
- **Bleibt gültig:** Consumer-WARP über natives RouterOS-WG ist auf absehbare Zeit blockiert (separates
|
||||
Produkt, betrifft nicht `cf-mesh`).
|
||||
|
||||
## 6. Nachtrag 2026-09-26: Mesh-Exit trägt und erreicht den Origin (200) — Lösung dauerhaft
|
||||
|
||||
**Frage:** Kann der bereits laufende `cf-mesh`-Tunnel (TP16.1) als *Ziel-basierter Ausgang* dienen, statt eines
|
||||
separaten Consumer-WARP? **Antwort: ja — und er erreicht den Origin mit `200`.** Die Lösung ist dauerhaft über
|
||||
Nautobot/Golden Config aktiv (Template-Abschnitt `[10b]`).
|
||||
|
||||
### 6.1 Der Origin-Weg ist jetzt vermessen (vorher nur angenommen)
|
||||
|
||||
`blaueblueten.de` geht **direkt** zum DO-Droplet, **kein Cloudflare im Pfad** (Zertifikat = Let's Encrypt
|
||||
`CN=blaueblueten.de` vom 11.08., **kein `cf-ray`**, kein `server`-Header, kein `/cdn-cgi/trace` — CF setzt
|
||||
`cf-ray` immer). Der 403 kommt also **vom Origin**, nicht vom Edge.
|
||||
|
||||
**Der Block ist rein quell-IP-basiert:** voller Safari-/Chrome-User-Agent ändert nichts, HTTP→HTTPS-Redirect
|
||||
(`302`) funktioniert, erst der HTTPS-Request antwortet `403` mit `content-length: 0`.
|
||||
|
||||
### 6.2 Mesh als Exit — funktioniert
|
||||
|
||||
Temporär gebaut (Tabelle `meshtest` + `0.0.0.0/0` via `cf-mesh` + Regel nur auf `1.1.1.1/32`), dann gemessen:
|
||||
|
||||
```
|
||||
/tool fetch url="http://1.1.1.1/cdn-cgi/trace" mode=http
|
||||
→ ip=104.28.207.39 warp=on gateway=on colo=VIE
|
||||
```
|
||||
|
||||
Der Core kommt also über den Mesh-Tunnel als **Cloudflare-IP** heraus (vorher `90.146.33.177`).
|
||||
⚠️ **Merkregel: für `cdn-cgi/trace` `mode=http` nehmen, nicht `mode=https`** — über HTTPS liefert RouterOS nur
|
||||
den 69-Byte-Datei-Header (`# … by RouterOS`, `# software id =`), die Nutzdaten fehlen. Das sieht wie ein
|
||||
leerer Download aus, ist aber nur der falsche Fetch-Modus.
|
||||
|
||||
### 6.3 KORREKTUR: der frühere „403 über Mesh" war ein Messfehler — der Weg trägt
|
||||
|
||||
Der erste Test hatte **nur** die Regel `1.1.1.1/32 → meshtest` gesetzt und dann das Ziel gemessen. Für das
|
||||
Ziel griff die Regel nicht → der Request lief über die **Heim-IP** (`90.146.33.177`) raus. Der gemeldete
|
||||
`403` war also **403 über die Heim-IP**, nicht über den Mesh-Ausgang.
|
||||
|
||||
Beweis (A/B in einem Durchgang, Regel `164.90.191.7/32 → egress-cannaleo`):
|
||||
|
||||
```
|
||||
Regel aktiv → https://blaueblueten.de/ → 200 (168 KiB)
|
||||
Regel deaktiv → https://blaueblueten.de/ → 403 (fällt auf Heim-IP zurück)
|
||||
```
|
||||
|
||||
Merke: `action=lookup-only-in-table` für Ziel A routet Ziel B **nicht** — nur messen, was tatsächlich
|
||||
umgeleitet ist. Dieselbe CF-IP (`104.28.207.39`) erreicht den Origin via WARP-Client und via `cf-mesh`.
|
||||
|
||||
### 6.4 Rückbau (vollständig)
|
||||
|
||||
Alle Objekte entfernt (Regel, Route, Tabelle, Testdateien). `/routing table print` = nur `main`,
|
||||
`/routing rule print` = leer, Default-Route wieder `90.146.33.1%vlan100`. `cf-mesh` unberührt und gesund
|
||||
(Handshake 4 s). Keine Drift.
|
||||
|
||||
### 6.5 Was daraus folgt
|
||||
|
||||
- **Der Core-Teil ist gelöst und wiederverwendbar:** `routing-table` + `0.0.0.0/0`-Route + `routing rule
|
||||
dst-address=<ziel>/32 action=lookup-only-in-table` + `masquerade out-interface=<wg-x>`. Nur **ein** Ziel
|
||||
betroffen, Rest unberührt. Genau so gebaut, verifiziert, zurückgebaut.
|
||||
- **Offen bleibt allein das Transportmittel.** CF scheidet aus (Ziel blockt CF). Der Rest des Vorschlags aus
|
||||
§5 steht: eigener Ausgang über einen **externen WireGuard-Host**. Ein DO-VPS ist dabei der naheliegendste
|
||||
Kandidat — der Miner fährt heute schon erfolgreich über eine DO-IP gegen genau diesen Origin.
|
||||
- 🔴 **Der Core hat heute keinen zweiten Ausgang.** Kein zweites WAN, kein zweites Gateway — jeder künftige
|
||||
Egress braucht erst ein Interface, das ihn trägt.
|
||||
@@ -926,6 +926,34 @@ def job_tp45_c_dns():
|
||||
return [(["crs326-core"], "\n".join(lines), "TP45-c-dns")]
|
||||
|
||||
|
||||
def job_tp10_mesh_nat():
|
||||
"""TP10 (2026-10-03): Mesh-WARP-Verkehr Richtung FM/DW-Spokes auf eine BF-IP NATen.
|
||||
|
||||
WARUM: die Spoke-FritzBoxen akzeptieren am WG-Peer nur Quell-IPs aus 10.40.0.0/16
|
||||
(AllowedIPs). WARP-Clients kommen ueber cf-mesh mit 100.96.0.0/12 -> die FritzBox
|
||||
verwirft den Verkehr (bewiesen: ping src=100.96.0.6 -> 100% Loss, src=10.40.20.1
|
||||
-> 0%). Eine src-nat auf dem wg-hub-Egress legt die Quelle in den AllowedIPs-Raum.
|
||||
|
||||
`action=src-nat` (NICHT masquerade): wg-hub hat keine Interface-IP, masquerade
|
||||
haette keine Quelle. src-nat auf eine feste BF-IP (10.40.10.1) haelt zudem das
|
||||
Connection-Tracking -- die Antwort an 10.40.10.1 wird im prerouting auf die
|
||||
Mesh-IP zurueckuebersetzt und via cf-mesh geroutet.
|
||||
|
||||
KEIN LOCKOUT-RISIKO: reine srcnat-Ergaenzung; der no-NAT-accept (dst 10.40.0.0/16)
|
||||
und der WAN-masquerade bleiben unberuehrt, die Regel matcht nur out-interface=wg-hub.
|
||||
"""
|
||||
cmt = "TP10: NAT mesh source to WG spokes (AllowedIPs)"
|
||||
find = f'find where comment="{cmt}"'
|
||||
add = (f'/ip firewall nat add action=src-nat chain=srcnat comment="{cmt}" '
|
||||
f'out-interface=wg-hub src-address=100.96.0.0/12 to-addresses=10.40.10.1')
|
||||
lines = [
|
||||
f':if ([:len [/ip firewall nat {find}]] = 0) do={{ {add} }} '
|
||||
f'else={{ :log info "tp10-mesh-nat: Regel existiert schon" }}',
|
||||
f':log info ("tp10-mesh-nat: regeln=" . [:tostr [:len [/ip firewall nat {find}]]])',
|
||||
]
|
||||
return [(["crs326-core"], "\n".join(lines), "TP10-mesh-nat")]
|
||||
|
||||
|
||||
JOBS = {
|
||||
"drift-cleanup": job_drift_cleanup,
|
||||
"mgmt-acl": job_mgmt_acl,
|
||||
@@ -938,6 +966,7 @@ JOBS = {
|
||||
"tp45-v30-leases": job_tp45_v30_leases,
|
||||
"tp45-v30-revert": job_tp45_v30_revert,
|
||||
"tp45-v30-restore": job_tp45_v30_restore,
|
||||
"tp10-mesh-nat": job_tp10_mesh_nat,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -359,6 +359,7 @@
|
||||
# [7] NAT
|
||||
/ip firewall nat add action=accept chain=srcnat comment="TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" dst-address=10.40.0.0/16
|
||||
/ip firewall nat add action=masquerade chain=srcnat comment="defconf: masquerade" ipsec-policy=out,none out-interface-list=WAN
|
||||
/ip firewall nat add action=src-nat chain=srcnat comment="TP10: NAT mesh source to WG spokes (AllowedIPs)" out-interface=wg-hub src-address=100.96.0.0/12 to-addresses=10.40.10.1
|
||||
|
||||
# [7b] FIREWALL FILTER (TP26, 2026-08-17). Kommt als DATEN aus
|
||||
# local_config_context_data.firewall_filter, nicht als Templatezeilen —
|
||||
@@ -396,6 +397,17 @@
|
||||
/ip address add address=100.96.0.6 comment="TP16.1 Mesh-Overlay" interface=cf-mesh network=100.96.0.6
|
||||
/ip route add comment="TP16.1 Mesh" disabled=no distance=1 dst-address=100.96.0.0/12 gateway=cf-mesh
|
||||
|
||||
# [10b] EGRESS (2026-09-26) — ziel-basierter Ausgang fuer 164.90.191.7/32 (cannaleo/DO-Origin).
|
||||
# Der Origin blockt die Heim-IP 90.146.33.177 hart (403), Cloudflare aber nicht: WARP-Client
|
||||
# und cf-mesh kommen beide durch (live gemessen, gleiche Ausgangs-IP 104.28.x). Genau EIN Ziel,
|
||||
# ein Ausgang — kein Default-Route-Eingriff, restlicher Traffic unberuehrt. Kein extra NAT noetig:
|
||||
# masquerade out-interface-list=WAN greift fuer cf-mesh NICHT, CF-NATet die LAN-Source im Mesh
|
||||
# selbst (verifiziert von macmini 10.40.20.167). Cave User: kein Zugriffsmuster, das Heim-IP und
|
||||
# diesen Ausgang gemeinsam gegen den Origin zeigt — der Ausgang ist ausschliesslich fuer cannaleo.
|
||||
/routing table add fib name=egress-cannaleo
|
||||
/ip route add comment="TP-EGRESS: cannaleo via cf-mesh" disabled=no distance=1 dst-address=0.0.0.0/0 gateway=cf-mesh routing-table=egress-cannaleo
|
||||
/routing rule add action=lookup-only-in-table comment="TP-EGRESS: cannaleo 164.90.191.7/32 via cf-mesh" disabled=no dst-address=164.90.191.7/32 table=egress-cannaleo
|
||||
|
||||
# [11] QoS — BEWUSST LEER (TP6 geschlossen 2026-08-23).
|
||||
#
|
||||
# Hier stand einen Tag lang eine CAKE-Queue. Sie ist entfernt, weil sie GEMESSEN
|
||||
|
||||
112
routeros/tp10-ddns-cf-updater.rsc
Normal file
112
routeros/tp10-ddns-cf-updater.rsc
Normal file
@@ -0,0 +1,112 @@
|
||||
# =============================================================================
|
||||
# TP10 — Cloudflare-DDNS-Updater fuer bf4.emsenhuber.net
|
||||
# =============================================================================
|
||||
# Zweck: der WG-Hub-Endpoint der Spokes (fm/dw) ist bf4.emsenhuber.net. Der
|
||||
# A-Record muss der Liwest-Public-IP des Core folgen. Heute manuell gepflegt.
|
||||
#
|
||||
# BASIS: bajodel/mikrotik-cloudflare-dns (MIT, Copyright (c) 2025 bajodel)
|
||||
# https://github.com/bajodel/mikrotik-cloudflare-dns
|
||||
# Aenderungen ggue. dem Original (Christoph-Entscheid 2026-10-03):
|
||||
# 1. IP kommt aus der DHCP-Lease (client1/vlan100), NICHT von Echo-Diensten.
|
||||
# Liwest ist die Quelle, kein Dritter; kein Cloudflare im Pfad.
|
||||
# 2. check-certificate=yes (Original-Default war false -> Token ungeprueft).
|
||||
# 3. Nur EIN Record (bf4), Konfiguration auf das Noetige reduziert.
|
||||
#
|
||||
# Token: liegt NICHT hier, sondern als globale Script-Variable `cfDdnsToken`.
|
||||
# Einmalig setzen per SSH (siehe docs/teilprojekte.md ### TP10).
|
||||
#
|
||||
# Installation: /import file=tp10-ddns-cf-updater.rsc
|
||||
# danach /system scheduler add ... (siehe unten)
|
||||
# =============================================================================
|
||||
|
||||
# ---- Konfiguration ----------------------------------------------------------
|
||||
:local DnsRcName "bf4.emsenhuber.net"
|
||||
:local DnsZoneID "1b5bc02fdf61b6b989afde543f3f4134" # CF_ZONE_ID aus mcp/cloudflare/.env
|
||||
:local DnsRcrdID "dba9d9ec3b261fdb314c0f83e4e62f0d"
|
||||
:local RecordTTL "120" # wie der bestehende Record
|
||||
:local RecordProxied "false" # WG-Endpoint: NICHT proxied
|
||||
:local VerboseLog false
|
||||
:local WanDhcpName "client1"
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Token aus /system script environment holen (NIE im Skriptkopf, NIE per :global).
|
||||
# Grund (2026-10-03): :global cfDdnsToken liest im Skript-Kontext "nothing", obwohl
|
||||
# /system script environment cfDdnsToken gesetzt ist. CLI-Scope und Skript-Scope sind
|
||||
# auf RouterOS 7.24 unterschiedlich. `/system script environment get` ist scope-unabhaengig.
|
||||
:do {
|
||||
:local tk ""
|
||||
:local found [/system script environment find name=cfDdnsToken]
|
||||
:if ([:len $found] > 0) do={
|
||||
:set tk [/system script environment get [find name=cfDdnsToken] value]
|
||||
}
|
||||
:if ([:typeof $tk] = "") do={
|
||||
:log error "[tp10-ddns] cfDdnsToken nicht gesetzt - Abbruch"
|
||||
:error "cfDdnsToken fehlt"
|
||||
}
|
||||
:global cfDdnsToken
|
||||
:set cfDdnsToken $tk
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] cfDdnsToken nicht lesbar - Abbruch"
|
||||
:error "cfDdnsToken fehlt"
|
||||
}
|
||||
|
||||
# ---- WAN-IPv4 aus der DHCP-Lease (Quelle: Liwest, kein Dritter) ------------
|
||||
:local CFWanIP4New ""
|
||||
:do {
|
||||
:local leaseAddr [/ip dhcp-client get [find name=$WanDhcpName] address]
|
||||
:if ([:typeof $leaseAddr] != "nothing") do={
|
||||
# "90.146.33.177/24" -> "90.146.33.177"
|
||||
:local slashPos [:find $leaseAddr "/"]
|
||||
:if ($slashPos >= 0) do={
|
||||
:set CFWanIP4New [:pick $leaseAddr 0 $slashPos]
|
||||
} else={
|
||||
:set CFWanIP4New $leaseAddr
|
||||
}
|
||||
}
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] DHCP-Lease nicht lesbar ($WanDhcpName)"
|
||||
}
|
||||
|
||||
:if ($CFWanIP4New = "") do={
|
||||
:log warning "[tp10-ddns] Keine WAN-IP aus der Lease, naechster Lauf"
|
||||
} else={
|
||||
|
||||
# ---- Guard: nur oeffentliche IPv4 zulassen --------------------------------
|
||||
# Faengt den Fall ab, dass DHCP Muell liefert (privat/CGNAT) -> kein Write.
|
||||
:local isPrivate false
|
||||
:if ([:pick $CFWanIP4New 0 3] = "10.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 8] = "192.168.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 7] = "172.16.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 8] = "100.64.0") do={ :set isPrivate true } # CGNAT 100.64/10
|
||||
:if ($isPrivate) do={
|
||||
:log error "[tp10-ddns] Lease liefert nicht-oeffentliche IP ($CFWanIP4New) - ABBRUCH, kein Write"
|
||||
} else={
|
||||
|
||||
# ---- Nur bei Abweichung schreiben (Idempotenz) --------------------------
|
||||
:global CFWanIP4Cur
|
||||
:if ($CFWanIP4New != $CFWanIP4Cur) do={
|
||||
:if ($VerboseLog) do={ :log info "[tp10-ddns] WAN-IP: $CFWanIP4Cur -> $CFWanIP4New" }
|
||||
|
||||
# Kommentar am Record: Zeit + Geraet, wie im Original
|
||||
:local ds [/system clock get date]
|
||||
:local ts [/system clock get time]
|
||||
:local datetime ([:pick $ds 0 4] . "." . [:pick $ds 5 7] . "." . [:pick $ds 8 10] . "-" . [:pick $ts 0 2] . ":" . [:pick $ts 3 5])
|
||||
:local CFComment ("$datetime by crs326-core")
|
||||
|
||||
:local url "https://api.cloudflare.com/client/v4/zones/$DnsZoneID/dns_records/$DnsRcrdID/"
|
||||
:do {
|
||||
:local res [/tool fetch http-method=put mode=https url=$url check-certificate=yes output=user as-value http-header-field="Authorization: Bearer $cfDdnsToken,Content-Type: application/json" http-data="{\"type\":\"A\",\"name\":\"$DnsRcName\",\"content\":\"$CFWanIP4New\",\"ttl\":$RecordTTL,\"proxied\":$RecordProxied,\"comment\":\"$CFComment\"}"]
|
||||
:if (($res->"status") = "finished") do={
|
||||
:log warning "[tp10-ddns] $DnsRcName -> $CFWanIP4New aktualisiert"
|
||||
:set CFWanIP4Cur $CFWanIP4New
|
||||
} else={
|
||||
:log error "[tp10-ddns] Update fehlgeschlagen (status != finished)"
|
||||
}
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] Exception beim Cloudflare-Update - naechster Lauf"
|
||||
}
|
||||
} else={
|
||||
:if ($VerboseLog) do={ :log info "[tp10-ddns] WAN-IP unveraendert ($CFWanIP4New)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user