Internetzugriff: fehlenden Einrichtungs-Runbook nachgereicht, Pfad-Freigabeliste korrigiert

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>
This commit is contained in:
2026-08-28 14:41:10 +02:00
parent 391725e663
commit 3ad1809af6
4 changed files with 270 additions and 35 deletions
+80 -5
View File
@@ -420,11 +420,21 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
### B) Infrastructure / commissioning (partly waits for FMM003 hardware) ### B) Infrastructure / commissioning (partly waits for FMM003 hardware)
- [ ] Switch `datametric360.app` nameservers at all-inkl to Cloudflare ("full setup") — - [ ] Switch `datametric360.app` nameservers at all-inkl to Cloudflare ("full setup") —
prerequisite for the tunnel; domain carries nothing else, so this is consequence-free prerequisite for the tunnel; domain carries nothing else, so this is consequence-free. Step-by-
- [ ] Decide hostname split (app on apex + API on `api.` subdomain, or vice versa) step runbook for this and everything below it now exists:
- [ ] Choose reverse proxy (Nginx Proxy Manager vs. Traefik) — **can be done before hardware** [`homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md`](homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md)
against the existing HA API (written 2026-08-28) — account creation, DNS/nameserver changes, and clicking through the HA
- [x] Define the reverse-proxy path allowlist (depends on final entity/service names) add-on UI all remain the owner's own action; nothing here can be done unattended.
- [x] Decide hostname split — **resolved 2026-08-28: no split, one hostname is enough.** The original
app-domain-vs-API-subdomain question assumed a served web build; distribution stayed native/
sideload only (never built as a web app), so `https://datametric360.app` alone covers both API
calls and the app's own OTA bundle. See `COMPANION_APP_ARCHITECTURE.md` §5 item 4.
- [x] Choose reverse proxy — **decided 2026-08-28: Nginx Proxy Manager**, not Traefik. See
`COMPANION_APP_ARCHITECTURE.md` §5 item 3.
- [x] Define the reverse-proxy path allowlist (depends on final entity/service names) — kept current
2026-08-28: corrected the stale `/local/dm360/*` assumption (never built) to the app's real
remote need, its OTA bundle at `/audi_dashboard_static/app/*` (`@capgo/capacitor-updater`). See
`homeassistant/REVERSE_PROXY.md`.
- [ ] Install/wire the FMM003; record firmware version (Codec JSON is firmware-dependent) - [ ] Install/wire the FMM003; record firmware version (Codec JSON is firmware-dependent)
- [x] Generate TLS certificates for Mosquitto + device (small private CA) — done 2026-08-11, 10-year - [x] Generate TLS certificates for Mosquitto + device (small private CA) — done 2026-08-11, 10-year
validity; Mosquitto configured (`certfile`/`keyfile`/`cafile`/`require_certificate: true`). validity; Mosquitto configured (`certfile`/`keyfile`/`cafile`/`require_certificate: true`).
@@ -4515,6 +4525,71 @@ cluster (`total_vehicle_mileage_read_from_can`, `front_left_door`, ...) both sti
majority-vote fix didn't regress the two-simultaneous-sources case `.7` was built to solve. `node --check` majority-vote fix didn't regress the two-simultaneous-sources case `.7` was built to solve. `node --check`
clean, manifest bumped to `2026.8.28.8`, `audi_ha_test` restarted and confirmed clean via log. clean, manifest bumped to `2026.8.28.8`, `audi_ha_test` restarted and confirmed clean via log.
## AH. Setup-Menü: fester Beispielname zusätzlich zum tatsächlich zugeordneten Sensor (2026.8.28.17)
Owner: the sensor-identity hint next to each Setup field headline (`entitaetIdKurz()`, section AG) changes
with whatever is currently mapped - useful for confirming a mapping, but it gives no orientation on what to
search for *before* anything is mapped, or after resetting/remapping. Wanted a second, permanently fixed
example name shown alongside it - `(z. B. can_fuel_volume)` / `(z. B. engine_ignition_or_acc_status)` -
styled prominently ("Sensorname-Font", white/big), with the real currently-mapped sensor kept smaller/grey
exactly as before.
Added a static `"beispiel"` string to every entry in the `FELDER` catalog (`einstellungen.py`) - one
plausible real-world sensor-ID suffix per role (`can_fuel_volume`, `engine_ignition_or_acc_status`,
`external_power_voltage`, `oil_change_due`/`oil_change_distance`, `front_left_door`, ...), several taken
directly from this project's own real Setup data (sections AC/AG). `FELDER` is serialized to the frontend
verbatim (`zuordnung.py`'s `katalog`), so no publish/service change was needed - the new key just appears on
`feld.beispiel` in JS.
`setupFeldzeile()` (`audi-dashboard-app.js`) now renders two stacked lines on the right side of each field's
header instead of one: `feld.beispiel` (fixed, never changes with the mapping) as
`.setup-feld-beispiel` - monospace like the sensor-ID font used elsewhere in this file
(`.entitaet-eintrag-id`), but larger (14px) and full-brightness (`var(--fg)`) - and the existing
`entitaetIdKurz()`-derived real mapping as `.setup-feld-hinweis`, unchanged styling (12.5px, `var(--fg2)`).
Both sit inside a new `.setup-feld-hinweise` flex-column wrapper (right-aligned, 2px gap); `.setup-feld-kopf`
switched from `align-items:baseline` to `flex-start` to keep the label aligned with the top of the now
two-line block instead of its vertical center. No change needed to `entitaetIdKurz()`/`praefixPaar()`
themselves (section AG's majority-vote logic) - only what's displayed alongside their result changed, not
how the real mapping is computed.
Verified live in `audi_ha_test` (not just read from source): read the first six rendered Setup rows via the
shadow DOM after a real restart - every row correctly shows both the fixed example and the real, currently
mapped sensor's short ID side by side and independently (e.g. Tankfüllstand: beispiel `fuel_level`, real
`fuel_level_in_tank_read_from_can` - genuinely different strings, confirming the example does not track the
mapping). `node --check` clean, `py_compile` clean on `einstellungen.py`, manifest bumped to `2026.8.28.17`,
`audi_ha_test` restarted and confirmed clean via log (single `audi_dashboard` loader warning, no traceback).
No companion-app change needed - the Setup menu is an HA-panel-only concept (parity rule's own carve-out,
already established for this exact screen in sections R/AF).
**Correction (2026.8.28.18), owner rejected the two-line design outright.** The invented static per-field
`beispiel` guesses were simply wrong for some fields ("Reichweite"/"Sofort-Aktualisierung" specifically
called out) - a hand-picked guess can't be trusted the way the actually-mapped sensor's own derived name can.
Owner's correction: **only one line**, showing the real, currently-mapped sensor (the existing
`entitaetIdKurz()` result, dynamic - not a static guess) formatted as `(z. B. REAL_SENSOR_NAME)`; the
`(z. B. `/`)` wrapper text in the same size/color as the field label on the left (`.setup-feld-label`,
14.5px/`var(--fg)`), the sensor name itself inside in the same size/color as the existing unit bracket
(`.setup-feld-einheit`, `[on/off]`/`[km]` - 12.5px/`var(--fg3)`); remove entirely for list fields (Türen/
Fenster - already excluded from `erwartet` by `feld.liste`, just needed the static example line gated the
same way, which it wasn't).
Removed the `"beispiel"` catalog data from `einstellungen.py`'s `FELDER` entirely (dead/wrong data, not
worth keeping around) and the `.setup-feld-hinweise`/`.setup-feld-beispiel` classes. `setupFeldzeile()`
now renders a single `<span class="setup-feld-hinweis">(z. B. <span class="setup-feld-einheit">...</span>)
</span>` - reusing the existing `.setup-feld-einheit` class for the inner sensor-name span instead of a new
rule, since the requested style is byte-identical to what that class already provides. `.setup-feld-hinweis`
itself changed from the old small/grey 12.5px to the label's own 14.5px/`var(--fg)`; `.setup-feld-kopf`
reverted to `align-items:baseline` (single line again, no more two-line stacking).
Verified live in `audi_ha_test` at `2026.8.28.18` (a fresh page load was required to see it - the panel's
script URL is version-query-busted and a stale tab keeps the old bundle even after the backend restarts,
confirmed via `performance.getEntriesByType('resource')` showing `?v=2026.8.28.18` only after navigating
again, same lesson as sections N/W): read every rendered Setup row's markup and computed styles - "Reichweite"
now shows the real `vehicle_range_read_from_can`, "Sofort-Aktualisierung" shows the real `refresh_now`
(both previously wrong static guesses), "Türen"/"Fenster" show no hint at all, and the wrapper/inner-name
computed styles matched the label (14.5px) and unit-bracket (12.5px, grey) styles exactly, byte for byte.
`node --check` clean, `py_compile` clean on `einstellungen.py`, `audi_ha_test` restarted and confirmed clean
via log (single `audi_dashboard` loader warning, no traceback).
--- ---
## Working conventions (observed — keep them) ## Working conventions (observed — keep them)
+27 -15
View File
@@ -302,12 +302,13 @@ never reachable from the public internet. Only a narrow, purpose-built slice of
tunnel from the HAOS host (the OptiPlex) to Cloudflare's edge; no router port ever opens. Confirmed tunnel from the HAOS host (the OptiPlex) to Cloudflare's edge; no router port ever opens. Confirmed
capable of routing **multiple public hostnames to multiple local ports/services** — so it points at capable of routing **multiple public hostnames to multiple local ports/services** — so it points at
the reverse-proxy add-on's port below, not at HA's own port 8123. the reverse-proxy add-on's port below, not at HA's own port 8123.
2. **Nginx Proxy Manager or Traefik add-on** — a path-scoped reverse proxy sitting between the tunnel 2. **Nginx Proxy Manager add-on** (decided, 2026-08-28 — see §5 item 3) — a path-scoped reverse proxy
and HA's internal API, on the Supervisor's internal Docker network. Configured with an **allowlist** sitting between the tunnel and HA's internal API, on the Supervisor's internal Docker network.
of only the specific paths the companion app needs (e.g. `/api/states/sensor.audi_*`, Configured with an **allowlist** of only the specific paths the companion app needs (`/api/states/
`/api/services/pyscript/audi_dashboard_*` — exact path list still TBD, depends on the final entity/ sensor.audi_dashboard_*`, `/api/services/audi_dashboard/*`, plus a handful more — the exact,
service names once the FMM003 mapping is built). Everything else — `/lovelace`, `/config`, `/auth`, current list lives in `homeassistant/REVERSE_PROXY.md`, not duplicated here since entity/service
the HA frontend itself — is blocked at this layer, never reaching the tunnel at all. names can change and this doc would only go stale again). Everything else — `/lovelace`, `/config`,
`/auth`, the HA frontend itself — is blocked at this layer, never reaching the tunnel at all.
3. **Auth stays a second, independent layer:** every proxied call still requires the same Bearer LLAT 3. **Auth stays a second, independent layer:** every proxied call still requires the same Bearer LLAT
HA's REST API already demands. Even a path that slipped through the allowlist would be useless HA's REST API already demands. Even a path that slipped through the allowlist would be useless
without that token, and any individual LLAT can be revoked from the HA profile if the phone is ever without that token, and any individual LLAT can be revoked from the HA profile if the phone is ever
@@ -333,8 +334,16 @@ surface *is* now internet-facing (even though HA itself never is) — accepted a
Deliberately deferred until hardware arrives / the next architecture review — not blocking anything Deliberately deferred until hardware arrives / the next architecture review — not blocking anything
else in this document: else in this document:
1. **Exact reverse-proxy path allowlist** — depends on the final pyscript entity/service names once the 1. **ERLEDIGT (2026-08-28): reverse-proxy path allowlist defined and current.** Lives in
FMM003 combined-entity mapping is implemented; write these down here once decided. `homeassistant/REVERSE_PROXY.md`, kept in sync with the real entity/service names
(`sensor.audi_dashboard_*` / `audi_dashboard.<name>` since the native-integration conversion,
`AGENTS.md` §H) rather than duplicated here. The step-by-step setup runbook that applies it in
Nginx Proxy Manager is `homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md`. One correction worth
recording: the allowlist originally (2026-08-11 planning) assumed the app itself would be served as
a web build under `/local/dm360/` — that was never built. Distribution stayed **native/sideload
only** (§1), so the allowlist instead needed the app's own OTA surface
(`/audi_dashboard_static/app/*`, the `@capgo/capacitor-updater` bundle) rather than a served
web app.
2. **No-hardcoding mapping system** for the FMM003 combined entity — discussed in an earlier session, 2. **No-hardcoding mapping system** for the FMM003 combined entity — discussed in an earlier session,
not yet written to a file or finalized in detail. **Kann erst nach der ersten echten not yet written to a file or finalized in detail. **Kann erst nach der ersten echten
MQTT-Nachricht fertig werden** (§2b): die Feldnamen im Codec-JSON sind noch unbekannt und werden MQTT-Nachricht fertig werden** (§2b): die Feldnamen im Codec-JSON sind noch unbekannt und werden
@@ -389,9 +398,10 @@ else in this document:
Client-Zertifikat, privater Schlüssel) müssen noch in den FMM003-Configurator hochgeladen werden — Client-Zertifikat, privater Schlüssel) müssen noch in den FMM003-Configurator hochgeladen werden —
dabei auf die Dateinamen-Endung achten (siehe Anmerkung oben bei „Dateinamen-Endung ist strikt dabei auf die Dateinamen-Endung achten (siehe Anmerkung oben bei „Dateinamen-Endung ist strikt
geprüft"). geprüft").
3. **Reverse-proxy add-on: Nginx Proxy Manager vs. Traefik** — both viable (§4), pick deferred to the 3. **ERLEDIGT (2026-08-28): Nginx Proxy Manager**, not Traefik. Both remained viable per §4; NPM was
next audit pass. Doesn't depend on the FMM003 — could be set up and tested against HA's existing API picked and is what `homeassistant/REVERSE_PROXY.md` and `INTERNET_ZUGRIFF_EINRICHTEN.md` are written
before the hardware arrives, if worth doing ahead of time. against. Setup itself (Cloudflare account, nameserver switch, add-on installation) is still the
owner's own action to perform — see the runbook for the exact steps.
4. **Cloudflare Tunnel domain** — ✅ **ERLEDIGT: `datametric360.app` ist bei all-inkl registriert** 4. **Cloudflare Tunnel domain** — ✅ **ERLEDIGT: `datametric360.app` ist bei all-inkl registriert**
(Stand 2026-08-11). Eigens dafür angelegt, ohne Webseite und ohne Postfach darauf — genau so, wie (Stand 2026-08-11). Eigens dafür angelegt, ohne Webseite und ohne Postfach darauf — genau so, wie
es die unten stehende Einschränkung verlangt. Vorgesehene Adresse der App später: es die unten stehende Einschränkung verlangt. Vorgesehene Adresse der App später:
@@ -407,10 +417,12 @@ else in this document:
setup"), gibt es nur im Business-Tarif für 200 $/Monat. Weil auf dieser Domain nichts liegt außer setup"), gibt es nur im Business-Tarif für 200 $/Monat. Weil auf dieser Domain nichts liegt außer
diesem Vorhaben, ist die Umstellung folgenlos. diesem Vorhaben, ist die Umstellung folgenlos.
Noch zu entscheiden (Kleinigkeit, erst bei der Einrichtung): ob die App unter der Domain selbst **ERLEDIGT (2026-08-28): ein einziger Hostname genügt**, kein Split nötig. Der ursprüngliche
liegt und die abgesicherte Schnittstelle unter einer Unteradresse (etwa `api.datametric360.app`), Gedanke (App-Domain vs. API-Subdomain) setzte voraus, dass die App selbst als Web-Build unter einer
oder umgekehrt. Beides funktioniert; ein getrennter Hostname für die Schnittstelle macht die eigenen Adresse ausgeliefert würde — das wurde nie gebaut (Distribution blieb nativ/sideload, §1).
Pfad-Freigabeliste im Reverse Proxy übersichtlicher. Es gibt nur eine Adresse, die überhaupt gebraucht wird: `https://datametric360.app`, die die App
sowohl für API-Aufrufe als auch für ihr eigenes OTA-Bündel verwendet (siehe §5 Punkt 1 oben,
`homeassistant/REVERSE_PROXY.md`).
Die Einschränkung, die zur eigenen Domain geführt hat: Die Einschränkung, die zur eigenen Domain geführt hat:
@@ -0,0 +1,130 @@
# Internetzugriff für DataMetric360 einrichten
Schritt-für-Schritt-Anleitung, damit die native Companion-App (iOS-Sideload, siehe
`../COMPANION_APP_ARCHITECTURE.md` §1) von unterwegs (Mobilfunk, fremdes WLAN) funktioniert - nicht nur
im Heimnetz/über Tailscale. Home Assistant selbst bleibt dabei durchgehend **lokal, ohne offenen Port**;
nur ein eng begrenzter, pfad-gefilterter Ausschnitt der API wird über einen Cloudflare-Tunnel erreichbar
gemacht. Architektur/Begründung: `../COMPANION_APP_ARCHITECTURE.md` §4. Die technische Pfad-Freigabeliste
selbst, die in Schritt 6 unten eingetragen wird: [`REVERSE_PROXY.md`](REVERSE_PROXY.md).
**Wer was macht:** Kontoerstellung, Domain-/Nameserver-Verwaltung bei Cloudflare bzw. all-inkl und das
Klicken durch die HA-Add-on-Oberfläche sind ausschließlich Sache des Fahrzeughalters selbst - dafür gibt
es hier keine Automatisierung, nur die genauen Schritte. Alles, was als Datei/Konfiguration vorbereitet
werden konnte, ist bereits fertig (`REVERSE_PROXY.md`s Regeln).
**Voraussetzungen, die bereits erfüllt sind** (nichts davon ist hier noch zu tun):
- `datametric360.app` ist bei all-inkl registriert (siehe `../COMPANION_APP_ARCHITECTURE.md` §5 Punkt 4)
- eigens dafür, ohne Webseite/Postfach darauf.
- Die companion-app spricht bereits ausschließlich REST/WebSocket gegen eine konfigurierbare
"Server-Adresse" (kein `hass`-Objekt, kein fest verdrahteter Hostname) - dieser Schritt ändert nur,
*welche* Adresse dort später eingetragen wird, nicht die App selbst.
---
## 1. Cloudflare-Konto anlegen, Domain hinzufügen
1. Auf [cloudflare.com](https://cloudflare.com) ein (kostenloses) Konto anlegen.
2. Im Dashboard *Add a Site*`datametric360.app` eingeben.
3. Plan **Free** wählen - reicht vollständig aus (Tunnel und DNS sind im Free-Plan enthalten, nur das
"partial/CNAME setup" ohne Nameserver-Wechsel wäre Business-only, siehe Schritt 2).
4. Cloudflare scannt bestehende DNS-Einträge der Domain und schlägt zwei Nameserver vor (z. B.
`xxx.ns.cloudflare.com`, `yyy.ns.cloudflare.com`) - **notieren**, die werden in Schritt 2 gebraucht.
## 2. Nameserver bei all-inkl auf Cloudflare umstellen
**Das ist der eigentliche Knackpunkt** - erst danach kann der Tunnel überhaupt einen Hostnamen
veröffentlichen.
1. Bei all-inkl (KAS-Verwaltung) einloggen → *Domains*`datametric360.app` → Nameserver-Einstellungen.
2. Von all-inkls eigenen Nameservern auf die beiden von Cloudflare vorgeschlagenen (Schritt 1.4)
umstellen - das ist ein **"Full setup"**: die komplette DNS-Verwaltung der Domain wandert zu
Cloudflare, nicht nur ein einzelner Eintrag. Das ist hier folgenlos, weil auf dieser Domain nichts
anderes liegt (keine Webseite, kein Postfach) - siehe die Einschränkung in
`../COMPANION_APP_ARCHITECTURE.md` §5 Punkt 4, warum die Domain extra dafür angelegt wurde.
3. Umstellung kann laut Cloudflare bis zu 24 Stunden dauern (meist deutlich schneller). Status im
Cloudflare-Dashboard prüfen - die Domain wechselt dort von "Pending Nameserver Update" auf "Active".
4. Erst wenn "Active" angezeigt wird, mit Schritt 3 weitermachen (ein Tunnel-Hostname lässt sich vorher
zwar anlegen, aber die DNS-Auflösung funktioniert erst danach).
## 3. Cloudflared-Add-on installieren und Tunnel einrichten
1. In Home Assistant: *Einstellungen → Add-ons → Add-on Store* → nach "Cloudflared" suchen, installieren.
(Offizielles Home Assistant Community Add-on - nicht die eigenständige `cloudflared`-CLI von Hand
einrichten.)
2. Im Cloudflare-Dashboard: *Zero Trust → Networks → Tunnels**Create a tunnel* → Typ "Cloudflared" →
Namen vergeben (z. B. `datametric360`).
3. Cloudflare zeigt einen Tunnel-Token an - diesen im HA-Add-on unter *Konfiguration* eintragen (Feld
`tunnel_token` bzw. je nach Add-on-Version über die angezeigte `cloudflared service install`-Zeile;
das Add-on übernimmt das Token-Handling, kein manueller `cloudflared`-Aufruf per SSH nötig).
4. Add-on starten. Im Cloudflare-Dashboard sollte der Tunnel danach als "Healthy"/verbunden angezeigt
werden - das bestätigt die **ausgehende** Verbindung vom HAOS-Host zu Cloudflares Edge (kein
Router-Port nötig, kein Port-Forward einzurichten).
5. **Öffentlichen Hostnamen noch nicht auf Home Assistant selbst zeigen lassen** - das kommt erst in
Schritt 5, nachdem der Reverse Proxy (Schritt 4) steht. Der Tunnel zeigt am Ende auf den Reverse
Proxy, nie direkt auf Port 8123.
## 4. Nginx Proxy Manager installieren und Pfad-Freigabeliste eintragen
1. *Einstellungen → Add-ons → Add-on Store* → "Nginx Proxy Manager" installieren, starten.
2. Die Weboberfläche des Add-ons öffnen (Standard-Login beim ersten Start: `admin@example.com` /
`changeme` - **sofort ändern**, siehe Add-on-Dokumentation).
3. *Hosts → Proxy Hosts → Add Proxy Host*:
- **Domain Names:** `datametric360.app`
- **Forward Hostname/IP:** `homeassistant` (interner Name im Supervisor-Docker-Netz;
alternativ `localhost`/`homeassistant.local.hass.io`, je nach Add-on-Version - im Zweifel im
Add-on-Log nachsehen, mit welchem Namen sich `homeassistant:8123` von dort aus auflösen lässt)
- **Forward Port:** `8123`
- **SSL:** eigenes Cloudflare-Zertifikat reicht (der Tunnel terminiert TLS bereits an Cloudflares
Edge) - hier kann "Force SSL" aktiv bleiben, ein eigenes Let's-Encrypt-Zertifikat ist nicht
zwingend nötig, schadet aber auch nicht.
4. Im Tab **Advanced** den kompletten `nginx`-Codeblock aus [`REVERSE_PROXY.md`](REVERSE_PROXY.md)
(Abschnitt "Nginx Proxy Manager") **unverändert** einfügen - das ist die eigentliche
Pfad-Freigabeliste: nur `/api/`, `/api/states/sensor.audi_dashboard_*`,
`/api/services/audi_dashboard/*`, `/api/services/homeassistant/restart`, `/api/websocket`,
`/audi_dashboard_static/app/*` und `/local/bilder/*.{webp,png,jpg,svg}` kommen durch - alles andere
(`/lovelace`, `/config`, `/auth/*`, die HA-Oberfläche selbst) liefert `404`.
5. Speichern.
## 5. Cloudflare-Tunnel auf den Reverse Proxy zeigen lassen
1. Zurück im Cloudflare-Dashboard: *Zero Trust → Networks → Tunnels* → den Tunnel aus Schritt 3 öffnen →
*Public Hostname**Add a public hostname*.
2. **Subdomain:** leer lassen (Apex-Domain `datametric360.app` selbst - kein Subdomain-Präfix, siehe
"Ein einziger Hostname genügt" in `REVERSE_PROXY.md`).
3. **Domain:** `datametric360.app`
4. **Service Type:** `HTTPS` (nicht `HTTP` - Nginx Proxy Manager terminiert selbst wieder TLS).
5. **URL:** die interne Adresse des Nginx-Proxy-Manager-Add-ons, üblicherweise der Add-on-Hostname im
Supervisor-Netz plus dessen konfigurierten Port (im Add-on selbst unter *Info* nachsehen, welcher
interne Port/Hostname das ist - **nicht** Port 8123, das wäre HA direkt).
6. Speichern.
## 6. Prüfen
Von einem Netz **ohne** VPN/Tailscale (z. B. Mobilfunk, WLAN-Tethering vom Handy) die Prüfbefehle aus
[`REVERSE_PROXY.md`](REVERSE_PROXY.md) (Abschnitt "Nach der Einrichtung prüfen") ausführen. Kurzfassung:
`GET /api/` und `GET /api/states/sensor.audi_dashboard_profil` (mit gültigem Zugriffstoken) müssen
funktionieren; `GET /auth/authorize` und `GET /api/config` müssen `404`/`403` liefern; ohne Token muss
`/api/states/...` mit `401` statt `200` antworten. **Und:** Port 8123 darf von außen gar nicht antworten
(z. B. über einen externen Port-Checker prüfen, oder `curl` gegen `http://<öffentliche-WAN-IP>:8123`,
das muss ins Leere laufen).
## 7. Server-Adresse in der App eintragen
Auf jedem Gerät, auf dem die companion-app sideload-installiert ist: beim (erneuten) Einrichten
`https://datametric360.app` als Server-Adresse eintragen, den bestehenden Zugriffstoken wiederverwenden
oder einen neuen erzeugen. Ab hier funktionieren sowohl die normalen Datenabrufe als auch
Fern-OTA-Updates (`AGENTS.md`, Abschnitt zu `@capgo/capacitor-updater`) über denselben Weg - lokal im
Heimnetz weiterhin genauso wie zuvor, da HA selbst unverändert nur lokal erreichbar bleibt und die App
ohnehin dieselbe Server-Adresse für beide Fälle verwendet.
## Was danach noch offen bleibt
- Ein Fehlschlag in Schritt 6 zuerst hier prüfen, in dieser Reihenfolge: Ist die Domain in Cloudflare
"Active" (Schritt 2)? Zeigt der Tunnel "Healthy" (Schritt 3.4)? Antwortet der Nginx-Proxy-Manager-Host
lokal überhaupt (`curl` von der HA-Konsole gegen die in Schritt 5.5 eingetragene interne Adresse)?
Erst danach die Pfad-Regeln selbst (Schritt 4) noch einmal gegen `REVERSE_PROXY.md` vergleichen.
- Ein Gerät verlieren/kompromittiert vermuten: den betroffenen Zugriffstoken im HA-Profil zurückziehen
(*Profil → Sicherheit → Zugriffstoken (Long-Lived Access Tokens)*) - das sperrt sofort aus, unabhängig
vom Tunnel/Reverse-Proxy.
- Diese Anleitung deckt nur den Netzwerkweg ab. Für alles rund um Xcode-Signierung/Sideload der App
selbst: `../COMPANION_APP_ARCHITECTURE.md` §1 ("Distribution: sideload only").
+33 -15
View File
@@ -15,9 +15,23 @@ Proxy, der ausschließlich die unten aufgeführten Pfade durchlässt.
> Seit der Umstellung auf eine native Integration (2026-08-23, siehe `AGENTS.md` Abschnitt H) heißen > 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 > 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 > 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 - > Bedarfe ergänzt (`homeassistant.restart` fürs Selbst-Update, sowie das OTA-Bündel unter
> beide fehlten in der alten Fassung ersatzlos, ohne sie hätte weder der Update-Neustart-Knopf noch die > `/audi_dashboard_static/app/*` - beide fehlten in der alten Fassung ersatzlos, ohne sie hätte weder
> ausgelieferte App über den Tunnel funktioniert). > 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 ## Was die App wirklich braucht
@@ -31,7 +45,7 @@ nicht geschätzt - dort gibt es genau drei REST-Aufrufmuster plus WebSocket:
| `POST` | `/api/services/audi_dashboard/*` | alle schreibenden Vorgänge (Fahrt/Tankvorgang anlegen/ändern, CSV-/Historien-Import, Update prüfen/installieren, ...) | | `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) | | `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` | `/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 | | `GET` | `/local/bilder/*.{webp,png,jpg,svg}` | Fahrzeugfotos, die die App anzeigt |
Alles andere wird geblockt, insbesondere `/auth/*`, `/lovelace*`, `/config*`, `/api/config`, Alles andere wird geblockt, insbesondere `/auth/*`, `/lovelace*`, `/config*`, `/api/config`,
@@ -101,8 +115,9 @@ 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/). # Das OTA-Oberflächen-Bündel für die native App (siehe Hinweis oben - das ist
location ^~ /local/dm360/ { # NICHT die App selbst, die läuft nativ; nur ihr nachladbares HTML/CSS/JS).
location ^~ /audi_dashboard_static/app/ {
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;
@@ -121,11 +136,13 @@ location / {
} }
``` ```
Wird die App selbst unter derselben Adresse ausgeliefert (`/local/dm360/` auf demselben Hostnamen wie **Ein einziger Hostname genügt.** Die frühere Überlegung, App und Schnittstelle auf zwei getrennte
die Schnittstelle), braucht dieser Proxy-Host nur die Blöcke oben - kein separater Host nötig. Ein Hostnamen zu legen (`datametric360.app` für die App, `api.datametric360.app` für die Schnittstelle),
getrennter Hostname für die Schnittstelle (`api.datametric360.app` statt `datametric360.app`) ist stammte aus der Annahme, die App würde selbst als Web-Build unter einer eigenen Adresse ausgeliefert
trotzdem sauberer, siehe `COMPANION_APP_ARCHITECTURE.md` §5 Punkt 4: dann bleibt der `/local/dm360/`-Block (siehe Korrektur-Hinweis oben) - das entfällt mit der nativen Distribution ersatzlos. Es gibt nur noch
auf dem App-Host, die übrigen Blöcke auf dem API-Host. *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 ## Nach der Einrichtung prüfen
@@ -133,13 +150,15 @@ Von einem Netz ohne VPN, etwa über Mobilfunk (Hostnamen unten sind Platzhalter
Einrichtung tatsächlich gewählt wurde): Einrichtung tatsächlich gewählt wurde):
```bash ```bash
API=https://api.datametric360.app API=https://datametric360.app
T=<Zugriffstoken> 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/sensor.audi_dashboard_profil" "$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 # 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"
@@ -160,6 +179,5 @@ geordneten Ablauf - kurz zusammengefasst bleibt vor der ersten echten Nutzung di
- Cloudflare-Konto anlegen, `datametric360.app`-Zone hinzufügen - Cloudflare-Konto anlegen, `datametric360.app`-Zone hinzufügen
- Nameserver von `datametric360.app` bei all-inkl auf Cloudflare umstellen („Full setup") - Nameserver von `datametric360.app` bei all-inkl auf Cloudflare umstellen („Full setup")
- Cloudflared- und Nginx-Proxy-Manager-Add-ons installieren, Tunnel + öffentliche Hostnamen einrichten - Cloudflared- und Nginx-Proxy-Manager-Add-ons installieren, Tunnel + öffentlichen Hostnamen einrichten
- Endgültige Hostnamen festlegen (App-Domain vs. API-Subdomain) und hier eintragen - `https://datametric360.app` als Server-Adresse in der App eintragen (jedes Gerät, beim Einrichten)
- companion-app-Web-Build nach `/config/www/dm360/` ausliefern