Nothing updates this app on its own. It is downloaded as an APK from a web page, so there is no store to notice a release and no background updater to install one — a person can run a build from months ago indefinitely, through every fix that came after it, and never find out. That is the part of sideloading that is genuinely worse than a store, and this is the only thing that closes it. The background check asks the RELAY the install is signed in to, not a fixed address of ours. Pointing every install at flamenetmessenger.com would mean an app whose whole claim is that it does not phone home checking in with us on a timer, from every device, including the self-hosted ones that deliberately never contact us at all. A relay that publishes no manifest simply produces no prompt. "Check for updates" in Account is the other half, and the reason the fallback to our site exists: pressing it is a question the user asked, so answering "your relay does not publish updates" when we plainly could is unhelpful. It tries the relay first and names whichever host answered, so it is never a silent phone-home. It also distinguishes "no newer build" from "could not check" — reporting silence as good news is how somebody sits on a build with a fix in it believing otherwise. The download URL is built from the host that answered and never read from the manifest. A `url` field would let any relay send its users to an APK on a host of its choosing; Android's signature check is a backstop, not a reason to build the hole in front of it. A test asserts a hostile manifest cannot move it, and fails if the guard is removed. versionCode is bumped and `scripts/release-manifest.sh` reads it back out of the built APK, so the manifest and the artifact cannot drift. ⚠️ Nobody already installed sees this: every build before this one has no check in it, so the first prompt anybody gets is for the build after this one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
1.8 KiB
Bash
Executable File
44 lines
1.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Write the update manifest for a built APK.
|
|
#
|
|
# 🔴 The version in the manifest is read back OUT of the APK, never typed. A
|
|
# manifest that claims a version the APK is not is worse than no manifest: it
|
|
# either offers an update that installs as the same build (and so appears to do
|
|
# nothing), or fails to offer one that exists. Reading it from the artifact is
|
|
# what makes the two incapable of disagreeing.
|
|
#
|
|
# Usage: scripts/release-manifest.sh <apk> [notes] > android-latest.json
|
|
set -euo pipefail
|
|
|
|
apk="${1:?usage: release-manifest.sh <apk> [notes]}"
|
|
notes="${2:-}"
|
|
|
|
[ -f "$apk" ] || { echo "no such APK: $apk" >&2; exit 1; }
|
|
|
|
# aapt2 ships with the build tools; find the newest one rather than pinning a
|
|
# version that will be wrong on the next machine.
|
|
sdk="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
|
|
aapt2="$(find "$sdk/build-tools" -maxdepth 2 -name aapt2 -type f 2>/dev/null | sort -V | tail -1)"
|
|
[ -n "$aapt2" ] || { echo "aapt2 not found under $sdk/build-tools" >&2; exit 1; }
|
|
|
|
badging="$("$aapt2" dump badging "$apk")"
|
|
code="$(sed -n "s/.*versionCode='\([0-9]*\)'.*/\1/p" <<<"$badging" | head -1)"
|
|
name="$(sed -n "s/.*versionName='\([^']*\)'.*/\1/p" <<<"$badging" | head -1)"
|
|
[ -n "$code" ] && [ -n "$name" ] || { echo "could not read version from $apk" >&2; exit 1; }
|
|
|
|
sha="$(shasum -a 256 "$apk" | cut -d' ' -f1)"
|
|
|
|
# ⚠️ No `url` field, deliberately. The client builds the download URL from the
|
|
# relay it is signed in to and ignores anything the manifest says about where
|
|
# the APK lives — a relay that could name the host could hand its users a
|
|
# different app. Adding one here would be dead weight at best and a trap for
|
|
# the next person to implement a client at worst.
|
|
cat <<JSON
|
|
{
|
|
"versionCode": $code,
|
|
"versionName": "$name",
|
|
"sha256": "$sha",
|
|
"notes": $(python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$notes")
|
|
}
|
|
JSON
|