Skip to content

Commit 9e39e63

Browse files
awhitchurchclaude
andcommitted
Add docs/mobile-deployment.md
Notes for the mobile session: what iOS/Android deployment actually looks like today (iOS is manual from a laptop, Android has no pipeline at all), the keystore rotation that is now overdue, the iOS local-network bug that likely breaks Wi-Fi mode, and F-Droid feasibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 19aca93 commit 9e39e63

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

docs/mobile-deployment.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Mobile deployment — current state and what's missing
2+
3+
Written 2026-07-14, alongside the v3.0.0 desktop release. Desktop (Linux /
4+
Windows / macOS) now builds and publishes from `.github/workflows/release.yml`.
5+
**Mobile does not ship from CI at all.** This is the working notes for that.
6+
7+
OpenView is already live on both stores, so nothing here is greenfield:
8+
9+
- App Store (iPhone / iPad / Apple Silicon Mac) — `id1667747246`, `com.protocentral.openview`
10+
- Google Play — `com.protocentral.openview`
11+
12+
## Where mobile deployment actually stands
13+
14+
**iOS — manual, from a developer Mac.** `ios.sh` runs `flutter build ios
15+
--release --no-codesign` then `fastlane ios beta`, and `ios/fastlane/Fastfile`
16+
does `build_app``upload_to_testflight`. That works, but:
17+
18+
- It is not wired into any workflow. Every release is somebody's laptop.
19+
- `ios/fastlane/Appfile` authenticates as a personal Apple ID
20+
(`ashwinkw@ieee.org`). CI needs an **App Store Connect API key** (issuer ID,
21+
key ID, `.p8`) instead — an Apple ID login with 2FA cannot run unattended.
22+
- There is no `match`/certificate management, so signing assets live only on
23+
that one Mac.
24+
25+
**Android — nothing.** No fastlane, no Play publishing, no signed build.
26+
`distribution/whatsnew/whatsnew-en-US` is a leftover from a
27+
`r0adkll/upload-google-play` setup that no longer exists in this repo — nothing
28+
reads it today.
29+
30+
## Fixed already (found while getting v3.0.0 out)
31+
32+
- **A Java keystore was committed**`android/akw-newkey`, in the initial
33+
commit, in a public repo. Removed from HEAD and `*.jks`/`key.properties`/`*.p12`
34+
are now gitignored. **The key is still public in git history and in the 14
35+
forks — it must be rotated.** See "Key rotation" below.
36+
- **`flutter build apk --release` was broken in CI**: an undefined GitHub
37+
Actions secret is exported as an *empty string*, so
38+
`System.getenv("KEYSTORE_BASE64") != null` was true with no secret set, and
39+
Gradle wrote a zero-byte keystore. Now tests for blank.
40+
- **`flutter build apk --release` OOM'd**`JetifyTransform` ran out of heap at
41+
`-Xmx1536M`. Now `-Xmx4g`, and `android.enableJetifier=false` (every plugin
42+
here is AndroidX already). *Unverified — no Android build has run since.*
43+
44+
## Key rotation (do this first)
45+
46+
The exposed keystore is almost certainly your Play **upload** key, not the app
47+
signing key — if Play App Signing is on, Google holds the signing key and the
48+
upload key is resettable.
49+
50+
1. Play Console → Test and release → Setup → **App integrity** → App signing.
51+
2. If Play App Signing is enabled: **Request upload key reset**, upload a new
52+
upload certificate. Old key stops working; users are unaffected.
53+
3. If it is *not* enabled, the leaked key signs your published app — that is the
54+
bad case, and it needs an app signing key upgrade (new installs only).
55+
56+
A backup of the removed keystore is at `~/.protocentral-keys/akw-newkey.jks` on
57+
Ashwin's machine. Treat it as burned.
58+
59+
## To deploy Android from CI
60+
61+
1. New release keystore, kept out of the repo. Secrets: `KEYSTORE_BASE64`,
62+
`KEY_ALIAS`, `KEY_PASSWORD`, `STORE_PASSWORD`. (The `key.properties` plumbing
63+
in `android/app/build.gradle.kts` already reads these.)
64+
2. `flutter build appbundle --release` for Play; `flutter build apk --release`
65+
only if you also want a sideload asset on the GitHub release.
66+
3. Play service account JSON → `PLAY_SERVICE_ACCOUNT_JSON` secret →
67+
`r0adkll/upload-google-play` with `track: internal`, promoting manually.
68+
`distribution/whatsnew/` becomes the `whatsNewDirectory` again.
69+
70+
Note: a GitHub-released APK can never upgrade a Play-installed app in place —
71+
Play delivers APKs signed with Google's app signing key. That is expected, not a
72+
bug, and it is independent of which keystore we choose.
73+
74+
## To deploy iOS from CI
75+
76+
1. App Store Connect API key → secrets (`ASC_KEY_ID`, `ASC_ISSUER_ID`,
77+
`ASC_KEY_P8`). Replace the `apple_id` auth in `Appfile`.
78+
2. Signing assets into CI — `fastlane match` (private certs repo) is the usual
79+
answer; otherwise import a distribution `.p12` + provisioning profile from
80+
secrets, like the macOS job does.
81+
3. `macos-latest` runner, `flutter build ipa` (or fastlane `build_app`), then
82+
`upload_to_testflight`.
83+
84+
## Bugs to fix before the next mobile release
85+
86+
- **`NSLocalNetworkUsageDescription` is missing from `ios/Runner/Info.plist`.**
87+
`lib/transport/wifi_service.dart` is a raw TCP client to a LAN `host:port`. On
88+
iOS 14+ that requires the local-network permission, and without the usage
89+
string iOS never prompts and refuses the connection — **Wi-Fi mode is probably
90+
broken on iOS today**. Worth reproducing on a device first.
91+
- `NSAppTransportSecurity.NSAllowsArbitraryLoads = true` is a blanket ATS
92+
exemption and invites App Store review questions. If it is only there for the
93+
LAN TCP transport, `NSAllowsLocalNetworking` is the narrower answer.
94+
- `ITSEncryptionExportComplianceCode` is set to the string `"Yes"`, which is not
95+
a compliance code. With `ITSAppUsesNonExemptEncryption = false` it should just
96+
be removed.
97+
- iOS deployment target is inconsistent: `Podfile` says 13.1, the Runner target
98+
says 15.3, `Info.plist` `LSMinimumSystemVersion` says 13.0.
99+
- `AndroidManifest.xml` declares `ACCESS_FINE_LOCATION` / `ACCESS_COARSE_LOCATION`
100+
*and* removes the `-sdk-23` variants, while `BLUETOOTH_SCAN` is already
101+
`neverForLocation`. If location is not actually needed, dropping it simplifies
102+
the Play data-safety declaration.
103+
- `WRITE_EXTERNAL_STORAGE` / `READ_EXTERNAL_STORAGE` are declared with no
104+
`maxSdkVersion` — Play flags this on modern targets.
105+
- Cosmetic: `MainActivity.kt` declares `package com.protocentral.openview` but
106+
lives in `.../kotlin/com/protocentral/healthypiconnect/`. Stale
107+
`MARKETING_VERSION = 2.0.15` / `CURRENT_PROJECT_VERSION = 85` in
108+
`ios/Runner.xcodeproj` are harmless (Info.plist uses `$(FLUTTER_BUILD_NAME)` /
109+
`$(FLUTTER_BUILD_NUMBER)`, so Flutter's pubspec version wins).
110+
111+
## F-Droid
112+
113+
Customers have asked for it. It is feasible, and the licence is not the problem
114+
— the repo is MIT, there is no telemetry, no ads, no Firebase.
115+
116+
**The one blocker is `geolocator`.** It is declared in `pubspec.yaml` but
117+
**imported nowhere in `lib/`**, and `geolocator_android` pulls in
118+
`com.google.android.gms:play-services-location` — a proprietary dependency
119+
F-Droid will not build against. Deleting the dependency removes the blocker,
120+
shrinks the APK, and drops Play Services entirely. That is a one-line change
121+
plus a `flutter pub get`, and it is worth doing regardless of F-Droid.
122+
123+
After that, F-Droid inclusion is a metadata exercise, not an engineering one:
124+
125+
1. Verify `flutter build apk --release` works from a clean checkout (needs the
126+
Gradle heap fix above to be confirmed).
127+
2. Submit a metadata YAML merge request to
128+
[fdroiddata](https://gitlab.com/fdroid/fdroiddata) with a build recipe using
129+
the Flutter srclib, pinned to our Flutter version, plus
130+
`UpdateCheckMode: Tags` and `AutoUpdateMode: Version` so new git tags are
131+
picked up automatically.
132+
3. Expect review round-trips on the first MR.
133+
134+
Effort: roughly a day for the first submission, then near-zero per release
135+
(F-Droid builds from tags). Two things to tell users up front: F-Droid signs
136+
with **its own key**, so the F-Droid build and the Play build cannot upgrade each
137+
other — switching stores means uninstall/reinstall — and F-Droid builds land
138+
days after the Play release, because they build and sign on their own
139+
infrastructure.

0 commit comments

Comments
 (0)