Where we stand on getting Sheaf Android into the two distribution channels users will look for it in, and what's blocking each one. This is a working document; update it as items get checked off.
- GitHub releases:
- Rolling
devtag: every push tomasterproduces a cosign-signed.openAPK pair (phone + watch). - Tagged
vX.Y.Zreleases: produce signed.openAPKs publicly and unsigned prod APK + AAB privately as a workflow artefact for the offline signing ceremony.
- Rolling
- Google Play: in progress. Developer account approved; tagged-release pipeline ready; first
v0.1.0upload pending. - F-Droid (main repo): blocked by a proprietary dependency. See the F-Droid section.
- F-Droid (IzzyOnDroid third-party repo): plausible without code changes.
Two applicationIds are produced from the same source tree:
| Flavour | applicationId |
Distribution | Signing |
|---|---|---|---|
.open (-PopenBuild=true) |
systems.lupine.sheaf.open |
GitHub releases, IzzyOnDroid | CI keystore (rotatable, kept in CI secrets) |
| prod (default) | systems.lupine.sheaf |
Google Play | YubiKey-resident production key, signed offline |
The two install side-by-side on a device — different package names = different APK identities. The CI keystore never signs the systems.lupine.sheaf namespace, and the production key never touches CI.
versionCode is derived per-build:
- Tagged releases (
v*.*.*):MAJOR*10000 + MINOR*100 + PATCH. v0.1.0 → 100, v1.0.0 → 10000. Caps at v99.99.99 → 999999. Bump the scheme before that bites. - Dev (
masterhead):1000000 + git rev-list --count HEAD. Always strictly greater than any tagged release, so.openusers can keep updating from dev without ever being blocked by a tagged release downgrade.
versionName mirrors the tag for tagged releases (0.1.0); dev uses 0.0.0-dev.<short-sha>.
tagged-release.yml triggers on v*.*.* tag pushes and emits two sets of artefacts:
- Public GitHub release (the tag):
.openAPKs (phone + watch), each cosign-signed (*.sig+*.pem). This is what IzzyOnDroid pulls and what direct-download users install. - Private workflow artefact (
prod-unsigned-vX.Y.Z, 90-day retention): unsigned prod APK + AAB (phone + watch), each cosign-signed. Only repo collaborators withactions:readcan fetch it viagh run download. Operational details for fetching, verifying, signing with the YubiKey, and uploading to Play live in the private release-ops repo.
The cosign signature over the unsigned prod bytes is the chain-of-custody from "CI built this commit" to "this is the exact byte sequence the offline ceremony then signed".
- Privacy policy URL plumbing. Policy text exists; in-app pointer + Play Console URL still needed.
- Store listing assets. Icon (already present), feature graphic 1024x500, phone screenshots (recommend 4–8), Wear screenshots, short description (80 chars), full description (4000 chars). Same assets reused for both stores plus IzzyOnDroid where applicable.
- Reproducible builds. Not strictly required by either store but a F-Droid badge and a meaningful improvement to the verifiability story. Currently blocked by typical AGP non-determinism (timestamps, ZIP entry order, baseline profiles). Tracked in
docs/VERIFYING.md.
- Google Play Developer account. Approved (LLC tier, skips the 20-day closed-testing gate that applies to personal accounts).
- Play App Signing decision. Opted out — production signing key lives on YubiKeys held by the maintainers, AABs are signed offline (jarsigner + PKCS#11). The cosign chain attests to the exact unsigned bytes that get YubiKey-signed.
- Content rating. IARC questionnaire complete.
- Data Safety form. Submitted; mirrors the Android privacy policy (no app-side telemetry, instance-side data is the operator's responsibility).
- App access information. Reviewer test account provided.
- Target API level.
targetSdk = 35(Android 15) — current. - Pre-launch report compliance. First upload will surface this. Expect to need to fix at least a couple of edge cases.
Use the Play tracks ladder:
- Internal testing (up to 100 testers, no review needed for first push) — wire up to the tagged-release workflow.
- Closed testing (named groups) — useful for the broader plural community testers.
- Open testing (any user can opt in) — public beta channel.
- Production — staged rollout (start at 5–10%) so issues surface before they're at scale.
First-time submissions go through a more thorough review, often 3–7 days. Subsequent updates usually clear in 24–48 hours unless the listing changes.
The app and Wear module both depend on com.google.android.gms:play-services-wearable (the Wearable Data Layer SDK), which is proprietary. F-Droid's main repo policy excludes apps with non-libre runtime dependencies.
Three plausible paths:
- Product flavors. Define
playandfdroidflavors inapp/build.gradle.kts, with a stubplay-services-wearableshim in thefdroidflavor. Thefdroidflavor would lose phone↔watch credential sync; users on F-Droid would have to log in on the watch directly. Practical but adds maintenance overhead. - Drop Wear OS support from the F-Droid build. Same as flavor approach but more aggressive: ship only the phone APK on F-Droid. Cleanest if the watch IPC is hard to abstract.
- Don't target F-Droid main, target IzzyOnDroid instead (next section). Lowest effort.
Recommendation: start with IzzyOnDroid, plan the flavor split as a follow-on if F-Droid main is genuinely needed by the user base.
- FLOSS license. AGPL-3.0-or-later.
- Public source. Repo is public.
- Domain-derived application ID.
systems.lupine.sheaf(lupine.systems). - No proprietary tracking/ads SDKs. None present.
-
fdroiddatarecipe. YAML metadata file atmetadata/systems.lupine.sheaf.ymlin thefdroiddatarepo, describing build commands, versions, and theBinaries:field pointing at our GitHub release APKs (so F-Droid can extract the upstream signature for theAllowedAPKSigningKeyscheck). - Anti-features declared. Likely
NonFreeDepon the Play-flavor build if we go the flavor route. PossiblyNonFreeNetdepending on F-Droid's interpretation of self-hostable network apps; usually fine because the server is libre. - Funding metadata. F-Droid surfaces donation links if
funding.jsonor.github/FUNDING.ymlis present, or a clearly-marked donation link in the README. None set up yet. -
AllowedAPKSigningKeysconfigured. Once we have a stable release keystore, extract the cert SHA-256 withapksigner verify --print-certsand add it to the fdroiddata recipe.
If we eventually go the F-Droid main route, RB earns a green badge and means F-Droid's build matches our published APK byte-for-byte, so users can install ours directly without re-trusting F-Droid's. Steps:
- Pin all build-tool versions (NDK if any, AGP, Kotlin, Gradle).
- Disable PNG crushing and resource shrinker if they introduce non-determinism.
- Strip embedded build paths and timestamps. R8 mappings should be deterministic given a fixed seed.
- Use
apksignerwith explicit signing args. - Run
diffoscopebetween two builds on different machines to find remaining drift.
IzzyOnDroid is a popular third-party F-Droid repo with looser rules: it allows some non-FLOSS dependencies, doesn't require source-built APKs, and accepts the upstream-signed releases directly from GitHub.
- Open a request at https://gitlab.com/IzzyOnDroid/repo/-/issues referencing our GitHub releases.
- Provide an icon, short and long descriptions, and the cosign verification commands from
docs/VERIFYING.md(Izzy users may want to verify upstream-signed APKs). - Maintain a stable release cadence; Izzy auto-pulls new tagged releases once configured.
This is the lowest-friction path to a curated F-Droid listing, and it preserves the Wear OS functionality. Recommended first move once a real v0.1.0 tagged release exists.
- Privacy policy + tagged-release workflow + versionCode strategy + AAB build + store listing assets. (Cross-cutting; gates everything else.)
- Cut a
v0.1.0tag, sanity-check the signed AAB and APK outputs. - Submit to IzzyOnDroid (low friction, no code changes).
- Apply for Google Play Developer account, complete listing, push to internal track for soak.
- Decide whether F-Droid main is worth the flavor split. If yes, do the work and PR
fdroiddata. - Pursue reproducible builds as a separate, lower-priority track.