TP31 + TP32 angelegt; Startreihenfolge auf pve repariert

TP31 -- Caddy als Reverse Proxy fuer innen UND aussen, cloudflared von
sechs Containern auf einen. Anlass war Christophs Einwand, er habe den
Core unter der Praemisse gekauft, dass auch die Public-Apps darueber
laufen. Aktenlage geprueft: eine Zusage gibt es nicht, aber TP16.1
begruendet die Auswahl mit seinem Prinzip 'Router = einziger SPOF' --
und niemand hat je geprueft, ob der oeffentliche Strang ohne sechs
Container auskommt. Keine Taeuschung, eine ungepruefte Annahme.

- dst-nat-Variante geprueft und verworfen (ginge, Free-Tarif reicht),
  weil Christophs Tunnel-Variante drei Risiken spart: keine angreifbare
  Origin-IP, keine CF-IP-Allowlist, kein DDNS als Voraussetzung
- cloudflared auf ap1/ap2: live gemessen arm64, 95 MB Flash frei,
  690 MB RAM frei. Der Core kann es nie (arm 32-bit, 2,9 MB frei)
- Kriterium fuer Infrastruktur-Container: was sich selten aendert und
  wenig schreibt. Caddy nein (Config aendert staendig), AGH nein
  (Query-Log frisst NAND)

TP32 -- VMID-Systematik (Hunderter = Klasse, gemeinsamer Raum fuer VMs
und LXCs) + letztes Oktett je /24, uebertragbar auf andere Standorte.
🔴 Dabei gefunden und SOFORT REPARIERT: die Startreihenfolge war falsch.
Proxmox startet erst Gaeste mit explizitem startup order=, dann den Rest
nach VMID -- drei Gaeste hatten einen, die DBs nicht. Home Assistant
startete damit vor MariaDB, MQTT und z2m25. Jetzt fuenf Stufen
(Core/Daten/Platform/Broker/Apps). Erkenntnis: VMID = Klassifikation,
startup order = Abhaengigkeit -- die Umnummerierung muss den Startlauf
gar nicht reparieren.

Doku-Korrekturen aus den Messungen:
- CLAUDE.md: 'Container NICHT auf dem Router' war nie nachgemessen
- tp16.1: 'Connector als Container ❌' galt dem TUN-Bedarf des
  WARP-Connectors; cloudflared braucht kein TUN
- cf-tunnel-mapping Paragraph 16: die macvlan-Blindheit war mit einem
  Shim behebbar -- der Workaround war groesser als der Fix

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 01:03:54 +02:00
parent ec8051508e
commit c9a413858b
4 changed files with 241 additions and 2 deletions

View File

