From 85a75ac3b75effdf1f323b81fe02b69927b5d39a Mon Sep 17 00:00:00 2001 From: vjrj Date: Sun, 19 Jul 2026 11:36:15 +0200 Subject: [PATCH] docs(fdroid): post-merge runbook for reproducible developer-signed builds --- docs/fdroid/reproducible-post-merge.md | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/fdroid/reproducible-post-merge.md diff --git a/docs/fdroid/reproducible-post-merge.md b/docs/fdroid/reproducible-post-merge.md new file mode 100644 index 0000000..902f154 --- /dev/null +++ b/docs/fdroid/reproducible-post-merge.md @@ -0,0 +1,75 @@ +# F-Droid reproducible, developer-signed builds — post-merge follow-up + +The initial F-Droid submission is **F-Droid-signed** (F-Droid builds from source and +signs with its own key). Making F-Droid publish *our* tane-upload–signed APK +(same signature as Google Play, so users move between stores) is a **post-merge** +step, because F-Droid derives `SOURCE_DATE_EPOCH` from the **commit timestamp of +our metadata file inside fdroiddata** — a value that only exists once the MR is +merged. So a byte-matching reference cannot be produced beforehand. + +## Why our own CI couldn't do this pre-merge + +- Deps are NOT the problem: F-Droid uses the **standard public repos** + (`mavenCentral()` + Google's maven) — no private mirror. `jsr305` and the old + jcenter artifacts are on Maven Central. The build environment that "just works" + is the public image `registry.gitlab.com/fdroid/fdroidserver:buildserver-trixie` + (bundles the Android SDK + NDK); we verified a full build succeeds in it. +- The real blocker is `SOURCE_DATE_EPOCH` (fdroidserver `get_source_date_epoch` + falls back to the fdroiddata metadata commit, since the app isn't checked out + yet when it's read). Unknown until F-Droid commits our metadata. + +## Post-merge procedure + +Run this **after** `org.comunes.tane` is merged into fdroiddata, from a clone of +the **merged** fdroiddata (so the metadata commit — and thus SOURCE_DATE_EPOCH — +matches what F-Droid used). Requires Docker and the tane-upload keystore. + +```bash +#!/bin/bash +set -eu +IMG=registry.gitlab.com/fdroid/fdroidserver:buildserver-trixie +FDROIDDATA=/path/to/merged/fdroiddata # git clone of gitlab.com/fdroid/fdroiddata (or fork synced past merge) +KS=/home/vjrj/proyectos/sync/comunes/shared-l2/apkSigning/tane-upload.jks +VC_BASE=10 # pubspec versionCode base (0.1.x+N -> N) + +docker run --rm -v "$FDROIDDATA":/build -v "$KS":/tmp/ks.jks:ro \ + -e KSPASS -e KEYPASS -w /build "$IMG" bash -c ' + set -e + sh /opt/buildserver/setup-env-vars /opt/android-sdk + . /etc/profile.d/bsenv.sh + fds=/tmp/fds; mkdir -p "$fds" + curl -sL https://gitlab.com/fdroid/fdroidserver/-/archive/master/fdroidserver-master.tar.gz \ + | tar -xz -C "$fds" --strip-components=1 + export PATH="$fds:$PATH" PYTHONPATH="$fds:$fds/examples" + git config --global --add safe.directory "*" + # Build the three per-ABI splits exactly as F-Droid does (SOURCE_DATE_EPOCH + # comes from THIS merged fdroiddata metadata commit -> matches F-Droid). + fdroid build --verbose --no-tarball \ + org.comunes.tane:$((VC_BASE*10+1)) \ + org.comunes.tane:$((VC_BASE*10+2)) \ + org.comunes.tane:$((VC_BASE*10+3)) + apksigner=$(find "$ANDROID_HOME" -name apksigner -type f | sort -V | tail -1) + for f in unsigned/org.comunes.tane_*.apk; do + vc=${f##*_}; vc=${vc%.apk} + case $((vc%10)) in 1) abi=armeabi-v7a;; 2) abi=arm64-v8a;; 3) abi=x86_64;; esac + "$apksigner" sign --ks /tmp/ks.jks --ks-key-alias tane-upload \ + --ks-pass "env:KSPASS" --key-pass "env:KEYPASS" \ + --out "/build/app-${abi}-release.apk" "$f" + "$apksigner" verify --print-certs "/build/app-${abi}-release.apk" | grep -i SHA-256 + done + ' +``` + +Set `KSPASS`/`KEYPASS` in the environment before running (never echo them). + +Expected: `Signer #1 certificate SHA-256 digest:` = +`ddfae432091b8248a8a4a1b353487fa626301f4357ef835e94ec312f69418e38` +(the tane-upload cert = the future `AllowedAPKSigningKeys` value). + +Then: +1. Upload the three `app--release.apk` to a git.comunes.org release for the tag. +2. Add to the recipe: `AllowedAPKSigningKeys: ddfae432…` and per-build + `binary: https://git.comunes.org/comunes/tane/releases/download/v%v/app--release.apk`. +3. Open a follow-up MR. F-Droid re-runs `fdroid build`, gets byte-identical output, + verifies our signature, and switches to publishing our signed APK. +4. If `fdroid verify` still differs, keep it F-Droid-signed — no harm.