OTA-Updates für die iOS-App; HACS-Fehlannahme korrigiert

HACS kann laut eigener Dokumentation grundsätzlich nicht mit privaten
GitHub-Repositories arbeiten (hacs.xyz/docs/faq/private_repositories) - keine
Ausnahme für Tokens oder verbundene Konten. Meine frühere Annahme, HACS käme
damit zurecht, wenn es unter dem richtigen Konto angemeldet ist, war falsch.
Da das Repository aus Lizenzgründen privat bleiben muss (Audi-Hausschrift,
Typenschilder), ist install.ps1 damit nicht die Rückfallebene, sondern der
einzige Installationsweg - README, INSTALL.md, ANLEITUNG.md, install.ps1 und
VERSIONIERUNG.md korrigiert.

Oberflächen-Updates für die iOS-App laufen jetzt ohne Xcode:
@capgo/capacitor-updater eingebaut, ein Update-Abschnitt in den
Einstellungen lädt ein neues Bündel und tauscht die Oberfläche aus. Kein
Selbstlauf (autoUpdate: false) - nur auf Tastendruck, nie während der
Benutzung.

Das Bündel liegt in der Integration selbst
(custom_components/audi_dashboard/frontend/app/), nicht unter /local/: so
reist es bei jeder Installation automatisch mit, ohne zweiten
Auslieferungsweg. Gebaut von companion-app/scripts/ota-paket.ps1 (neuer
Befehl: npm run ota), gemeldet über sensor.audi_dashboard_app_version
(neues Feld daten.buendel).

Ein echter Bug beim Bauen gefunden: [IO.Compression.ZipFile]::CreateFrom-
Directory schreibt unter Windows PowerShell 5.1 Backslashes in die
Zip-Einträge - iOS hätte das Archiv falsch entpackt. Behoben, indem die
Einträge von Hand mit "/" geschrieben werden.

Rückfallebene: notifyAppReady() läuft erst, wenn React nachweislich
gerendert hat (App.tsx). Kommt diese Meldung nicht, rollt das Plugin nach
20 Sekunden von selbst auf das vorherige Bündel zurück.

