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:
@@ -188,7 +188,7 @@ assets. Never mix these the other way around.
|
||||
|
||||
| Area | What | Status |
|
||||
|---|---|---|
|
||||
| `custom_components/audi_dashboard/` | The HA integration: backend + panel, installable via HACS | ✅ **finished, in use** — replaced the pyscript backend on 2026-08-23 (section H) |
|
||||
| `custom_components/audi_dashboard/` | The HA integration: backend, panel, OTA app bundle. HACS-shaped but HACS-installable only in principle — the repo is private and HACS categorically refuses private repos, so `install.ps1` is the actual, only install path (section H) | ✅ **finished, in use** — replaced the pyscript backend on 2026-08-23 (section H) |
|
||||
| `homeassistant/` | Install package, recorder snippet, guides — **no code any more** | ✅ |
|
||||
| `testumgebung/` | Script that rebuilds a throwaway Home Assistant with the real backend | ✅ new, reproducible |
|
||||
| `design-system/` | React component library `@audi-dash/ui`, brand-free, feeds Claude Design | ✅ done as a kit (20 components, 1,690 lines) |
|
||||
@@ -216,10 +216,11 @@ the repo.
|
||||
|
||||
**`companion-app/` — what exists:** the full app. Data layer (`src/api/`: REST, WebSocket with
|
||||
reconnect backoff, persistent offline write queue, credential storage), domain logic
|
||||
(`src/daten/`: profile adapter, statistics, service forecast, data context), all 21 screens
|
||||
(`src/screens/`), Audi assets (`src/assets/audi/`), PWA manifest and icons. Verified by 90 unit
|
||||
and render tests plus 9 checks against a live Home Assistant. `npm run dev` in the repo root
|
||||
starts it; `testumgebung/aufsetzen.sh` provides the server side.
|
||||
(`src/daten/`: profile adapter, statistics, service forecast, data context, OTA update flow — see
|
||||
section H), all 21 screens (`src/screens/`), Audi assets (`src/assets/audi/`), PWA manifest and
|
||||
icons. Verified by 117 unit and render tests (`npm test`) plus the two smoke suites against a live
|
||||
Home Assistant (`npm run smoke`, `npm run smoke:auth`). `npm run dev` in the repo root starts it;
|
||||
`testumgebung/aufsetzen.sh` provides the server side.
|
||||
|
||||
**DataMetric360 architecture (short — details in `COMPANION_APP_ARCHITECTURE.md`):**
|
||||
- Future data source: **Teltonika FMM003** on the CAN bus, fully replacing the iPhone WLAN sensor
|
||||
@@ -1814,10 +1815,20 @@ HACS 2.0.5 rather than from memory:
|
||||
`custom_templates/` and `appdaemon/apps/<name>/` (verified in `repositories/*.py` → `localpath`).
|
||||
The app needed `/config/pyscript/` **and** `/config/audi_dashboard/` **and** `configuration.yaml`
|
||||
entries. This is what the conversion solved.
|
||||
- **HACS is GitHub-only.** `github.com`/`api.github.com` are hardcoded throughout; the repo lives
|
||||
on `gitea.nothaft.cloud`. This is **not** solved and cannot be solved from this side — it needs
|
||||
the repo mirrored to GitHub. Until then `homeassistant/installationspaket/install.ps1` installs
|
||||
the identical folder without HACS, so the conversion is useful either way.
|
||||
- **HACS is GitHub-only.** `github.com`/`api.github.com` are hardcoded throughout; the repo lived
|
||||
on `gitea.nothaft.cloud`. 2026-08-24: the owner mirrored it to `github.com/T130B/DM360`, and the
|
||||
manifest URLs/codeowners now point there — but this does **not** make HACS usable. **HACS
|
||||
categorically refuses private repositories**, confirmed against its own docs
|
||||
(hacs.xyz/docs/faq/private_repositories: "Private GitHub repositories can not be used with HACS
|
||||
at all... HACS can only get publicly available information") — no token, no signed-in-account
|
||||
exception. An earlier note in this file claimed the opposite ("HACS must be signed in with the
|
||||
account that owns it"); that was wrong and is corrected here. The GitHub mirror is **private on
|
||||
purpose** (it carries the Audi typeface and the model badges, licensed for this one private
|
||||
install only), so making it public to satisfy HACS is not on the table.
|
||||
`homeassistant/installationspaket/install.ps1` is therefore not a fallback — it is **the only
|
||||
installation path**, and the docs (`README.md`, `homeassistant/INSTALL.md`,
|
||||
`installationspaket/ANLEITUNG.md`, `VERSIONIERUNG.md`, the script's own header) were rewritten
|
||||
2026-08-24 to say so instead of presenting a HACS path that cannot work.
|
||||
|
||||
**The shape.** `custom_components/audi_dashboard/` at repo root (HACS looks for
|
||||
`custom_components/<first dir>` there and nowhere else), `hacs.json` beside it. Config flow, one
|
||||
@@ -1904,8 +1915,14 @@ left to add), `ANLEITUNG.pdf` (stale and not regenerable here; the `.md` beside
|
||||
**Do not resurrect the pyscript tree.** If something is missing, port it — running both backends
|
||||
writes two sets of trips into the same files.
|
||||
|
||||
**Still open, unchanged by this:** the repo must reach GitHub before HACS can install it. Everything
|
||||
else about the HACS path is built and tested.
|
||||
**HACS is not the delivery path — confirmed dead-end, not a gap.** 2026-08-24, checked against
|
||||
HACS's own docs: private repos are refused categorically, no exception. The layout is still HACS
|
||||
shape (`custom_components/audi_dashboard/` + `hacs.json` at repo root, manifest with `domain`,
|
||||
`name`, `version`, `documentation`, `issue_tracker`, `codeowners`) in case the repo is ever
|
||||
restructured to separate the licensed Audi assets from the installable code — but there is no
|
||||
current plan to do that, and `install.ps1` is not standing in for HACS temporarily; it is the
|
||||
permanent, only path. Don't spend effort re-verifying HACS compatibility for this repo again
|
||||
unless the private/public situation changes.
|
||||
|
||||
### The deployed-parity problem, and what was built for it (2026-08-23)
|
||||
|
||||
@@ -1913,8 +1930,8 @@ The parity rule (binding, above) guarantees *source* parity: both codebases chan
|
||||
session. It guarantees nothing about what is *running*. After the conversion the two sides are
|
||||
wired differently in a new way:
|
||||
|
||||
- the panel ships **inside** the integration — one HACS update moves backend and panel together,
|
||||
and it cannot go stale at all
|
||||
- the panel ships **inside** the integration — one `install.ps1` run (the only delivery path — HACS
|
||||
is a dead end here, see section H) moves backend and panel together, and it cannot go stale at all
|
||||
- the companion app is a Capacitor shell with `webDir: "dist"` and **bundled** assets, sideloaded
|
||||
through Xcode. It stays on whatever was bundled at signing time, silently, indefinitely
|
||||
|
||||
@@ -1928,15 +1945,40 @@ silently on a format change. Either side missing yields `"unbekannt"` and **no**
|
||||
older backend or an offline start cannot produce a false alarm. And the vite build **fails hard** if
|
||||
the manifest version is absent rather than emitting an app that cannot detect its own staleness.
|
||||
|
||||
**OTA delivery: verified viable, not yet built.** `@capgo/capacitor-updater` 8.51.14 checked against
|
||||
the real package: MPL-2.0, peer `@capacitor/core: ^8.0.0` against our `^8.5.0`, and self-hosting is
|
||||
first-class (`updateUrl`, or manual mode entirely). Manual mode is the good fit —
|
||||
`download({version, url})` + `set()` against a plain zip under `/local/`, needing **no** custom
|
||||
endpoint at all, with automatic rollback to the last good bundle via `notifyAppReady()`. It pairs
|
||||
exactly with the version entity above: that entity is already the signal that a newer bundle exists.
|
||||
Owner approved going this route, and a paid Apple developer account is available through Paul (Paul
|
||||
Nothaft, the Gitea repo owner) — which is what makes it worthwhile, since the free account's 7-day
|
||||
signature expiry would otherwise force Xcode weekly anyway.
|
||||
**OTA delivery: built and verified end to end, 2026-08-24.** `@capgo/capacitor-updater` 8.51.14
|
||||
installed (MPL-2.0, peer `@capacitor/core: ^8.0.0` against our `^8.5.0`). Manual mode, not
|
||||
`autoUpdate` (that stays `false` on purpose — an update must never swap the UI out from under
|
||||
someone mid-entry): `companion-app/src/daten/ota.ts` wraps `download({url, version, checksum})` +
|
||||
`set({id})`, wired to a button in the Einstellungen screen (`otaUpdateVerfuegbar` /
|
||||
`otaAusloesen`). `notifyAppReady()` runs from a `useEffect` in `AngemeldeteApp` (`App.tsx`) —
|
||||
deliberately there and not in `main.tsx`, so a crash before React actually renders never gets
|
||||
confirmed and the plugin's `appReadyTimeout` rollback (`capacitor.config.ts`, 20s, more generous
|
||||
than the 10s default because first load also fetches profile/trips/refuels) takes over.
|
||||
|
||||
The bundle itself lives **inside the integration**, not under `/local/` as first sketched:
|
||||
`custom_components/audi_dashboard/frontend/app/{bundle.zip,bundle.json}`, built by
|
||||
`companion-app/scripts/ota-paket.ps1`, served at `/audi_dashboard_static/app/bundle.zip`. Reason
|
||||
for the relocation: `/local/` would have needed its own delivery step that `install.ps1` doesn't
|
||||
touch and could be forgotten; inside the integration folder it travels with every install/update
|
||||
automatically, same as the panel. `sensor.audi_dashboard_app_version`'s `daten` gained a `buendel`
|
||||
field (`koordinator._buendel_lesen()`) carrying `{version, sha256, bytes, gebaut, url}` — `null`
|
||||
when no bundle is published, a normal state, not an error.
|
||||
|
||||
One real bug the build caught: `[IO.Compression.ZipFile]::CreateFromDirectory` on Windows
|
||||
PowerShell 5.1 (.NET Framework, not Core) writes **backslashes** as path separators for nested
|
||||
entries — a spec-violating zip that iOS would not have unpacked correctly. Caught by inspecting the
|
||||
actual zip contents after the first build, not assumed; fixed by writing entries by hand via
|
||||
`ZipFile::Open` + `CreateEntryFromFile` with `\` replaced by `/`. The plugin verifies SHA-256 over
|
||||
the *downloaded zip itself* before unpacking (`CapgoUpdater.swift` `calcChecksum`, confirmed by
|
||||
reading the plugin source, not the README) — `ota-paket.ps1` hashes the same file it writes, so the
|
||||
two can't drift.
|
||||
|
||||
Verified against the running test container: entry reload picks up code changes but **not** a
|
||||
manifest version bump (HA caches the `Integration` object — needs a full restart, same lesson as
|
||||
the pyscript-removal step in section G); after a real restart, `sensor.audi_dashboard_app_version`
|
||||
published the new manifest version and a complete `buendel` block; the zip served over HTTP hashed
|
||||
identically to `bundle.json`'s `sha256`; `unzip -l`/`-t` confirmed 13 entries, `index.html` at the
|
||||
root, no backslashes, no corruption.
|
||||
|
||||
Capacitor's `server.url` pointed at HA was considered and **rejected**: it would make the app as
|
||||
current as the panel, but the shell then cannot boot without reaching HA, gutting the deliberately
|
||||
|
||||
Reference in New Issue
Block a user