Skip to content

Use a fixed release keystore for Android signing (CI signs with ephemeral debug keys) #300

@TMaYaD

Description

@TMaYaD

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-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.yml android-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).
  • iOS code signing (see iOS support: run Jeeves natively on iPhone/iPad #123).

Related

Metadata

Metadata

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions