thermograph/infra/deploy/twa/README.md

87 lines
3.7 KiB
Markdown
Raw Permalink Normal View History

# Thermograph Android app (Trusted Web Activity)
The Android app is a **Trusted Web Activity (TWA)**: a thin native shell that opens
`https://thermograph.org` full-screen in the user's Chrome engine. It *is* the live
PWA — no content is duplicated, and the existing service worker + VAPID **web push
keeps working** inside the app via Android notification delegation (no Firebase, no
backend change). `enableNotifications: true` in `twa-manifest.json` turns that on
and wires the Android 13+ `POST_NOTIFICATIONS` runtime permission.
This folder holds the build config. **Building the APK is a local/manual step**
it needs a JDK, the Android SDK, and a signing keystore that only you should hold.
The current goal is a **signed APK you sideload for testing**; Play Store
submission ($25 one-time) is deferred.
## What's in the repo
- `twa-manifest.json` — Bubblewrap config (reference values; see comment inside).
docs: correct file references and the dev reachability claim Audited the five CLAUDE.md files and all twenty-one README.md files against the tree, machine-checking every in-repo path they name and verifying the testable claims against the live hosts. The one that matters is in the root file: dev was documented as reachable on the mesh at 10.10.0.2:8137. It is not, and never was from anywhere but vps1 — infra/docker-compose.yml binds the port to 127.0.0.1, and the address answers from neither vps2 nor vps1 itself. Anyone following it gets a connection refused with nothing to explain it. The rest are stale paths, several from the reunification: * assetlinks.json moved under frontend/static/ in the subtree merge; the TWA README kept the pre-merge path in both places it names it. Following it would put the file where nothing serves it and Android app-link verification would fail silently. * push.py and notify.py now live in backend/notifications/. * INFRA.md and deploy/stack/README have never existed in this repo, in any branch. * the Caddyfile is at deploy/stack/lb/Caddyfile. * three bare relative paths that resolve for a reader but not from the directory the file sits in: units.js is the frontend's, deploy.sh is infra's, entrypoint.sh is the backend's. Also records why mesh clients must pin the ROOT_URL host and not only the image host: the registry's bearer-token realm follows ROOT_URL, so pinning git.thermograph.org alone still sends the token request out the public route, where the /v2/* matcher returns 403 and docker falls back to anonymous. That surfaces as `unauthorized: reqPackageAccess`, indistinguishable from a bad credential. Verified true and left alone: the four-domain layout, both .claude runbooks, the absence of any domain-level .forgejo directory, the pinned compose project name, the deploy contract, prod's eight stack services, beta's five prefixed ones with no db of its own, dev's five, and every documented make target.
2026-08-01 18:49:28 +00:00
- `../../../frontend/static/.well-known/assetlinks.json` — Digital Asset Links, served at
`https://thermograph.org/.well-known/assetlinks.json`. Its fingerprint is a
**placeholder** until you generate the signing key (step 4).
## One-time build (you)
Prereqs: Node 18+, JDK 17. Bubblewrap can download the Android SDK for you.
```bash
npm install -g @bubblewrap/cli
# 1. Scaffold from the live manifest (creates ./twa-manifest.json + a keystore).
# Accept defaults, then reconcile with this repo's twa-manifest.json — in
# particular set: packageId = org.thermograph.twa, enableNotifications = true.
bubblewrap init --manifest https://thermograph.org/manifest.webmanifest
# 2. (Or) copy this repo's config and let Bubblewrap fill the SDK bits:
# cp deploy/twa/twa-manifest.json ./twa-manifest.json && bubblewrap update
# 3. Build the signed APK (and .aab for the store, for later).
bubblewrap build
# -> ./app-release-signed.apk and ./app-release-bundle.aab
```
### 4. Wire Digital Asset Links (required — or the app opens with a URL bar)
```bash
# Print the signing key's SHA-256 fingerprint:
bubblewrap fingerprint list # or: keytool -list -v -keystore android-keystore.jks
```
docs: correct file references and the dev reachability claim Audited the five CLAUDE.md files and all twenty-one README.md files against the tree, machine-checking every in-repo path they name and verifying the testable claims against the live hosts. The one that matters is in the root file: dev was documented as reachable on the mesh at 10.10.0.2:8137. It is not, and never was from anywhere but vps1 — infra/docker-compose.yml binds the port to 127.0.0.1, and the address answers from neither vps2 nor vps1 itself. Anyone following it gets a connection refused with nothing to explain it. The rest are stale paths, several from the reunification: * assetlinks.json moved under frontend/static/ in the subtree merge; the TWA README kept the pre-merge path in both places it names it. Following it would put the file where nothing serves it and Android app-link verification would fail silently. * push.py and notify.py now live in backend/notifications/. * INFRA.md and deploy/stack/README have never existed in this repo, in any branch. * the Caddyfile is at deploy/stack/lb/Caddyfile. * three bare relative paths that resolve for a reader but not from the directory the file sits in: units.js is the frontend's, deploy.sh is infra's, entrypoint.sh is the backend's. Also records why mesh clients must pin the ROOT_URL host and not only the image host: the registry's bearer-token realm follows ROOT_URL, so pinning git.thermograph.org alone still sends the token request out the public route, where the /v2/* matcher returns 403 and docker falls back to anonymous. That surfaces as `unauthorized: reqPackageAccess`, indistinguishable from a bad credential. Verified true and left alone: the four-domain layout, both .claude runbooks, the absence of any domain-level .forgejo directory, the pinned compose project name, the deploy contract, prod's eight stack services, beta's five prefixed ones with no db of its own, dev's five, and every documented make target.
2026-08-01 18:49:28 +00:00
Copy the `SHA256` value into `frontend/static/.well-known/assetlinks.json`, replacing
`REPLACE_WITH_TWA_SIGNING_KEY_SHA256_FINGERPRINT`, then **deploy the site** so the
new file is live. Confirm:
```bash
curl -s https://thermograph.org/.well-known/assetlinks.json # must show your fingerprint, Content-Type: application/json
```
> If you later publish to Play Store with **Play App Signing**, add *Google's* app
> signing certificate fingerprint (from the Play Console) to `assetlinks.json` too —
> the upload-key fingerprint alone won't verify the store build.
### 5. Sideload and test
```bash
adb install app-release-signed.apk
```
On the device:
- Launch Thermograph — it must open **full-screen with no browser address bar**
(that confirms assetlinks verified).
- Subscribe to a city (or trigger a test alert) and confirm the "unusual weather"
push arrives as a **native Android notification**.
- Grant the notifications permission when prompted (Android 13+).
## Keep the keystore safe
`android-keystore.jks` is required for **every future update** and for the
eventual Play Store listing. Losing it means a new package identity and a fresh
install for every user. Store it out of the repo, backed up.
## Deferred
- **Play Store listing** ($25 one-time Google Play developer account). Upload the
`.aab`; the store review for a well-formed TWA is light. Not part of this pass.
## Why no backend changes
docs: correct file references and the dev reachability claim Audited the five CLAUDE.md files and all twenty-one README.md files against the tree, machine-checking every in-repo path they name and verifying the testable claims against the live hosts. The one that matters is in the root file: dev was documented as reachable on the mesh at 10.10.0.2:8137. It is not, and never was from anywhere but vps1 — infra/docker-compose.yml binds the port to 127.0.0.1, and the address answers from neither vps2 nor vps1 itself. Anyone following it gets a connection refused with nothing to explain it. The rest are stale paths, several from the reunification: * assetlinks.json moved under frontend/static/ in the subtree merge; the TWA README kept the pre-merge path in both places it names it. Following it would put the file where nothing serves it and Android app-link verification would fail silently. * push.py and notify.py now live in backend/notifications/. * INFRA.md and deploy/stack/README have never existed in this repo, in any branch. * the Caddyfile is at deploy/stack/lb/Caddyfile. * three bare relative paths that resolve for a reader but not from the directory the file sits in: units.js is the frontend's, deploy.sh is infra's, entrypoint.sh is the backend's. Also records why mesh clients must pin the ROOT_URL host and not only the image host: the registry's bearer-token realm follows ROOT_URL, so pinning git.thermograph.org alone still sends the token request out the public route, where the /v2/* matcher returns 403 and docker falls back to anonymous. That surfaces as `unauthorized: reqPackageAccess`, indistinguishable from a bad credential. Verified true and left alone: the four-domain layout, both .claude runbooks, the absence of any domain-level .forgejo directory, the pinned compose project name, the deploy contract, prod's eight stack services, beta's five prefixed ones with no db of its own, dev's five, and every documented make target.
2026-08-01 18:49:28 +00:00
Android delegates the TWA's web notifications to the OS, so
`backend/notifications/push.py` (VAPID) and `backend/notifications/notify.py`
deliver push to the app unchanged. Nothing in
`backend/` is touched by this track.