You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a Jeeves release engineer, I want every Android artifact we ship — Firebase App Distribution drops, GitHub Releases APK/AAB, and any future Play Store / Solana dApp Store upload — to be signed with a stable, project-owned release key, so that upgrades install over previous builds, the Play upload key never rotates underneath us, and we stop relying on an ephemeral debug key generated fresh on every CI runner.
Problem
app/android/app/build.gradle.kts:44-50 currently signs the release build type with the debug signing config:
buildTypes {
release {
// TODO: Add your own signing config for the release build.// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
None of the workflows (.github/workflows/flutter-ci.yml, pr-apk.yml, cd-app.yml) install a stored keystore or load any signing secret. The Android SDK auto-generates ~/.android/debug.keystore on first use, so each GitHub Actions runner ends up with its own ephemeral debug key.
Concrete consequences:
Per-PR APKs distributed via Firebase App Distribution (pr-apk.yml:97-103) cannot be installed over a previous build from a different runner without uninstall-first.
The production release APK + AAB built by cd-app.yml:259-267 and uploaded to GitHub Releases / Firebase App Distribution are debug-signed, with a different key per run.
The Seeker APK (cd-app.yml:342-349) destined for the Solana dApp Store has the same problem.
The AAB cannot be uploaded to Google Play — debug keys are rejected for upload, and even if they weren't, the key would change on every run.
Scope
Generate a project-owned release keystore (and a separate upload keystore for Play if/when we publish there — Play App Signing then handles the distribution key).
Store the keystore + credentials as GitHub Actions secrets:
ANDROID_KEYSTORE_BASE64 (the .jks file, base64-encoded)
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD
In app/android/, read credentials from key.properties (gitignored) and a real signingConfigs.release { ... } block; wire buildTypes.release.signingConfig = signingConfigs.getByName("release") in app/android/app/build.gradle.kts.
Allow local flutter run --release to keep working when key.properties is absent — fall back to debug signing only in that case, never on CI.
In CI:
cd-app.yml (build-android, build-seeker): decode the secret into app/android/keystore.jks, write key.properties, then build.
pr-apk.yml: same setup so tester upgrades work seamlessly (the dev flavor APK should also be signed with a stable key — separate dev key alias if we want it isolated from prod).
flutter-ci.ymlandroid-build job stays debug-signed (smoke test only).
Document the rotation process and the secret list in docs/RELEASES.md.
Add a CI guard that fails the prod build if key.properties was not produced (i.e. secrets missing) — silent fall-back to debug on prod is the failure mode we're fixing.
Acceptance Criteria
app/android/app/build.gradle.kts defines a signingConfigs.release block backed by key.properties, and buildTypes.release uses it.
cd-app.yml produces a release APK + AAB signed with the project's release key; apksigner verify --print-certs shows the same SHA-256 fingerprint across two consecutive runs.
The Seeker variant (build-seeker) is signed with the same release key.
pr-apk.yml produces APKs signed with a stable key (dev or release); a tester can install build N+1 over build N without uninstalling.
Local flutter run --release still works on a developer machine without key.properties (debug fallback only when key.properties is missing).
Prod CD fails fast with a clear error if signing secrets are not configured — no silent debug-signing on the published artifacts.
docs/RELEASES.md documents the keystore, the GitHub Actions secrets, and the rotation procedure.
key.properties and *.jks are gitignored.
Out of Scope
Enabling Play App Signing or actually publishing to the Google Play Store (separate issue when we're ready to ship to Play).
Submitting to the Solana dApp Store (separate issue; this one just ensures the Seeker APK is signed with a stable key when we get there).
User Story
As a Jeeves release engineer, I want every Android artifact we ship — Firebase App Distribution drops, GitHub Releases APK/AAB, and any future Play Store / Solana dApp Store upload — to be signed with a stable, project-owned release key, so that upgrades install over previous builds, the Play upload key never rotates underneath us, and we stop relying on an ephemeral debug key generated fresh on every CI runner.
Problem
app/android/app/build.gradle.kts:44-50currently signs the release build type with the debug signing config:buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") } }None of the workflows (
.github/workflows/flutter-ci.yml,pr-apk.yml,cd-app.yml) install a stored keystore or load any signing secret. The Android SDK auto-generates~/.android/debug.keystoreon first use, so each GitHub Actions runner ends up with its own ephemeral debug key.Concrete consequences:
pr-apk.yml:97-103) cannot be installed over a previous build from a different runner without uninstall-first.cd-app.yml:259-267and uploaded to GitHub Releases / Firebase App Distribution are debug-signed, with a different key per run.cd-app.yml:342-349) destined for the Solana dApp Store has the same problem.Scope
ANDROID_KEYSTORE_BASE64(the.jksfile, base64-encoded)ANDROID_KEYSTORE_PASSWORDANDROID_KEY_ALIASANDROID_KEY_PASSWORDapp/android/, read credentials fromkey.properties(gitignored) and a realsigningConfigs.release { ... }block; wirebuildTypes.release.signingConfig = signingConfigs.getByName("release")inapp/android/app/build.gradle.kts.flutter run --releaseto keep working whenkey.propertiesis absent — fall back to debug signing only in that case, never on CI.cd-app.yml(build-android,build-seeker): decode the secret intoapp/android/keystore.jks, writekey.properties, then build.pr-apk.yml: same setup so tester upgrades work seamlessly (the dev flavor APK should also be signed with a stable key — separatedevkey alias if we want it isolated from prod).flutter-ci.ymlandroid-buildjob stays debug-signed (smoke test only).docs/RELEASES.md.key.propertieswas not produced (i.e. secrets missing) — silent fall-back to debug on prod is the failure mode we're fixing.Acceptance Criteria
app/android/app/build.gradle.ktsdefines asigningConfigs.releaseblock backed bykey.properties, andbuildTypes.releaseuses it.cd-app.ymlproduces a release APK + AAB signed with the project's release key;apksigner verify --print-certsshows the same SHA-256 fingerprint across two consecutive runs.build-seeker) is signed with the same release key.pr-apk.ymlproduces APKs signed with a stable key (dev or release); a tester can install build N+1 over build N without uninstalling.flutter run --releasestill works on a developer machine withoutkey.properties(debug fallback only whenkey.propertiesis missing).docs/RELEASES.mddocuments the keystore, the GitHub Actions secrets, and the rotation procedure.key.propertiesand*.jksare gitignored.Out of Scope
Related
app/android/app/build.gradle.kts:44-50— current TODO..github/workflows/cd-app.yml,.github/workflows/pr-apk.yml,.github/workflows/flutter-ci.yml.