diff --git a/CLAUDE.md b/CLAUDE.md index ac11ffc..bb3e5ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,12 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der ## Pflichtlektüre pro Sitzung (Reihenfolge) +0. 🔴 **TP45 = aktuell dringendster Posten (2026-09-21, Gitea #71):** interne `*.bf.emsenhuber.net` + lösen im Browser **nicht** auf (AGH wertet Rewrites im **Filter-Pfad** aus → für unfiltrierte + Clients übersprungen → Fallback auf den Core, der nur 10 von 26 Namen hält), und `ds-bf` ist + **öffentlich kaputt** (einziger Ingress-Eintrag, der Caddy umgeht → TP5-Drop). Reihenfolge: + VLAN20 ins TP32-Schema → VLAN30-Pool-Grenze → DNS/FQDN. Ausführlich: `### TP45`. + **`### TP46`** = CF-/Tunnel-SSoT + Recovery (eigene Baustelle). 1. **Status/Index = Gitea-Board** (seit TP36-Cutover 2026-09-07): [`admin/bf-infra` · Issues](https://gitea.emsenhuber.net/admin/bf-infra/issues) — TP = Issue, Status = Label `status:*`, Gruppierung = `thema:*`. Für den **Detail/Kontext** eines TP dann den `### TPnn`-Prosa-Abschnitt in **`docs/teilprojekte.md`** (aktiv) bzw. **`docs/teilprojekte-archiv.md`** lesen. *(Die frühere „Quick-Index-Block, 1 Read reicht"-Zeile ist überholt — Index/Status stehen nicht mehr in der Datei.)* 2. **`docs/handoff-*.md`** (neueste) — wo die letzte Session aufgehört hat. 3. Bei Live-Writes oder Inventur: **`docs/cf-tunnel-mapping.md`**, **`docs/teilprojekte-archiv.md`**. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index c72714f..ebc2e7f 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -2391,18 +2391,36 @@ haben: `100–109` netznah · **`110–119` DNS** · **`130–139` Reverse Proxy **Letztes Oktett je /24 — gilt in jedem VLAN, an jedem Standort:** -| Oktett | Rolle | Anker heute | -|---|---|---| -| `.1–.9` | Gateways / L3 | `.1` je VLAN ✅ · `99.1` core, `99.2` branch ✅ | -| `.10–.29` | physische Hosts (Storage, Hypervisor, APs) | NAS `20.10` ✅ · APs `99.11/.12` ✅ | -| `.30–.99` | virtuelle Core-Services, Zehnergruppen wie oben | DNS `.3x` · Ingress `.4x` · DBs `.5x` · Security `.6x` · Monitoring `.7x` | -| `.100–.199` | App-Services | | -| `.200–.249` | DHCP-Pool | | -| `.250–.254` | VIPs / Relays | | +| Oktett | Rolle | Ableitung aus TP32 | Anker | +|---|---|---|---| +| `.1–.9` | Gateways / L3 | Bestand | `.1` je VLAN ✅ · `99.1` core, `99.2` branch ✅ | +| `.10–.19` | physische Netzhardware im VLAN | APs/Switches, sobald sie im VLAN liegen | heute nur VLAN99: `99.11/.12/.13` ✅ · VLAN30: Nuki-Bridge, NSPanelPro, P750W, DTUBI | +| `.20–.29` | Core / netznah | Kreis 100–199, Stufe `10x` | mcast-relay (heute `10.21`/`40.21` — **kein VLAN20-Bein**) | +| `.30–.39` | DNS / Resolver | Stufe `11x` | AGH-Paar — **Soll** `.30`/`.31`, Ist `.36`/`.37` (bis Nautobot dort weg ist) | +| `.40–.49` | Ingress / Reverse-Proxy | Stufe `13x` | caddy `20.40` ✅ | +| `.50–.59` | Datenbanken | Kreis 200–299 | Soll `platform-pg .50`, `mariadb .51`, `influxdb2 .52`, `redis .53` | +| `.60–.69` | Security / Identity | Kreis 300–399 | Soll `vaultwarden .60` | +| `.70–.79` | Monitoring | Stufe `15x` | frei | +| `.80–.89` | Platform / Runtime | Kreis 400–499 | Soll `docker .80`, `coolify .81` | +| `.90–.99` | Broker / Funk | **Ordnungsgruppe 40** (s. u.) — *nicht* aus dem VMID-Kreis abgeleitet | Soll `mqtt5 .90`, `z2m25 .91` | +| `.100–.149` | **Server-Hosts** (physische Bleche) | `.10x` Reserve · `.11x` Storage · `.12x` Hypervisor · `.13x` Bleche/Workstation/Backup | Soll `nas .110`, `pve2 .120` (reserviert), `pve .121`, `macmini .130`; Ist `pbs 20.135` ✅ | +| `.150–.199` | App-Services / Gäste | Kreis 500–599 | `gitea 20.157` ✅, `pve-mcp .173` ✅, `shelly-mcp .180` ✅ | +| `.200–.249` | DHCP-Pool | Bestand | VLAN20 soll `.200–.249` (Ist `.100–.200`, `used=0`) | +| `.250–.254` | VIPs / Relays | Bestand | | 🔑 **Der Merk-Trick: die Zehnergruppe im Oktett spiegelt die der VMID.** `110 agh` → `.3x`, `130 caddy` → `.4x`. Wer eine Zahl kennt, kennt die Klasse der anderen. +⚠️ **Kein Rechenweg — eine Nachschlagetabelle.** Die Zeile `.90–.99` (Broker/Funk) ist die +**Ausnahme**: sie stammt nicht aus dem VMID-Kreis, sondern aus der Ordnungsgruppen-Tabelle unten +(Stufe 40). **Wo die beiden Achsen auseinanderlaufen, gilt die Gruppe, nicht die Ziffer** +(Christoph 2026-09-21). Dasselbe Muster bei `.100–.149`: die Server-Hosts werden über den +**Gerätetyp** einsortiert (Blech), nicht über eine VMID. + +⚠️ **Solange eine Alt-IP als Zweitadresse weiterläuft, ist sie reserviert** — `.10` (nas), +`.100` (pve), `.167` (macmini) dürfen bis zum Rückbau **nicht** neu vergeben werden. Deshalb +bekommt z. B. `z2m25` **nicht** `.167`, sondern `.91`. + #### Warum der Bestand anders aussieht — er war nicht falsch, nur auf einer anderen Achse **User 2026-08-25:** „zuerst war haos, welches mit 100 den nummernkreis für iot eröffnet hat. und die anderen … gehörten zu ha, daher 1xx. erst später habe ich erfahren, dass die nummer die @@ -2425,19 +2443,35 @@ alle Gäste mit `order=`, dann den Rest nach VMID** — real lief also `iob-mast | 10 | Core | `108 mcast-relay` | | 20 | Daten | `105 platform-pg` (`up=15`), `302 mariadb` (`up=15`), `106 influxdb2`, `107 redis` | | 30 | Platform | `400 docker` (`up=20`) | -| 40 | Broker / Funk | `102 mqtt5` (`up=10`), `112 z2m25`, `330 RaspberryMatic` | -| 50 / 51 | Anwendungen | `101 iob-master`, **`100 haos`**, `150 iob-exposed` | +| 40 | Broker / Funk | `102 mqtt5` (`up=10`), `112 z2m25` | +| 50 / 51 | Anwendungen | `101 iob-master`, **`100 haos`**, `150 iob-exposed`, `330 RaspberryMatic` | + +🔴 **Korrektur 2026-09-21 (Christoph):** die CCU gehörte **nie** in die Funk-Gruppe. „Funk" ist +`102`/`112` — die CCU läuft in der **Anwendungs-Gruppe**, in derselben wie `haos`. Live stand sie +noch auf `order=40`; der Geräte-Write ist Teil desselben Zuges (nicht nur diese Doku-Zeile — eine +Doku, die dem Gerät vorauseilt, ist genau der Drift, den TP32 vermeiden soll). 🎯 **Die wichtigste Erkenntnis des TP: VMID = Klassifikation, `startup: order=` = Abhängigkeitsreihenfolge.** Die Umnummerierung muss die Startreihenfolge gar nicht reparieren — das ist bereits erledigt, ohne eine einzige ID zu ändern. **Die Nummernkreise sind damit reine Ordnung und Lesbarkeit**, und der Bestand kann in Ruhe nachziehen. -- **Offen:** Migration des Bestands (Tabelle oben) — *am Ende der Reise, nicht jetzt*. Und die - Kollision **AGH will `.30`, dort sitzt Nautobot** (`.31` = nautobot-worker) ⇒ Vorschlag `.36` als - Interim, `.30` erst nach dem Nautobot-Umzug in den App-Bereich. **Fällt vor dem AGH-Neubau an.** +- **Offen:** Migration des Bestands (Tabelle oben) — **läuft jetzt an**, nicht mehr „am Ende der + Reise": siehe **TP45** (VLAN20 + VLAN30 ins Schema, danach DNS/FQDN). Die alte Kollision + **AGH will `.30`, dort sitzt Nautobot** (`.31` = nautobot-worker, `.32` = scheduler, `.34` = redis) + ist dort **Schritt 1**: Nautobot zieht in den App-Block (`20.154–.156`), **erst danach** greift + AGH `.30`/`.31`. Reihenfolge bewusst so — der GC-Pfad läuft über Nautobot, ein Umzug von AGH zuerst + würde die Werkzeugkette gegen sich selbst laufen lassen. - **Abh.:** keine. **Blockiert nichts**, aber TP31 vergibt bereits danach. +#### 🔎 Bestands-Fehler, die beim Umsetzen auffielen (2026-09-21, live gemessen) +Die frühere Fassung dieser Tabelle war an vier Stellen **nicht gemessen, sondern angenommen**: +`mcast-relay` liegt **nicht in VLAN20** (Beine `10.40.10.21` + `10.40.40.21`), der Anker der Zeile +`.20–.29` war damit falsch; `nautobot` sind **vier** Gäste (`.30`–`.32`, `.34`), nicht drei; und der +**DHCP-Pool in VLAN30 enthält ~25 Statics** (`.202–.249`) — die VLAN30-Nummerierung war ebenfalls +gewachsen, nicht geplant. Merkregel: **„Bestand bleibt, wo er ist" gilt nur, solange niemand behauptet, +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") @@ -2650,11 +2684,130 @@ Der HA-Recorder schreibt **unbegrenzt** — es ist keine `purge_keep_days`-Grenz die Pipeline ist stabil und **104/104 compliant**. TP37 vergrößert die Angriffsfläche dieser Pipeline. Deshalb Punkt 1–2 als eigener, abnehmbarer Schritt **mit Compliance-Gegenprobe danach**, nicht als Nebeneffekt von Punkt 3. -- **Status:** ⏳ **Punkt 3 (CF) ✅ via TP44; Punkt 1+2 (Substrat + Dienste) offen** — das ist die - fehlende Voraussetzung für TP34. Datenmodell **entschieden 2026-09-06 (native Modelle: - `virtualization.Cluster/VirtualMachine` + `ipam.Service`, keine App)**. Runbook liegt jetzt vor: - [`handoff-20260906-tp37-34-36-plan.md`](handoff-20260906-tp37-34-36-plan.md). Live-Ausgangslage: - VM 0 · Cluster 0 · `ipam.Service` 0 · CustomField 0 (2026-09-06 gegen die API gelesen). +- **Status:** ✅ **Punkt 1+2 (Substrat + Dienste) ERLEDIGT** — die frühere Zeile „offen" war Doku-Drift. + Live gegen die API gelesen **2026-09-21**: **28 VMs · 3 Cluster · 26 `ipam.Service` · 38 `ipam.IPAddress` + · 9 Devices · 5 CustomFields**. Beide Seeder sind also gelaufen (`tp37-substrat-seed.py`, + `tp37-dienste-seed.py` — die 26 Services sind genau die 26 Zeilen der Dienste-Liste). + Datenmodell **entschieden 2026-09-06 (native Modelle: `virtualization.Cluster/VirtualMachine` + + `ipam.Service`, keine App)**; Runbook [`handoff-20260906-tp37-34-36-plan.md`](handoff-20260906-tp37-34-36-plan.md). + **Offen bleibt Punkt 4** (Drift-Check gegen die CF-API) — und der Weg **Daten → gerenderte Config** + ist nicht mehr TP37, sondern **TP34/TP45 Schicht C**. + ⚠️ **Merkregel:** die Zeile stand seit dem 09-06 auf „offen", obwohl die Seeder längst gelaufen waren. + Ein Status, der nur in einer Datei lebt, altert still — der Abgleich gegen die API kostet eine Minute. + +--- + +### TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN aus Nautobot ▸ 🔜 **ANGELEGT 2026-09-21** (Gitea #71) +> **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, +> unsere infrastruktur vlan20 an das ausgedachte ip-schema anzupassen. danach dns/fqdn."* + +#### Befund (live, 2026-09-21) — **weder Caddy noch Cloudflare haben etwas verloren** +| # | Befund | Beleg | +|---|---|---| +| 1 | **CF ist gewollt geschrumpft** — 24 Records, 8 proxied; `bf-public` trägt 5 (`vw`,`hass`,`n8n`,`gitea`,`ds-bf`), `fm` 2 | CF-DNS-API | +| 2 | **Caddy hat nichts verloren** — 16 Sites, live **byte-gleich** zum Repo (`diff` identisch, 2977 B) | LXC 130 | +| 3 | 🔴 **Internes Split-Horizon tot** | s. u. | +| 4 | 🔴 **`ds-bf` öffentlich kaputt** | s. u. | + +🔴 **Root Cause #3 (bewiesen).** AGH wertet DNS-Rewrites **im Filter-Pfad** aus; für Clients mit +`filtering_enabled: false` werden sie **übersprungen**. Genau die Gruppen, von denen gebrowst wird, +sind unfiltriert (`dns-bypass-exempt` = beide Mac-NICs, `grp-infra`, `grp-media`, `grp-mgmt`). Diese +Clients fallen auf den conditional forward `[/bf.emsenhuber.net/] → 10.40.20.1` (Core) zurück — der +Core hält aber nur **10** Host-Records; die **11 Dienstnamen** (`nautobot`, `portainer`, `pbs`, `ccu`, +`iobroker`, `z2m25`, `tv`, `mfc`, `ha-mcp`, `gitea-mcp`, `n8n-mcp`) existieren **nur** als AGH-Rewrite +→ **NXDOMAIN**. Aus dem AGH-Container selbst (`127.0.0.1`, kein Client-Match) liefern dieselben +Rewrites korrekt — **deshalb war jede Handoff-Abnahme grün: sie wurde nie vom Browser aus gemessen.** +Zweitdefekt: `pve.bf`/`pve-mcp.bf` **doppelt** definiert (Core → Host-IP, AGH → Caddy `.40`). + +🔴 **Root Cause #4 (bewiesen, Firewall).** Der Origin antwortet aus VLAN20 in 0,08 s mit `200`, +öffentlich `000` nach 20 s — die vier anderen Namen desselben Tunnels sind in ~0,2 s da. `vw`/`hass`/ +`n8n`/`gitea` gehen auf `https://10.40.20.40:443` (**Caddy**); `ds-bf` geht **direkt** auf +`http://10.40.20.10:8080` (**NAS**) und ist damit der **einzige Ingress-Eintrag, der Caddy umgeht** → +die AP-Connectoren (VLAN99) dürfen per Regel `*42` **ausschließlich** Caddy:443, alles andere fällt +unter `*1A TP5-WOULD-DROP` / `*2D TP5: deny-by-default` (621 Pakete). +**Fix ohne Firewall-Änderung: `ds-bf` über Caddy führen.** + +#### Schichten (Reihenfolge = Abhängigkeit) +| Schicht | Inhalt | Stand | +|---|---|---| +| **A** | VLAN20 virtuelle Gäste ins Schema — **Nautobot zuerst** (`.30–.34` → `.154–.156`/`.158`), dann AGH | offen | +| **B** | VLAN30 — **nur die Pool-Grenze**, Geräte bleiben (Christoph 2026-09-21) | offen | +| **C** | DNS/FQDN: `ds-bf` über Caddy, `coolify.bf` neu, `.bf` vollständig auf den Core, AGH nur Filtering, TP34 | offen | +| **D** | Tunnel-Inventar + GC + generische Namen, TP30 #10, Leak-Ursachen | offen | +| **E** | nas/pve/macmini mit Doppeladresse — **eigene Session**, pve2 nur reservieren | offen | +| **F** | CF-/Tunnel-SSoT + Recovery → **TP46** | offen | + +🔴 **Warum Nautobot zuerst.** AGH will `.30`/`.31` — dort sitzt heute Nautobot (`.30`/`.31`/`.32`/`.34`). +**27 Skript-/Config-Dateien** referenzieren `10.40.20.30` (alle `gc-*.py`, 14× `ingest-*.py`, +`tp44-*`, `routeros/fetch-core-cutover-config.py`). Und: **der GC-Pfad läuft über Nautobot** — AGH +zuerst zu ziehen hieße, die Werkzeugkette gegen sich selbst laufen zu lassen. + +#### Soll-Oktette (Nachschlagetabelle, s. TP32 für die Herleitung) +| Oktett | Rolle | Soll | +|---|---|---| +| `.20–.29` | Core / netznah | mcast-relay | +| `.30–.39` | DNS | agh-bf1 `.30` · agh-bf2 `.31` | +| `.50–.59` | DBs | platform-pg `.50` · mariadb `.51` · influxdb2 `.52` · redis `.53` | +| `.60–.69` | Security | vaultwarden `.60` | +| `.80–.89` | Platform | docker `.80` · coolify `.81` | +| `.90–.99` | Broker/Funk | mqtt5 `.90` · z2m25 `.91` | +| `.150–.199` | App | haos `.150` · iob-master `.151` · CCU `.152` · nautobot `.154–.156`+`.158` · gitea `.157` · iob_slave `.159` | + +🔴 **DHCP-Reservierungen sind die Lücke:** Golden Config rendert sie **nirgends** (pools/DNS-statics/ +NAT/mangle/WG sind Template-Literale, der Firewall-Filter kommt als ConfigContext-**Daten** — aber +`/ip dhcp-server lease` existiert im Template nicht). Die 26 VLAN30- + 1 VLAN20-Reservierung sind +**Handarbeit auf dem Gerät** aus der ap1-Ära. Christoph-Entscheid 2026-09-21: **Renderer bauen** +(Template + Nautobot-Datenmodell), statt Handarbeit zu dokumentieren. + +#### Live-Befunde, die den ursprünglichen Plan widerlegten (2026-09-21) +- `mcast-relay` liegt **nicht in VLAN20** (Beine `10.40.10.21` + `10.40.40.21`) → der Anker der + Zeile `.20–.29` war falsch gesetzt. +- `nautobot` sind **vier** Gäste (`.30`–`.32`, `.34`), der Plan sah **drei** Slots vor. +- Die VLAN30-Soll-Spalte war **durchgehend erfunden**: Ender3V3KE liegt live auf `.150` (Plan `.180`), + die 26 Shelly-Reservierungen auf `.202–.226`+`.233` (Plan `.150–.169`), Twinkly `.228`, + Lichterketten `.236`/`.237`, Withings `.230`/`.231` … → Christoph: **„VLAN30 nur Pool-Grenze"**. +- Pool-Fund: `pool-v20` `.100–.200` hat **`used=0`** (gratis verschiebbar); `pool-v30` `.100–.250` + enthält **~25 Statics** → Pool und Statics tauschen praktisch die Plätze. +- ⚠️ **`ap3.bf` existiert nirgends** — weder Core-Static noch AGH-Rewrite, obwohl ap3 seit 08-30 live ist. + +- **Abh.:** TP32 (Systematik) · TP37 ✅ (Nautobot-Datenmodell) · TP21 ✅. **Blockiert:** TP34 (rendert + 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 | +|---|---|---| +| 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. --- @@ -2761,6 +2914,12 @@ den Weg, über den man den Fehler zurücknehmen wollte. ### Follow-ups nebenher (blockieren nichts) +- **🔴 TP45 — VLAN20/VLAN30 ins TP32-Schema + DNS/FQDN (NEU 2026-09-21, Gitea #71):** der aktuell + **dringendste** Posten — die internen `*.bf.emsenhuber.net`-Namen lösen im Browser nicht auf + (Ursache: AGH-Rewrites im Filter-Pfad für unfiltrierte Clients übersprungen), und `ds-bf` ist + öffentlich kaputt (einziger Ingress-Eintrag, der Caddy umgeht). Danach ist **TP34** erst baubar. +- **TP46 — CF-/Tunnel-SSoT + Recovery (NEU 2026-09-21, Gitea #72):** Push-Weg + Ingress-Origins + + DNS + Access-Policies nach Nautobot, Recovery-Runbook. Eigene Baustelle, unabhängig von TP45. - **TP25 — WLAN-Stabilisierung (NEU 2026-08-17):** Audit durch, **6 Funde** (2,4 GHz fährt 40 MHz statt der profilierten 20; ap2 sendet auf 5 GHz mit **9 statt 18 dBm**, weil ch149 unter ETSI leistungsbegrenzt ist; 80 MHz kostet Randabdeckung; unzulässiger Channel-Override auf `w2-iot`; keine `access-list`; 5-Minuten-Takt