Am laufenden Testcontainer verifiziert: die ausgelieferte Zip hasht exakt
auf den in bundle.json hinterlegten Wert, 13 Einträge, index.html in der
Wurzel, keine Backslashes, keine Beschädigung. tsc sauber, 117/117 Tests
(5 davon neu für buendelPasst() - dabei eine echte Lücke gefunden: die
Funktion hätte bei unbekannter eigener Version fälschlich ein Update
angeboten, jetzt genauso vorsichtig wie versionVergleichen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 09:20:14 +02:00
parent d8b12da36d
commit 1354ca6b06
22 changed files with 695 additions and 99 deletions
+52 -13
View File
@@ -30,10 +30,16 @@ In `custom_components/audi_dashboard/manifest.json`, Feld `version`. Format
`2026.8.23.2` (Datum + laufende Nummer).
Genau dort und nirgends sonst. Home Assistant verlangt das Feld für jede
benutzerdefinierte Integration, HACS zeigt es an und entscheidet danach, ob ein
Update bereitliegt — eine zweite Quelle für dieselbe Angabe wäre eine zu viel.
Sie hätten irgendwann auseinandergelegen, und dann wäre der Vergleich still
falsch geworden statt laut.
benutzerdefinierte Integration — eine zweite Quelle für dieselbe Angabe wäre
eine zu viel. Sie hätten irgendwann auseinandergelegen, und dann wäre der
Vergleich still falsch geworden statt laut.
> HACS würde dieses Feld ebenfalls lesen und danach entscheiden, ob ein
> Update bereitliegt — spielt hier aber keine Rolle: HACS kann laut eigener
> Dokumentation grundsätzlich nicht mit privaten GitHub-Repositories
> arbeiten, und das Repository ist privat (lizenzierte Audi-Assets). Das
> Manifest-Feld bleibt trotzdem die einzig richtige Stelle - Home Assistant
> selbst braucht es unabhängig von HACS.
> Bis 2026-08-23 gab es dafür eine eigene Datei `VERSION` in der Repo-Wurzel,
> daneben eine Unix-Sekundenzahl in `www/audi-dashboard-version.json` als
@@ -82,8 +88,8 @@ ohne Netz darf keinen Fehlalarm auslösen.
1. `version` in `manifest.json` erhöhen — bei mehreren Änderungen am selben Tag
die laufende Nummer: `2026.8.23.2``2026.8.23.3`, am nächsten Tag
`2026.8.24.1`.
2. Integration ausliefern: über HACS (wenn das Repository auf GitHub liegt)
oder mit `homeassistant\installationspaket\Installieren.cmd`.
2. Integration ausliefern: `homeassistant\installationspaket\Installieren.cmd`
(HACS scheidet aus — siehe Kasten oben, das Repository ist privat).
3. iOS-App neu bauen (`npm run build`), damit sie dieselbe Zahl einkompiliert
bekommt.
@@ -92,11 +98,44 @@ dass sie älter ist als der Server.
**Bei einer reinen Neuinstallation** ohne Codeänderung: nichts tun.
## Ausblick: OTA-Updates
## OTA-Updates (seit 2026-08-24)
Sobald `@capgo/capacitor-updater` eingebaut ist (geprüft: MPL-2.0, passt zu
Capacitor 8, Selbst-Hosting ohne fremde Cloud möglich), holt sich die iOS-App
den neuen Stand selbst — dann entfällt Schritt 3 für alles, was nur
JavaScript/CSS betrifft. Xcode wird dann nur noch für echte native Änderungen
gebraucht. Der Vergleich aus dieser Datei bleibt dabei unverändert nützlich: er
ist genau das Signal, an dem die App erkennt, dass ein neues Bündel bereitliegt.
`@capgo/capacitor-updater` ist eingebaut. Für reine Oberflächen-Änderungen
(JavaScript, CSS, Bilder) entfällt Schritt 3 oben — die App lädt sich den
neuen Stand selbst, auf einen Tastendruck des Nutzers in Einstellungen →
App-Update. Xcode wird nur noch für echte native Änderungen gebraucht
(neue Plugins, Berechtigungen, `capacitor.config.ts`).
**Woher das Bündel kommt.** `companion-app/scripts/ota-paket.ps1` packt
`dist/` (ohne Sourcemaps) in
`custom_components/audi_dashboard/frontend/app/bundle.zip`, daneben
`bundle.json` mit Version, SHA-256 und Größe. Weil das im Integrationsordner
liegt, reist es bei jeder Auslieferung automatisch mit — kein zweiter Weg, den
man vergessen könnte. Ausgeliefert wird es unter
`/audi_dashboard_static/app/bundle.zip`, gemeldet über dasselbe Feld, das
schon den Versionsvergleich trägt: `sensor.audi_dashboard_app_version`,
Attribut `daten.buendel`.
**Was die App damit macht** (`companion-app/src/daten/ota.ts`): erscheint ein
Bündel, dessen Version von der eigenen abweicht, zeigt Einstellungen →
App-Update einen Knopf. Ein Tastendruck lädt die Zip, prüft sie clientseitig
gegen die mitgelieferte SHA-256 (das Plugin selbst, nicht diese App) und
tauscht die Oberfläche aus — die App startet dabei neu.
**Die Rückfallebene.** `notifyAppReady()` läuft in `App.tsx`, sobald React
tatsächlich gerendert hat. Bleibt diese Meldung aus, weil das neue Bündel
die App zerlegt hat, rollt das Plugin nach der eingestellten Frist
(`appReadyTimeout` in `capacitor.config.ts`) von selbst auf das vorherige
Bündel zurück — ein kaputtes Update kann das Gerät deshalb nicht dauerhaft
unbrauchbar machen.
**Warum kein Selbstlauf.** `autoUpdate: false` — die App lädt nur auf
ausdrücklichen Tastendruck, nie im Hintergrund. Ein Update, das während einer
Fahrteintragung ungefragt die Oberfläche austauscht, wäre die falsche Sorte
Hilfsbereitschaft.
**HACS spielt hier keine Rolle und wird es auch nicht.** Das Update-Bündel
reist mit `install.ps1` (dem einzigen Installationsweg, siehe oben) genauso
mit wie mit einem hypothetischen HACS-Download — der Mechanismus hängt nicht
daran, wie die Integration selbst auf die Instanz kommt, nur daran, dass das
Bündel im Integrationsordner liegt.