Amber ships one build. The manifest hook returned `adult` or `clean` according to the account's nsfwEnabled flag; it now returns `clean` to everyone. The `variant` field itself stays in the response, and that is the important part: every Amber already installed compares it against its own and refuses a mismatch SILENTLY - no error, no prompt, the update simply never appears. Removing the field, or sending anything else, would strand every one of those installs with nothing to see. whoami stops returning nsfwEnabled. It was there in case some future amber-api surface wanted to scope results by it; none was built, and an account flag nobody reads is what a token-introspection endpoint should not be handing out. amber-api's copy goes with it - it was parsed into the user object and never once branched on. The dashboard loses the 18+ account tile, the variant column, the per-flavour error table, the 18+ pill, the per-user enable/disable button and the new-account checkbox. A control that sets a flag nothing reads is worse than no control. Releases are now read as newest-per-platform filtered to variant='clean', rather than newest-per-platform-and-variant. Leftover 18+ rows are still in the collection and would otherwise have been reported as what the family is being served. Those rows and the collection rule that hides them are deliberately left alone: the rule is what keeps them unreachable, and deleting published artifacts is not something to do as a side effect of a cleanup. check-flavor.py is gone. It proved which of two builds an artifact was, by a marker compiled into the Dart snapshot, because a clean-named Windows installer once carried the 18+ payload. With one build there is nothing to tell apart.
4.5 KiB
Auto-update — contract (issue #16)
The app checks for a newer build and installs it. One variant ships, clean;
the server used to pick between that and an adult one from the account's
nsfwEnabled flag, and no longer does.
Server pieces (this repo):
pb_migrations/1786500000_releases.js— thereleasescollection.pb_hooks/update.pb.js— theGET /api/update/manifestroute.scripts/publish-release.sh— uploads a built artifact as a superuser.
Client pieces (amber-app): lib/data/update/update_service.dart (version check +
manifest fetch), the desktop swap-installer, and the Android APK install channel.
releases collection
| Field | Type | Notes |
|---|---|---|
platform |
select | windows | linux | android. |
variant |
select | clean | adult. Only clean is published now. The field stays because every installed app compares it against its own and refuses a mismatch silently — removing it would strand those installs with no error to see. |
version |
text | Human semver shown to the user, e.g. 1.0.1. |
buildNumber |
number | Monotonic int; the comparator the updater actually uses. |
file |
file | The artifact: windows = .exe installer, linux = .zip of the release bundle, android = .apk. Protected (see rules). |
sha256 |
text | Lowercase hex SHA-256 of the artifact; verified before install. |
size |
number | Bytes. |
notes |
text | Optional release notes (shown in the update prompt). |
created/updated |
autodate |
Unique index on (platform, variant, buildNumber).
Access rules. Read is gated:
@request.auth.id != '' && (variant = 'clean' || @request.auth.nsfwEnabled = true)
— any signed-in account reads clean rows; only an nsfwEnabled account reads
adult rows. That rule is deliberately left in place: adult rows from before
the split are still in this collection, and the rule is what keeps them out of
reach. The file field is protected: true, which is what gates the bytes:
a protected file is served only with a short-lived file token
(POST /api/files/token) whose grant re-checks the view rule above (and
without the flag the file URL would be public regardless of the view rule). create/update/delete
are superuser-only (null rules); publishing goes through the admin API.
Endpoint
GET /api/update/manifest?platform=windows|linux|android (auth: users)
Returns the latest build for the caller's platform, always variant: "clean".
- No release for that platform →
{ "available": false }. - Otherwise:
{ "available": true, "platform": "linux", "variant": "clean", "version": "1.0.1", "buildNumber": 2, "notes": "…", "sha256": "<64 hex>", "size": 12345678, "filename": "amber-linux-clean.zip", "downloadPath": "/api/files/releases/<recordId>/<filename>" }
Download. The app mints a file token (POST /api/files/token, its user
auth) and GETs {base}{downloadPath}?token=<fileToken>, then verifies sha256
before installing.
Publishing a build
Build the artifact, then (on the build machine):
PB_ADMIN_TOKEN=<superuser token> ./scripts/publish-release.sh \
--platform linux --variant clean --version 1.0.1 --build 2 \
--file build/amber-linux-clean.zip --notes "What changed"
PB_ADMIN_EMAIL + PB_ADMIN_PASSWORD work instead of a token. The script
computes the SHA-256 + size and uploads via the superuser REST API. Re-publishing
the same (platform, variant, buildNumber) is rejected by the unique index —
bump buildNumber for each release.
Windows publishes the installer, amber-setup-<variant>-<version>.exe, not
the zip. One artifact serves both jobs: people download and run it, and the app's
updater runs the same file with /SILENT, which is why DesktopInstaller
dispatches on the extension. The zip release_windows.ps1 still builds beside it
is for unpacking by hand — do not publish it.
Gating summary
There is nothing left to gate between: one variant is published, and the manifest hook returns it to everyone.
nsfwEnabled still exists on the users record and the collection rules still
read it. Both stay because adult rows from before the split are still in the
collection, and those rules are what keeps them unreachable. Nothing writes the
flag any more: the manifest hook ignores it, whoami no longer returns it, and
the status dashboard's toggle is gone.