@@ -75,7 +75,13 @@ ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)*
</details>
**Nächster Schritt (Stand 2026-08-23): Christoph entscheidet — der kritische Pfad ist LEER.**
**Nächster Schritt (Stand 2026-08-25): [TP31](#tp31) — Caddy als Reverse Proxy für innen und außen.**
> 🆕 **Neu am 2026-08-24/25:** [TP31](#tp31) löst **TP16** und **TP16.2** ab und dampft cloudflared
> von **sechs Containern auf einen** ein; [TP32](#tp32) gibt die Nummern- und Adress-Systematik vor
> (und hat nebenbei die **falsche Startreihenfolge auf pve repariert** — HA startete vor seiner
> Datenbank). **TP16 ✅, TP25 ✅ und TP16.3 (Pilot) ✅** sind am 24./25. durchgezogen worden.
**Vorherige Fassung (Stand 2026-08-23): Christoph entscheidet — der kritische Pfad ist LEER.**
> 🎯 **Das Projekt ist in seinem Kern fertig.** deny-by-default steht (TP5), Golden Config steht und
> meldet **104/104 compliant bei 0 Drifts** (live geprüft 2026-08-23), WLAN steht über CAPsMAN auf dem
> Core (TP28), VLAN1 ist leer (TP19) — und **TP6 ist beantwortet statt offengelassen**: CAKE gebaut,
@@ -1324,6 +1330,183 @@ unabhängig vom gebündelten Durchgang.**
**Blockiert nichts.** Die frühere Kopplung „erst TP17.1, dann rotieren" ist **gegenstandslos** —
TP17.1 ist am 2026-08-23 verworfen, die Geräte bleiben WLAN-Clients.
<a id="tp31"></a>
### TP31 — Reverse Proxy für innen und außen; cloudflared von sechs Knoten auf einen ▸ 🔜 **GEPLANT 2026-08-25**
> **Auslöser (User 2026-08-24):** „ich weigere mich, für public applications 2 cloudflare container zu
> installieren. … ich habe [den Core] unter der prämisse gekauft, dass über diesen dann auch die public
> apps laufen können. wir wollten doch den core als single point of failure platzieren."
#### Der Vorwurf, fair geprüft
Eine schriftliche Zusage, dass die Public-Apps auf den Core wandern, existiert **nicht** —
[TP16.1](teilprojekte-archiv.md#tp16-1) ist durchgehend auf den *privaten* Zugang zugeschnitten und hält
mehrfach fest, dass „der cloudflared-Public-Teil unberührt blieb". **Aber der Vorwurf trifft trotzdem
etwas Richtiges:** dasselbe Dokument begründet die gesamte Auswahl mit **Christophs** Prinzip —
*„Router = einziger SPOF, keine Abhängigkeit zu anderen Rechnern"* — und **niemand hat je geprüft, ob der
öffentliche Strang ohne sechs Container auskommt.** Keine Täuschung, aber eine **ungeprüfte Annahme**.
#### Die Lösung ist nicht „raus aus Cloudflare", sondern „ein Knoten statt sechs"
**Caddy** als Reverse Proxy für **innen und außen**, `emsenhuber.net` in beiden Richtungen, plus **ein**
cloudflared als Tunnel-Endpunkt.
- ⚖️ **Erst geprüft, dann verworfen: der Weg über `dst-nat`.** Der Core hält `90.146.33.177` direkt ohne
CGNAT, Liwest lässt eingehend alles durch (User: „hatte früher schon nginx hier laufen"), und
Origin Rules (Ziel-Port, **Free = 10 Regeln**), die CF-HTTPS-Ports (`443, 2053, 2083, 2087, 2096, 8443`)
sowie Authenticated Origin Pulls sind alle im Free-Tarif enthalten. Es **ginge** also ohne jeden Container.
- 🎯 **Christophs Einwand ist der bessere Weg:** *„caddy muss so oder so. nachdem dieser vorhanden ist,
fahren wir natürlich über zero trust apps. noch etwas sicherer, aber kein zusätzlicher knotenpunkt,
der ausfallen kann."* Der Tunnel spart drei Risiken auf einmal: **keine angreifbare Origin-IP, keine
CF-IP-Allowlist, kein DDNS als Voraussetzung** (der TP10-Updater bliebe sonst zwingend).
#### Zielbild
```
Internet ─► Cloudflare (Access, WAF) ─► Tunnel »bf-public« ─► cloudflared auf ap1/ap2
WARP ─► cf-mesh (nativ am Core, kein Container) ─┐
LAN ─► Split-Horizon-DNS ───────────────────────┴─► Caddy (LXC 130, 10.40.20.40)
└─► alle Dienste per Host-Header
```
**Öffentlich bleiben vier:** `vw` (Bitwarden-Clients), `hass` (Companion-App), `n8n` (Webhooks Dritter),
`gitea` (Access **nur für die Web-UI**, Git-/API-Pfade daran vorbei ⇒ **Push ohne WARP bleibt**).
Alles andere ist intern; **19 Ingress-Zeilen über drei Tunnel werden EINE Catch-all-Regel auf Caddy.**
#### Wo cloudflared läuft — und warum die APs (live gemessen 2026-08-24/25)
| Gerät | Arch | Flash frei | RAM frei | Container? |
|---|---|---|---|---|
| **ap1 / ap2** (hAP ax²) | **arm64** | **95 MB / 128 MB** | **690 MB / 1 GB** | ✅ ja |
| **crs326-core** | arm **32-bit** | **2,9 MB / 16 MB** | 512 MB | ❌ **nie** |
- ✅ **Christophs Argument, das den Ausschlag gibt:** *„cf auf den aps hat den vorteil, dass wir gegenüber
unseren hosts/container nicht blind sind."* — Der AP ist eine **eigene Maschine**, kein Docker-Host;
die macvlan/ipvlan-Blindheit kann dort **prinzipiell** nicht auftreten. Kein anderer Ort hat das.
💡 Daraus folgt eine Reserve: fällt Caddy aus, kann derselbe Connector über eine zweite Ingress-Regel
**direkt** auf ein, zwei kritische Backends zeigen.
- 🔴 **Ehrliche Einschränkung:** cloudflareds einziges Origin ist Caddy. Solange Caddy auf pve liegt,
kauft ein Connector auf dem AP **Hardware-Diversität, aber keine Verfügbarkeit** — fällt pve aus, ist
alles weg. Der echte Gewinn kommt erst mit `caddy-bf2` (LXC 131).
- **Caddy oder AGH zusätzlich auf die APs? Nein.** 95 MB minus RouterOS-Reserve fürs eigene `.npk`
(~20 MB) = **ein Container pro AP**. Kriterium, das auch künftig trägt: **auf die Infrastruktur darf,
was sich selten ändert und wenig schreibt** — cloudflared ja (Config liegt in Cloudflare, schreibt
nichts); **Caddy nein** (Caddyfile ändert sich bei jedem neuen Dienst); **AGH nein** (Query-Log
schreibt dauernd → NAND-Verschleiß).
- ⚠️ **Preis:** `container` verlangt eine **`device-mode`-Änderung mit physischer Bestätigung** am Gerät
(ap1 Technik UG, ap2 Technik OG). Und der AP bekommt eine zweite Rolle neben dem WLAN.
#### Umsetzung
1. **Leichen löschen:** `120 adguard-bf1`, `121 adguard-bf2`, `128 nginx-proxy-manager`, `129 caddy`,
`130 pihole1`, `131 pihole2` — alle `stopped`, alle auf **totem VLAN1** (`10.40.41.116/.117`,
GW `.41.254`), `onboot: 0`. ⚠️ `130`/`131` **müssen** weg, bevor die neuen Nummern vergeben werden.
**Alte Backups radieren** (Job `backup-6dcf5c10-610f`, So 01:00, Storage `pbs`, führt heute `120`
und `128`), **`130` neu aufnehmen — derselbe Job.**
2. **LXC 130 `caddy-bf1`** neu anlegen: VLAN20 `tag=20`, `10.40.20.40/24`, GW/DNS `10.40.20.1`,
**`onboot: 1`**, ~4 GB / 512 MB. *(Neu bauen statt umkonfigurieren — die Altlasten sind
Helper-Script-AdGuard-Container und würden auf Jahre eine falsche Identität führen.)*
3. **Caddy mit Cloudflare-DNS-Modul** → **DNS-01**, damit auch rein interne Namen echte Zertifikate
bekommen. 🔑 **Eigenes, eng geschnittenes Token** (`Zone:DNS:Edit` nur auf `emsenhuber.net`), **nicht**
das account-weite `CF_API_TOKEN` ⇒ neuer Posten für **TP30**.
4. **Split-Horizon**: Service-FQDNs → Caddy. 🔗 **Vorher entscheiden: Core (`/ip dns static`) oder
AdGuard?** Sobald AGH nach **TP23 Stufe 2** scharf ist, gehören die Einträge dorthin — sonst zweimal gebaut.
5. **Tunnel `bf-public`** + Connectoren auf ap1/ap2, CNAMEs umhängen (`n8n` → `gitea` → `hass` → `vw`).
6. **Abbau:** `bf-nas`/`bf-pve`/`bf-mm` löschen, sechs Container weg, SSH-Browser-Rendering entfällt,
**MCP-Clients auf FQDN umstellen** (`~/.claude.json` spricht heute `10.40.20.105:9583` und
`10.40.20.173:3000` roh; `n8n-mcp`/`gitea-mcp` kommen dabei wieder in Betrieb).
🎯 **Kernabnahme: WAN trennen, dann alle Dienste per FQDN.** Das ist die Autarkie, um die es geht.
⚠️ **Preis, offen benannt:** mit Split-Horizon greift **Access innerhalb des Netzes nicht mehr**; dort
tragen TP5-Segmentierung und die App-eigene Auth.
#### HA-Ausblick (nicht Teil der Umsetzung)
**Es bleibt bei Caddy** — zustandslos bis auf den Zertifikatsspeicher, identisches Caddyfile je Instanz,
versionierbar; NPM hätte eine Datenbank, die man mitreplizieren müsste.
🔴 **Prinzip für `vw`: der Eingang darf nicht klüger sein als die Datenbank.** [TP20.1](#tp20-1) sieht
**manuelles Promote** vor. Zwei Connectoren auf **verschiedene** Caddys an verschiedenen Standorten
würden die Hälfte der **Schreib**zugriffe auf einen read-only Standby schicken — das #1033-Muster,
diesmal mit Datenverlust statt 530ern. **Automatisches Ingress-Failover erst, wenn der DB-Teil es trägt**
(User: vertagt bis TP20.1 Phase 1+2 laufen).
- **Abh.:** [TP32](#tp32) (Nummern/IPs — vergibt `130`/`.40`), TP16.1 ✅ (Mesh), **TP23 Stufe 2**
(Split-Horizon-Ort). **Löst ab:** TP16 (reziprokes HA) und **TP16.2** (der `vw`-bypass-Strang bekommt
mit Caddy + CF davor einen anderen Schutz). **TP16.3 ist der Pilot dieses TP.**
- 📋 **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**
> **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."
>
> **Eigener TP, weil es eine Konvention ist, kein Bauteil** — TP31 ist ihr erster Kunde.
> **Regel: Bestand bleibt, wo er ist; jeder NEUE Gast folgt der Systematik.**
🔑 **Ein gemeinsamer VMID-Raum für VMs *und* LXCs.** `100 haos` und `330 RaspberryMatic` sind **VMs** —
die Klasse bestimmt die Nummer, nicht die Virtualisierungsart.
| Kreis | Klasse | heute dort (VM *kursiv*) |
|---|---|---|
| **100–199** | **Core / Infrastruktur** | `108 mcast-relay` |
| **200–299** | **Daten / DBs** (Precious-Tier) | *(`105`, `106`, `107`, `302` → später hierher)* |
| **300–399** | **Security / Identity** | — |
| **400–499** | **Platform / Runtime** | ✅ `400 docker` passt bereits |
| **500–599** | **App-Services / Smart Home** | `101`, `102`, `112`, `150`, *`100`*, *`330`* → später hierher |
| **600–699** | **Business / ERP** | `201 openproject` |
| **700–799** | **KI / ML** | — |
| **800–899** | **Dev / R&D / Test** | — |
| **900–999** | **Ephemeral / Scratch** | wird ungefragt gelöscht |
**Zehnerschritte innerhalb eines Kreises** („Respektabstand", User), damit Instanzen und Nachbarn Platz
haben: `100–109` netznah · **`110–119` DNS** · **`130–139` Reverse Proxy** · `150–159` Monitoring ·
`170–179` PKI/Zeit. ⇒ **AGH `110`, Caddy `130`.**
**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 | |
🔑 **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.
#### 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
startreihenfolge definiert."
⇒ Der Bestand gruppiert nach **Zugehörigkeit**, die neue Systematik nach **Klasse**. Beide Achsen sind
legitim; die zweite ist die, die das System auswertet. Der alte `1xx`-Block ist der **Smart-Home-Block**
und wandert später als Gruppe nach `5xx`.
#### ✅ ERLEDIGT 2026-08-25: die Startreihenfolge war nachweislich falsch
🔴 **Und der Hebel ist NICHT die VMID.** Drei Gäste hatten ein explizites `startup: order=`
(`101 iob-master` 110, `102 mqtt5` 120, `100 haos` 125), alle übrigen keines. **Proxmox startet erst
alle Gäste mit `order=`, dann den Rest nach VMID** — real lief also `iob-master → mqtt5 → haos` und
**erst danach** `platform-pg`, `mariadb`, `z2m25`, `docker`.
⇒ **Home Assistant startete vor seiner eigenen Datenbank.**
**Repariert (wirkt beim nächsten pve-Neustart):**
| `order=` | Stufe | Gäste |
|---|---|---|
| 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` |
🎯 **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.**
- **Abh.:** keine. **Blockiert nichts**, aber TP31 vergibt bereits danach.
---
## Abgeschlossene Teilprojekte ▸ [`teilprojekte-archiv.md`](teilprojekte-archiv.md)