6.1 KiB
Reproducible Build Notes — ai.opencode.mobile
F-Droid's modern AllowedAPKSigningKeys path requires that F-Droid's build
server can compile the same APK and arrive at a binary that matches the
pre-signed APK we supply via GitHub releases. Any non-determinism in the build
will break this verification.
Issues found (2026-05-24)
1. Kotlin error log files tracked in git — MEDIUM
Files committed:
android/.kotlin/errors/errors-1779181311003.log
android/.kotlin/errors/errors-1779181311094.log
Problem: These log files contain absolute host paths:
While analysing /home/azureuser/workspace/opencode-mobile/node_modules/...
When F-Droid builds from source on their server, these log files will not exist (or will contain different paths). Since they are tracked in git and checked out during the build, they could cause differing build outputs if the Kotlin compiler reads or embeds them. More practically, they make the source tree non-portable — a smell that will draw reviewer attention.
Recommended fix: Add .kotlin/ to android/.gitignore:
# android/.gitignore (add this line)
.kotlin/
Then remove the tracked files:
git rm -r --cached android/.kotlin/
git commit -m "chore: untrack kotlin error log files from android/.kotlin/"
This is a trivial fix. Do it before filing the F-Droid MR.
2. android/ directory tracked in git — LOW (expected but notable)
expo prebuild regenerates android/ from app.json and package.json.
F-Droid's build metadata uses npx expo prebuild as a prebuild: step,
which means F-Droid rebuilds android/ from scratch on their server.
The tracked android/app/build.gradle and other generated files must match
what expo prebuild produces. If the Expo SDK version drifts between what is
committed and what npm installs, the build will fail.
Mitigation already in place: package-lock.json is committed, which pins
all npm dependency versions. The F-Droid metadata Builds: step uses
npm install --legacy-peer-deps which respects package-lock.json.
Residual risk: If expo prebuild is non-deterministic (e.g., writes the
current date/time into generated files), subsequent runs will produce different
outputs. This is unlikely but should be verified by running prebuild twice and
comparing outputs:
npx expo prebuild --platform android --non-interactive --clean
git diff android/
3. Hermes bytecode embedding — LOW
The React Native Hermes engine compiles the JavaScript bundle to Hermes bytecode
at build time. The bytecode format is versioned but should be deterministic for
the same JS source + Hermes version. The Hermes version is pinned via
react-native in package-lock.json, so this is low risk.
4. PNG crunching — LOW
build.gradle has crunchPngs true for release builds. PNG crunching via aapt2
is generally deterministic but can vary across aapt2 versions. F-Droid's build
environment may use a different Android build tools version.
Mitigation: Pin buildToolsVersion in android/build.gradle explicitly
rather than relying on the Expo-supplied default. Check via:
grep buildToolsVersion android/build.gradle android/app/build.gradle
5. No hardcoded timestamps found — PASS
Grepped android/ for System.currentTimeMillis, new Date(), buildTime,
BUILD_DATE, UUID.randomUUID() — no results. This is the most common
reproducibility killer and is clean here.
6. No absolute host paths in build files — PASS
Grepped android/ *.gradle and *.properties for /home/, /Users/,
C:\ — no results in build config files.
Priority action items before F-Droid MR
| Priority | Item | Effort |
|---|---|---|
| HIGH | Add .kotlin/ to android/.gitignore and untrack log files |
5 min |
| MEDIUM | Run expo prebuild twice, compare output with git diff |
15 min |
| MEDIUM | Pin buildToolsVersion explicitly in android/build.gradle |
5 min |
| LOW | Verify Hermes bytecode is deterministic (compare two builds) | 30 min |
| LOW | Test full reproducible build using F-Droid's Docker build env | Hours |
How to test reproducible builds
F-Droid provides a reproducible build test tool:
# Install fdroidserver
pip install fdroidserver
# Test reproducibility against a released APK
fdroid signatures path/to/app-release.apk
# Full build test
fdroid build ai.opencode.mobile:<versionCode> --verbose
See https://f-droid.org/en/docs/Reproducible_Builds/ for the full guide.
Update (2026-07-17) — separate -fdroid release asset (issue #95)
Root cause of the original mismatch: Binaries: pointed at app-release.apk,
which is built WITHOUT the Builds:.prebuild patches (it still has Firebase,
Play Install Referrer, and Sentry). F-Droid's from-source rebuild applies
those patches, so the two binaries diverge structurally and fdroid build's
reproducibility check can never match — stripping the patches from the main
APK instead would have removed push notifications and crash reporting for
every Play/GitHub/IzzyOnDroid user, which is not acceptable.
Fix shipped in .github/workflows/build.yml (build-fdroid job, tag-release
only): build a second, additional artifact — app-release-fdroid.apk —
applying the exact same patch commands as Builds:.prebuild below, signed
with the same production keystore (v1+v2 only, matching publish-fdroid.yml's
existing androguard workaround), and attach it to the GitHub Release next to
the untouched app-release.apk. Binaries: now points at the -fdroid asset.
The main build job is unmodified — no feature loss for non-F-Droid users.
Remaining human step: this environment cannot run fdroid build (needs
fdroidserver + a maintainer's Docker/Linux environment — see "How to test
reproducible builds" above). Before relying on this for a real fdroiddata
submission or an AllowedAPKSigningKeys update, a maintainer must run
fdroid build ai.opencode.mobile:<versionCode> --verbose (or the fdroiddata
CI equivalent) against a tagged release that has an app-release-fdroid.apk
asset, and confirm the from-source rebuild matches it byte-for-byte.