Files
opencode-mobile/distribution/fdroid-submission/REPRODUCIBLE-BUILD-NOTES.md
Den 2b9b571d6e feat(privacy+dist): telemetry consent gate + app store distribution prep (#4)
* fix(security): fail closed on biometric init error

H-03: setting isAuthenticated: true on initialization failure was a
security bypass — any crash during biometric setup granted full access.
Fail closed instead; user sees auth prompt on next open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): use Crypto.randomUUID for connection IDs

H-04: Math.random() is not cryptographically random. Connection IDs are
used as SecureStore key suffixes; switch to expo-crypto randomUUID for
a secure source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): pin expo-crypto to ~15.0.9

15.0.10 does not exist on npm; ~15.0.9 is the latest stable in the 15.x series compatible with Expo SDK 54.

* feat: add OpenCode Connect coming-soon waitlist card

Adds a discoverable 'OpenCode Connect — Coming Soon' card to the
add-connection quick-connect screen. Users can enter their email and
tap 'Join Waitlist' to send a pre-filled mailto. No backend required.

* fix(cua): detect actual screen dimensions and fix JSON parsing

- Get real screen size via `wm size` instead of hardcoding 1080x2400;
  emulator is 1080x1920 so y-coordinates were systematically off
- Extract first JSON object via regex when model returns multiple objects
- Use AZURE_OPENAI_MODEL env var for deployment name (defaults gpt-5.4)
- Add AZURE_DEV_AI_* path for Azure AI Foundry endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): SHA-pin upload-google-play and sanitize notification bodies

M-02: Pin r0adkll/upload-google-play to commit SHA e738b9d (v1.1.5)
to prevent supply-chain hijack via tag mutation.

M-03: Sanitize all push notification bodies — strip control chars,
truncate to 200 chars. Prevents server-supplied strings (error messages,
file paths from permission patterns, session titles) from leaking
unbounded text into the OS notification drawer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(privacy): add telemetry consent gate for Sentry crash reporting

Sentry was always-on, violating F-Droid anti-feature policy and user
trust norms. Now gated behind explicit opt-in:

- First-launch consent modal (TelemetryConsentModal) shows once on
  fresh install; user can Allow or Decline.
- Consent state persisted in expo-secure-store (survives restarts).
- Settings > Privacy section: crash reporting toggle + privacy policy link.
- initSentry() called only after consent granted — not on app start.

Closes #3 (partial)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(config): add real icons and complete iOS/Android app.json config

- Add 1024×1024 app icon, 432×432 adaptive icon foreground, 200×200 splash
- iOS: push notification entitlement (aps-environment: production), speech/
  microphone/camera/photo usage descriptions for future features, disable
  ITSAppUsesNonExemptEncryption
- Android: adaptive icon with dark background (#0F172A), versionCode: 1
- expo-notifications plugin wired in app.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(dist): add iOS CI workflow, README rewrite, CONTRIBUTING, and LICENSE

- publish-app-store.yml: EAS Build + TestFlight submission; runs on tag/release/
  workflow_dispatch; bumps ios.buildNumber from github.run_number
- README: full rewrite — features, install badges, connection guide, contributing
- CONTRIBUTING.md: contribution guide for OSS contributors
- LICENSE: MIT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(dist): add store listings, strategy, privacy policy, F-Droid/IzzyOnDroid templates

- distribution/strategy.md: monetization strategy (free client + opencode Cloud)
- distribution/play-listing.md: Google Play store copy (name, description, tags)
- distribution/app-store-listing.md: App Store listing copy
- distribution/privacy-policy.{md,html}: GDPR-compliant privacy policy
- distribution/PLAY_CONSOLE_SETUP.md: Play Console setup runbook
- distribution/ios-enrollment-runbook.md: Apple Developer Program enrollment steps
- distribution/SIGNING-KEY-FINGERPRINTS.md: keystore fingerprint for reproducible builds
- distribution/fdroid-submission/: F-Droid metadata template
- distribution/izzyondroid-submission/: IzzyOnDroid submission template
- distribution/whatsnew/: Play Store release notes (en-US)
- distribution/whatsnew-ios/: TestFlight release notes
- distribution/play-graphics/: Play Store screenshot placeholders
- distribution/app-store-graphics/: App Store screenshot placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(telemetry): handle SecureStore failure + Android back button

- add .catch() on loadTelemetryConsent() so SecureStore rejection
  shows the consent modal instead of blocking startup forever
- add onRequestClose={onDecline} to Modal so Android back button
  records the decline rather than silently dismissing
- fix catch block in telemetry.ts to not clobber _resolved when
  SecureStore read fails mid-session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): run gradlew clean to prevent stale modules.json duplicate

Sentry Gradle plugin writes modules.json to src/main/assets; cached
build intermediates contain an old copy → mergeReleaseAssets fails
with 'Duplicate resources'. Running clean before assembleRelease
clears the intermediate state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): remove android build output cache causing duplicate modules.json

Caching android/app/build/intermediates and android/app/.cxx causes
two issues:
1. Stale modules.json in intermediates → Duplicate resources error
2. .cxx CMake artifacts reference absolute paths → ninja clean fails

Keeping only Gradle distribution cache (~/.gradle) which is safe.
Expo prebuild regenerates android sources fresh each run anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:42:03 -07:00

138 lines
4.5 KiB
Markdown

# 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:
```bash
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:
```bash
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:
```bash
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:
```bash
# 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.