3ad1809af6
REVERSE_PROXY.md verwies auf INTERNET_ZUGRIFF_EINRICHTEN.md, das nie geschrieben wurde - jetzt vorhanden (Cloudflare-Konto, Nameserver-Umstellung, Cloudflared/NPM-Add-ons, Pfad-Freigabeliste eintragen, Prüfung). Dabei einen echten Fehler in der Freigabeliste gefunden: sie ging noch von einem companion-app-Web-Build unter /local/dm360/ aus (Planungsstand 2026-08-11) - das wurde nie gebaut, die App ist nativ/sideload-only. Ersetzt durch den tatsächlichen Fernbedarf: das OTA-Bündel unter /audi_dashboard_static/app/* (@capgo/capacitor-updater). Damit genügt auch ein einziger Hostname statt der ursprünglich erwogenen App-/API-Trennung. COMPANION_APP_ARCHITECTURE.md §5 und AGENTS.md entsprechend nachgezogen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
184 lines
9.7 KiB
Markdown
184 lines
9.7 KiB
Markdown
# Reverse-Proxy: Pfad-Freigabeliste für DataMetric360
|
|
|
|
**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.
|
|
|
|
> **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, sowie das OTA-Bündel unter
|
|
> `/audi_dashboard_static/app/*` - beide fehlten in der alten Fassung ersatzlos, ohne sie hätte weder
|
|
> der Update-Neustart-Knopf noch ein OTA-Update über den Tunnel funktioniert).
|
|
>
|
|
> **Zweite Korrektur, selber Tag:** die erste Überarbeitung übernahm noch ungeprüft die alte Annahme
|
|
> aus der frühen Planungsphase (2026-08-11), die App würde als eigener Web-Build unter
|
|
> `/config/www/dm360/` bzw. `/local/dm360/` ausgeliefert. Das wurde nie gebaut - die Distributionsart
|
|
> ist seit der Architekturentscheidung "sideload only" (siehe `COMPANION_APP_ARCHITECTURE.md` §1) eine
|
|
> **native Capacitor-App**, direkt per Xcode aufs Gerät geladen, nicht über HA im Browser aufgerufen.
|
|
> Ein `/local/dm360/*`-Pfad existiert auf dieser Instanz nicht und wird nie gebraucht. Was die native
|
|
> App stattdessen tatsächlich extern erreichen muss, ist ihr eigener Oberflächen-Nachlieferweg (OTA,
|
|
> `@capgo/capacitor-updater`, siehe `AGENTS.md` Abschnitt "Deployed-parity"-Unterabschnitt): das Bündel
|
|
> liegt unter `/audi_dashboard_static/app/bundle.zip`, ausgeliefert von der Integration selbst
|
|
> (`custom_components/audi_dashboard/frontend/app/`, `const.py`'s `BUENDEL_URL`) - dieselbe
|
|
> `basisUrl`, die die App auch für ihre normalen API-Aufrufe verwendet (`ota.ts`'s
|
|
> `buendelAnwenden()`), also derselbe Tunnel-Hostname, kein zweiter. Die Regel unten ist entsprechend
|
|
> ersetzt, nicht nur umbenannt.
|
|
|
|
## Was die App wirklich braucht
|
|
|
|
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/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` | `/audi_dashboard_static/app/*` | das OTA-Oberflächen-Bündel (`bundle.zip`) für die native App - ohne diesen Pfad findet ein Update aus der Ferne nicht statt (die App selbst läuft nativ auf dem Gerät, wird nicht über diesen Pfad "geladen") |
|
|
| `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 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 `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.
|
|
|
|
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`).
|
|
|
|
```nginx
|
|
# Reihenfolge zählt: die erlaubenden Blöcke stehen vor dem pauschalen Verbot.
|
|
|
|
location = /api/ {
|
|
proxy_pass http://homeassistant:8123;
|
|
include conf.d/include/proxy.conf;
|
|
}
|
|
|
|
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/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;
|
|
}
|
|
|
|
location = /api/websocket {
|
|
proxy_pass http://homeassistant:8123;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_read_timeout 3600s;
|
|
include conf.d/include/proxy.conf;
|
|
}
|
|
|
|
# Das OTA-Oberflächen-Bündel für die native App (siehe Hinweis oben - das ist
|
|
# NICHT die App selbst, die läuft nativ; nur ihr nachladbares HTML/CSS/JS).
|
|
location ^~ /audi_dashboard_static/app/ {
|
|
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; }
|
|
proxy_pass http://homeassistant:8123;
|
|
include conf.d/include/proxy.conf;
|
|
}
|
|
|
|
# Alles Übrige: nicht durchlassen.
|
|
location / {
|
|
return 404;
|
|
}
|
|
```
|
|
|
|
**Ein einziger Hostname genügt.** Die frühere Überlegung, App und Schnittstelle auf zwei getrennte
|
|
Hostnamen zu legen (`datametric360.app` für die App, `api.datametric360.app` für die Schnittstelle),
|
|
stammte aus der Annahme, die App würde selbst als Web-Build unter einer eigenen Adresse ausgeliefert
|
|
(siehe Korrektur-Hinweis oben) - das entfällt mit der nativen Distribution ersatzlos. Es gibt nur noch
|
|
*eine* Adresse, die überhaupt gebraucht wird: die, die die App beim Einrichten als "Server-Adresse"
|
|
bekommt, und über die sie sowohl die API-Aufrufe als auch das OTA-Bündel erreicht. Ein zweiter Hostname
|
|
wäre zusätzlicher Aufwand ohne Gegenwert.
|
|
|
|
## Nach der Einrichtung prüfen
|
|
|
|
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://datametric360.app
|
|
T=<Zugriffstoken>
|
|
|
|
# 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/sensor.audi_dashboard_profil"
|
|
# kein Token nötig - statische Auslieferung, kein API-Aufruf
|
|
curl -s -o /dev/null -w '%{http_code}\n' "$API/audi_dashboard_static/app/bundle.zip"
|
|
|
|
# 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' -H "Authorization: Bearer $T" "$API/api/config"
|
|
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/sensor.audi_dashboard_profil"
|
|
```
|
|
|
|
Zusätzlich: Port 8123 darf von außen **gar nicht** antworten.
|
|
|
|
## Noch offen
|
|
|
|
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 + öffentlichen Hostnamen einrichten
|
|
- `https://datametric360.app` als Server-Adresse in der App eintragen (jedes Gerät, beim Einrichten)
|