Local APK build: support assembleRelease + signing config #165
Labels
No labels
bug
documentation
duplicate
e-copy
e-features
e-mobile
enhancement
f-coverage
f-forensic
f-perf
f-privacy
forensic
good first issue
help wanted
infra
invalid
phase-a
phase-b
phase-c
phase-d
phase-e
phase-f
phase-g
phase-h
priority-1
priority-2
priority-3
privacy
question
v5
v6
video-hardening
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: forgejo_admin/exifcleaner-web#165
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 0 — Keystore preparation (do this before any code changes)
Before writing the gradle/script changes in Phases 1-4 below, the maintainer needs to make four decisions and generate a keystore. The SHA-256 fingerprint of this keystore becomes part of the app's identity forever — every future install for
com.metascrub.appchecks against it. A lost keystore means no further APK can install as an update on top of an existing one (users would have to uninstall first), and there is no recovery mechanism.Decisions
Validity period.
keytool -validitydefault is 90 days — far too short.-validity 36500(100 years).Keystore location. Trades off against
.claude/rules/privacy-invariants.md§5 (no telemetry, no cloud sync) and CI ergonomics:Recommendation: age-encrypted in the repo at
android/metascrub-release.jks.age, with the decryption key in Forgejo Secrets for CI and in the maintainer's offline backup for manual builds. The encrypted file in git makes "every clone can build a release" achievable; the offline backup of the decryption key keeps the keystore itself recoverable.Backup strategy. Must align with privacy invariants §5 — no cloud sync, no transmission.
Rotation policy. APK Signature Scheme v3+ supports key rotation, but the new key must be signed by the old key. This is another reason the original keystore backup is load-bearing — even rotation requires the original. Document the rotation procedure now even if you never use it; once the original is lost, rotation is no longer possible.
Outputs of Phase 0
metascrub-release.jks(the file itself stays out of git; only the age-encrypted form is committed if option 1 was chosen)docs/android-signing.mdcovering:keytool -genkey -vcommand used (no secrets, just the public parameters: alias, keyalg, keysize, validity)keytool -list -alias metascrub -keystore metascrub-release.jksoutput, for users who want to verify the APK they downloaded)Only after Phase 0 is complete do Phases 1-4 below have a concrete artifact to wire up. The
signingConfigs.releaseblock inandroid/app/build.gradleand the--releaseflag inscripts/build-apk-local.shboth depend on the location decision.Background
scripts/build-apk-local.sh(added in #163) and.github/workflows/build-android.yml(added in #156) both run./gradlew assembleDebug, producingapp-debug.apksigned with the developer's per-machine debug keystore. This is the right default for personal sideloading and for the on-demand CI builds we have today.It is not sufficient for:
app-release-unsigned.apkis what their build server expects)The
Signed release APKsection ofdocs/android-apk.mdalready documents thekeytool+signingConfigs.releaseflow but it's a manual recipe — nothing inandroid/app/build.gradleis configured to actually pick up a keystore today.Scope
signingConfigs.releaseintoandroid/app/build.gradle— read keystore path + alias + passwords from environment variables (METASCRUB_KEYSTORE_FILE,METASCRUB_KEYSTORE_ALIAS,METASCRUB_KEYSTORE_PASSWORD,METASCRUB_KEY_PASSWORD) so the keystore is never committed.scripts/build-apk-local.shwith a--releaseflag that:./gradlew assembleReleaseinstead ofassembleDebugandroid/app/build/outputs/apk/release/app-release.apk).github/workflows/build-android.ymlwith arelease_buildboolean dispatch input that wires up the keystore from Forgejo Secrets when set. Keep the defaultfalseso the existing workflow_dispatch shape is unchanged.docs/android-apk.md— promote the signed-release section to a peer of the Local Docker build section and document the four env vars + how to generate the keystore.Out of scope
F-Droidsection of the doc)noper.claude/rules/project-direction.md)Acceptance criteria
./scripts/build-apk-local.sh --releaseproducesapp-release.apkwhen the env vars are set, withapksigner verifyreporting a valid v1+v2+v3 signature against the configured keystore./scripts/build-apk-local.shinvocation still produces the sameapp-debug.apkit does today — no regression on the personal-sideload pathapksigner verify --print-certs app-release.apkshows the project keystore's fingerprint, not the per-developer debug keykeytool -genkeyinvocation, env var reference, and warning that the keystore is not recoverable if lost (rotating signing keys breaks update installs for every existing user)References
docs/android-apk.md§ Signed release APK.claude/rules/project-direction.md— confirms APK is for personal sideloading + F-Droid, not Play Store