media-updates/README.md
Richard 2fc0981195 Somewhere for the app to look for a newer version of itself
Two static files behind nginx and a script that writes them. No
authentication: there is no account to check against, and the APK carries
no secret — the credential in that app is the addon URL, which lives in
the device keystore.

The APKs are committed rather than stored elsewhere. At ~34MB and a few
releases a year that beats standing up artifact storage for one app, and
the history is disposable because only the newest artifact is ever
fetched.

release.py reads the version out of amber-adult's pubspec instead of
taking it as an argument. A manifest advertising a build number the APK
does not carry is an update the device installs and is then offered
again, every launch, forever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 03:53:38 +02:00

1.3 KiB

media-updates

What the amber-adult app checks at launch to find a newer build of itself.

Two files behind nginx: manifest.json, and the APK it names. No authentication, deliberately — there is no account to check against and the APK holds no secret. The credential in that app is the addon URL, which lives in the device keystore and never leaves it.

Publishing a release

./release.py ../amber-adult/build/app/outputs/flutter-apk/app-release.apk "what changed"

It reads the version and build number out of amber-adult/pubspec.yaml, copies the APK in under a versioned name, writes manifest.json, and commits. Push, and Coolify redeploys.

The APKs are committed. At ~34MB and a handful of releases a year that is the cheaper trade against standing up artifact storage for one app; if the repository ever gets uncomfortable, the history is disposable — only the newest artifact is ever fetched.

The manifest

{
  "version": "0.1.0",
  "buildNumber": 1,
  "notes": "…",
  "sha256": "…64 hex…",
  "size": 34210000,
  "url": "https://media-updates.petruzalekr.cz/media-1.apk"
}

The client refuses anything it cannot act on: an equal or older buildNumber, a missing url, or a sha256 that is not 64 characters. The download is checked against that digest before it reaches the installer.