diff --git a/homeassistant/REVERSE_PROXY.md b/homeassistant/REVERSE_PROXY.md index 3a32b94..8f72566 100644 --- a/homeassistant/REVERSE_PROXY.md +++ b/homeassistant/REVERSE_PROXY.md @@ -1,50 +1,67 @@ # Reverse-Proxy: Pfad-Freigabeliste für DataMetric360 -**Vorbereitung, noch nicht in Betrieb.** Gehört zu Phase 12 des -`../UMSETZUNGSPLAN.md`; die Entscheidung dahinter steht in -`../COMPANION_APP_ARCHITECTURE.md` §4. +**Vorbereitet, noch nicht in Betrieb.** Gehört zu `../COMPANION_APP_ARCHITECTURE.md` §4. Reverse Proxy +ist entschieden: **Nginx Proxy Manager** (Add-on), auf demselben Host wie Home Assistant selbst. Die +konkreten Einrichtungsschritte (Cloudflare-Konto, Nameserver-Umstellung, Add-ons, ...) stehen in +[`INTERNET_ZUGRIFF_EINRICHTEN.md`](INTERNET_ZUGRIFF_EINRICHTEN.md) - dieses Dokument ist nur die +technische Pfad-Freigabeliste selbst, die dort im letzten Schritt eingetragen wird. -Ziel: Die App erreicht Home Assistant von unterwegs, **ohne dass Home Assistant -selbst im Internet steht**. Dazu ein Cloudflare-Tunnel (baut nur nach außen auf, -kein Port am Router) auf einen Reverse Proxy, der ausschließlich die unten -aufgeführten Pfade durchlässt. +Ziel: Die App erreicht Home Assistant von unterwegs, **ohne dass Home Assistant selbst im Internet +steht**. Dazu ein Cloudflare-Tunnel (baut nur nach außen auf, kein Port am Router) auf einen Reverse +Proxy, der ausschließlich die unten aufgeführten Pfade durchlässt. + +> **2026-08-28 aktualisiert:** die ursprüngliche Fassung dieses Dokuments ging noch von den +> pyscript-Zeiten aus (`pyscript.audi_dashboard_*`, `pyscript.reifen_*`, `/api/services/pyscript/...`). +> Seit der Umstellung auf eine native Integration (2026-08-23, siehe `AGENTS.md` Abschnitt H) heißen +> Entitäten `sensor.audi_dashboard_*` und Dienste `audi_dashboard.` - keine doppelten Präfixe +> mehr. Die Regeln unten sind auf den aktuellen Stand korrigiert und um zwei seither neu hinzugekommene +> Bedarfe ergänzt (`homeassistant.restart` fürs Selbst-Update, `/local/dm360/*` für die App selbst - +> beide fehlten in der alten Fassung ersatzlos, ohne sie hätte weder der Update-Neustart-Knopf noch die +> ausgelieferte App über den Tunnel funktioniert). ## Was die App wirklich braucht -Aus dem tatsächlichen Code der Datenschicht (`companion-app/src/api/`) -abgeleitet, nicht geschätzt: +Aus dem tatsächlichen Code der Datenschicht (`companion-app/src/api/rest.ts`, `index.ts`) abgeleitet, +nicht geschätzt - dort gibt es genau drei REST-Aufrufmuster plus WebSocket: | Methode | Pfad | Wofür | |---|---|---| | `GET` | `/api/` | Verbindungsprüfung bei der Ersteinrichtung | -| `GET` | `/api/states/pyscript.audi_dashboard_*` | Profil, Fahrten, Tankvorgänge, Status, Batterieverlauf, Belegergebnis, Updatestatus | -| `GET` | `/api/states/pyscript.reifen_*` | Reifensatz-Kilometerstände | -| `POST` | `/api/services/pyscript/audi_dashboard_*` | alle schreibenden Vorgänge | +| `GET` | `/api/states/sensor.audi_dashboard_*` | Profil, Fahrten, Tankvorgänge, Status, Batterieverlauf, Belegergebnis, Importstatus, App-Version, Reifen-Kilometerstände | +| `POST` | `/api/services/audi_dashboard/*` | alle schreibenden Vorgänge (Fahrt/Tankvorgang anlegen/ändern, CSV-/Historien-Import, Update prüfen/installieren, ...) | +| `POST` | `/api/services/homeassistant/restart` | genau ein Dienst außerhalb der eigenen Domain - der "Jetzt neu starten"-Knopf nach einem Integrations-Update (siehe `AGENTS.md` Abschnitt L) | | `GET` (Upgrade) | `/api/websocket` | Live-Aktualisierung | +| `GET` | `/local/dm360/*` | die ausgelieferte App selbst (`companion-app`, `vite.config.ts`: `base: "./"`, Ziel `/config/www/dm360/`) - ohne diesen Pfad lädt die Seite gar nicht erst | +| `GET` | `/local/bilder/*.{webp,png,jpg,svg}` | Fahrzeugfotos, die die App anzeigt | -Alles andere wird geblockt, insbesondere `/auth/*`, `/lovelace*`, `/config*`, -`/api/config`, `/api/history*`, `/developer-tools*` und die Oberfläche selbst. +Alles andere wird geblockt, insbesondere `/auth/*`, `/lovelace*`, `/config*`, `/api/config`, +`/api/history*`, `/developer-tools*` und die Home-Assistant-Oberfläche selbst. ## Ehrliche Einschränkung -**Der WebSocket lässt sich nicht pfadgenau beschneiden.** Nach `auth_ok` kann -über `/api/websocket` grundsätzlich jeder Zustand gelesen werden, nicht nur die -`pyscript.*`-Entitäten. Die Freigabeliste ist an dieser Stelle also grobkörniger -als beim REST-Zugriff. +**Der WebSocket lässt sich nicht pfadgenau beschneiden.** Nach `auth_ok` kann über `/api/websocket` +grundsätzlich jeder Zustand gelesen werden, nicht nur die `audi_dashboard`-Entitäten. Die Freigabeliste +ist an dieser Stelle also grobkörniger als beim REST-Zugriff. -Was bleibt: Ohne gültigen Token kommt gar keine Verbindung zustande, und ein -verlorenes Gerät wird durch Zurückziehen genau seines Tokens ausgesperrt. Das -ist die im Architekturdokument bewusst akzeptierte Abwägung — sie sollte nur -nicht in Vergessenheit geraten. +Was bleibt: Ohne gültigen Token kommt gar keine Verbindung zustande, und ein verlorenes Gerät wird durch +Zurückziehen genau seines Tokens ausgesperrt. Das ist die im Architekturdokument bewusst akzeptierte +Abwägung - sie sollte nur nicht in Vergessenheit geraten. -Wer sie nicht eingehen will, hat eine Alternative: den WebSocket weglassen und -die App auf regelmäßiges Abfragen umstellen. Kostet Akku und Datenvolumen, -verkleinert die Angriffsfläche aber auf die exakt aufgeführten REST-Pfade. +Wer sie nicht eingehen will, hat eine Alternative: den WebSocket weglassen und die App auf regelmäßiges +Abfragen umstellen. Kostet Akku und Datenvolumen, verkleinert die Angriffsfläche aber auf die exakt +aufgeführten REST-Pfade. + +`homeassistant.restart` verdient eine eigene Erwähnung: wer den Token besitzt, kann damit die gesamte +Home-Assistant-Instanz neu starten, nicht nur etwas im Rahmen dieser App. Dasselbe gilt lokal schon +heute (derselbe Token, dieselbe Berechtigung) - der Tunnel ändert am Berechtigungsumfang nichts, nur am +Netzwerkpfad. Wer das nicht mit ausliefern will: den Block für `homeassistant/restart` unten weglassen - +der Update-Knopf zeigt dann nach einem Update lediglich "bitte manuell neu starten" statt selbst zu +handeln (kein Absturz, siehe `AGENTS.md` Abschnitt L). ## Nginx Proxy Manager -Im Add-on unter *Hosts → Proxy Hosts → Edit → Advanced* eintragen. Ziel ist der -interne Name der Home-Assistant-Instanz (im Supervisor-Netz `homeassistant:8123`). +Im Add-on unter *Hosts → Proxy Hosts → Edit → Advanced* eintragen. Ziel ist der interne Name der +Home-Assistant-Instanz (im Supervisor-Netz `homeassistant:8123`). ```nginx # Reihenfolge zählt: die erlaubenden Blöcke stehen vor dem pauschalen Verbot. @@ -54,13 +71,22 @@ location = /api/ { include conf.d/include/proxy.conf; } -location ~ ^/api/states/pyscript\.(audi_dashboard_[a-z_]+|reifen_[a-z_]+)$ { +location ~ ^/api/states/sensor\.audi_dashboard_[a-z0-9_]+$ { limit_except GET { deny all; } proxy_pass http://homeassistant:8123; include conf.d/include/proxy.conf; } -location ~ ^/api/services/pyscript/audi_dashboard_[a-z_]+$ { +location ~ ^/api/services/audi_dashboard/[a-z_]+$ { + limit_except POST { deny all; } + proxy_pass http://homeassistant:8123; + include conf.d/include/proxy.conf; +} + +# Genau ein Dienst außerhalb der eigenen Domain - siehe "Ehrliche +# Einschränkung" oben, wer ihn nicht mit ausliefern will, lässt diesen +# Block weg. +location = /api/services/homeassistant/restart { limit_except POST { deny all; } proxy_pass http://homeassistant:8123; include conf.d/include/proxy.conf; @@ -75,6 +101,13 @@ location = /api/websocket { include conf.d/include/proxy.conf; } +# Die App selbst (companion-app, ausgeliefert unter /config/www/dm360/). +location ^~ /local/dm360/ { + limit_except GET { deny all; } + proxy_pass http://homeassistant:8123; + include conf.d/include/proxy.conf; +} + # Die Fotos der Fahrzeuge, die die App anzeigt. Nur Lesen, nur Bilder. location ~ ^/local/bilder/[a-z0-9_-]+\.(webp|png|jpg|svg)$ { limit_except GET { deny all; } @@ -88,14 +121,16 @@ location / { } ``` -Wird die App selbst unter derselben Adresse ausgeliefert, braucht sie einen -eigenen Block auf ihr Verzeichnis — sauberer ist ein getrennter Hostname -(`datametric360.app` für die App, `api.datametric360.app` für die -Schnittstelle), dann bleibt diese Liste unverändert. +Wird die App selbst unter derselben Adresse ausgeliefert (`/local/dm360/` auf demselben Hostnamen wie +die Schnittstelle), braucht dieser Proxy-Host nur die Blöcke oben - kein separater Host nötig. Ein +getrennter Hostname für die Schnittstelle (`api.datametric360.app` statt `datametric360.app`) ist +trotzdem sauberer, siehe `COMPANION_APP_ARCHITECTURE.md` §5 Punkt 4: dann bleibt der `/local/dm360/`-Block +auf dem App-Host, die übrigen Blöcke auf dem API-Host. ## Nach der Einrichtung prüfen -Von einem Netz ohne VPN, etwa über Mobilfunk: +Von einem Netz ohne VPN, etwa über Mobilfunk (Hostnamen unten sind Platzhalter - eintragen, was bei der +Einrichtung tatsächlich gewählt wurde): ```bash API=https://api.datametric360.app @@ -104,7 +139,7 @@ T= # muss gehen curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" "$API/api/" curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" \ - "$API/api/states/pyscript.audi_dashboard_profil" + "$API/api/states/sensor.audi_dashboard_profil" # muss 404 oder 403 liefern curl -s -o /dev/null -w '%{http_code}\n' "$API/auth/authorize" @@ -113,14 +148,18 @@ curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" \ "$API/api/states/sensor.audi_rs_4_avant_mileage" # ohne Token: 401, nicht 200 -curl -s -o /dev/null -w '%{http_code}\n' "$API/api/states/pyscript.audi_dashboard_profil" +curl -s -o /dev/null -w '%{http_code}\n' "$API/api/states/sensor.audi_dashboard_profil" ``` Zusätzlich: Port 8123 darf von außen **gar nicht** antworten. -## Offen bis zur Einrichtung +## Noch offen -- Nameserver von `datametric360.app` auf Cloudflare umstellen („Full setup") -- Entscheidung Nginx Proxy Manager gegen Traefik (die Liste oben ist für NPM - geschrieben und für Traefik sinngemäß zu übertragen) -- Endgültige Hostnamen festlegen und hier eintragen +Siehe [`INTERNET_ZUGRIFF_EINRICHTEN.md`](INTERNET_ZUGRIFF_EINRICHTEN.md) für den vollständigen, +geordneten Ablauf - kurz zusammengefasst bleibt vor der ersten echten Nutzung dieser Liste offen: + +- Cloudflare-Konto anlegen, `datametric360.app`-Zone hinzufügen +- Nameserver von `datametric360.app` bei all-inkl auf Cloudflare umstellen („Full setup") +- Cloudflared- und Nginx-Proxy-Manager-Add-ons installieren, Tunnel + öffentliche Hostnamen einrichten +- Endgültige Hostnamen festlegen (App-Domain vs. API-Subdomain) und hier eintragen +- companion-app-Web-Build nach `/config/www/dm360/` ausliefern