REVERSE_PROXY.md auf aktuelle Entitäts-/Dienstnamen korrigiert

Die Pfad-Freigabeliste ging noch von pyscript.audi_dashboard_*/
pyscript.reifen_* und /api/services/pyscript/... aus - seit der Umstellung
auf die native Integration (2026-08-23) heißen Entitäten
sensor.audi_dashboard_* und Dienste audi_dashboard.<name>. Ergänzt um zwei
seither neu hinzugekommene Bedarfe, die in der alten Fassung fehlten:
homeassistant.restart (Update-Neustart-Knopf) und /local/dm360/* (die
ausgelieferte App selbst - ohne diesen Pfad hätte sie über den Tunnel gar
nicht geladen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 12:01:19 +02:00
parent 6b35de458d
commit bf1f9646e2
+80 -41
View File
@@ -1,50 +1,67 @@
# Reverse-Proxy: Pfad-Freigabeliste für DataMetric360 # Reverse-Proxy: Pfad-Freigabeliste für DataMetric360
**Vorbereitung, noch nicht in Betrieb.** Gehört zu Phase 12 des **Vorbereitet, noch nicht in Betrieb.** Gehört zu `../COMPANION_APP_ARCHITECTURE.md` §4. Reverse Proxy
`../UMSETZUNGSPLAN.md`; die Entscheidung dahinter steht in ist entschieden: **Nginx Proxy Manager** (Add-on), auf demselben Host wie Home Assistant selbst. Die
`../COMPANION_APP_ARCHITECTURE.md` §4. 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 Ziel: Die App erreicht Home Assistant von unterwegs, **ohne dass Home Assistant selbst im Internet
selbst im Internet steht**. Dazu ein Cloudflare-Tunnel (baut nur nach außen auf, steht**. Dazu ein Cloudflare-Tunnel (baut nur nach außen auf, kein Port am Router) auf einen Reverse
kein Port am Router) auf einen Reverse Proxy, der ausschließlich die unten Proxy, der ausschließlich die unten aufgeführten Pfade durchlässt.
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.<name>` - 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 ## Was die App wirklich braucht
Aus dem tatsächlichen Code der Datenschicht (`companion-app/src/api/`) Aus dem tatsächlichen Code der Datenschicht (`companion-app/src/api/rest.ts`, `index.ts`) abgeleitet,
abgeleitet, nicht geschätzt: nicht geschätzt - dort gibt es genau drei REST-Aufrufmuster plus WebSocket:
| Methode | Pfad | Wofür | | Methode | Pfad | Wofür |
|---|---|---| |---|---|---|
| `GET` | `/api/` | Verbindungsprüfung bei der Ersteinrichtung | | `GET` | `/api/` | Verbindungsprüfung bei der Ersteinrichtung |
| `GET` | `/api/states/pyscript.audi_dashboard_*` | Profil, Fahrten, Tankvorgänge, Status, Batterieverlauf, Belegergebnis, Updatestatus | | `GET` | `/api/states/sensor.audi_dashboard_*` | Profil, Fahrten, Tankvorgänge, Status, Batterieverlauf, Belegergebnis, Importstatus, App-Version, Reifen-Kilometerstände |
| `GET` | `/api/states/pyscript.reifen_*` | Reifensatz-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/pyscript/audi_dashboard_*` | alle schreibenden Vorgänge | | `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` (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*`, Alles andere wird geblockt, insbesondere `/auth/*`, `/lovelace*`, `/config*`, `/api/config`,
`/api/config`, `/api/history*`, `/developer-tools*` und die Oberfläche selbst. `/api/history*`, `/developer-tools*` und die Home-Assistant-Oberfläche selbst.
## Ehrliche Einschränkung ## Ehrliche Einschränkung
**Der WebSocket lässt sich nicht pfadgenau beschneiden.** Nach `auth_ok` kann **Der WebSocket lässt sich nicht pfadgenau beschneiden.** Nach `auth_ok` kann über `/api/websocket`
über `/api/websocket` grundsätzlich jeder Zustand gelesen werden, nicht nur die grundsätzlich jeder Zustand gelesen werden, nicht nur die `audi_dashboard`-Entitäten. Die Freigabeliste
`pyscript.*`-Entitäten. Die Freigabeliste ist an dieser Stelle also grobkörniger ist an dieser Stelle also grobkörniger als beim REST-Zugriff.
als beim REST-Zugriff.
Was bleibt: Ohne gültigen Token kommt gar keine Verbindung zustande, und ein Was bleibt: Ohne gültigen Token kommt gar keine Verbindung zustande, und ein verlorenes Gerät wird durch
verlorenes Gerät wird durch Zurückziehen genau seines Tokens ausgesperrt. Das Zurückziehen genau seines Tokens ausgesperrt. Das ist die im Architekturdokument bewusst akzeptierte
ist die im Architekturdokument bewusst akzeptierte Abwägung sie sollte nur Abwägung - sie sollte nur nicht in Vergessenheit geraten.
nicht in Vergessenheit geraten.
Wer sie nicht eingehen will, hat eine Alternative: den WebSocket weglassen und Wer sie nicht eingehen will, hat eine Alternative: den WebSocket weglassen und die App auf regelmäßiges
die App auf regelmäßiges Abfragen umstellen. Kostet Akku und Datenvolumen, Abfragen umstellen. Kostet Akku und Datenvolumen, verkleinert die Angriffsfläche aber auf die exakt
verkleinert die Angriffsfläche aber auf die exakt aufgeführten REST-Pfade. 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 ## Nginx Proxy Manager
Im Add-on unter *Hosts → Proxy Hosts → Edit → Advanced* eintragen. Ziel ist der Im Add-on unter *Hosts → Proxy Hosts → Edit → Advanced* eintragen. Ziel ist der interne Name der
interne Name der Home-Assistant-Instanz (im Supervisor-Netz `homeassistant:8123`). Home-Assistant-Instanz (im Supervisor-Netz `homeassistant:8123`).
```nginx ```nginx
# Reihenfolge zählt: die erlaubenden Blöcke stehen vor dem pauschalen Verbot. # Reihenfolge zählt: die erlaubenden Blöcke stehen vor dem pauschalen Verbot.
@@ -54,13 +71,22 @@ location = /api/ {
include conf.d/include/proxy.conf; 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; } limit_except GET { deny all; }
proxy_pass http://homeassistant:8123; proxy_pass http://homeassistant:8123;
include conf.d/include/proxy.conf; 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; } limit_except POST { deny all; }
proxy_pass http://homeassistant:8123; proxy_pass http://homeassistant:8123;
include conf.d/include/proxy.conf; include conf.d/include/proxy.conf;
@@ -75,6 +101,13 @@ location = /api/websocket {
include conf.d/include/proxy.conf; 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. # Die Fotos der Fahrzeuge, die die App anzeigt. Nur Lesen, nur Bilder.
location ~ ^/local/bilder/[a-z0-9_-]+\.(webp|png|jpg|svg)$ { location ~ ^/local/bilder/[a-z0-9_-]+\.(webp|png|jpg|svg)$ {
limit_except GET { deny all; } limit_except GET { deny all; }
@@ -88,14 +121,16 @@ location / {
} }
``` ```
Wird die App selbst unter derselben Adresse ausgeliefert, braucht sie einen Wird die App selbst unter derselben Adresse ausgeliefert (`/local/dm360/` auf demselben Hostnamen wie
eigenen Block auf ihr Verzeichnis — sauberer ist ein getrennter Hostname die Schnittstelle), braucht dieser Proxy-Host nur die Blöcke oben - kein separater Host nötig. Ein
(`datametric360.app` für die App, `api.datametric360.app` für die getrennter Hostname für die Schnittstelle (`api.datametric360.app` statt `datametric360.app`) ist
Schnittstelle), dann bleibt diese Liste unverändert. 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 ## 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 ```bash
API=https://api.datametric360.app API=https://api.datametric360.app
@@ -104,7 +139,7 @@ T=<Zugriffstoken>
# muss gehen # 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/"
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" \ 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 # muss 404 oder 403 liefern
curl -s -o /dev/null -w '%{http_code}\n' "$API/auth/authorize" 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" "$API/api/states/sensor.audi_rs_4_avant_mileage"
# ohne Token: 401, nicht 200 # 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. 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") Siehe [`INTERNET_ZUGRIFF_EINRICHTEN.md`](INTERNET_ZUGRIFF_EINRICHTEN.md) für den vollständigen,
- Entscheidung Nginx Proxy Manager gegen Traefik (die Liste oben ist für NPM geordneten Ablauf - kurz zusammengefasst bleibt vor der ersten echten Nutzung dieser Liste offen:
geschrieben und für Traefik sinngemäß zu übertragen)
- Endgültige Hostnamen festlegen und hier eintragen - 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