tane/docs/fdroid/reproducible-post-merge.md
vjrj 85a75ac3b7
All checks were successful
ci / test-commons-core (push) Successful in 51s
ci / analyze (push) Successful in 1m35s
site / deploy (push) Successful in 34s
ci / test-app-seeds (push) Successful in 6m40s
docs(fdroid): post-merge runbook for reproducible developer-signed builds
2026-07-19 11:36:15 +02:00

3.9 KiB
Raw Blame History

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-uploadsigned 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.

#!/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-<abi>-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-<abi>-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.