7.5 KiB
Android signing and updates
Every published APK uses com.flectar.mail and the certificate pinned in
platform/android/app-signing-certificate.sha256. This includes alpha, beta,
release-candidate, and stable versions, starting with alpha 8. Keep the original
private key and certificate for the lifetime of this update path. Alpha 6
and alpha 7 used different identities and cannot update to this one.
The certificate's historical subject (Flectar Mail Test) and alias
(androiddebugkey) are names, not a measure of key security. The pin identifies
the actual certificate. Re-encrypting the existing keystore with a strong
password keeps the certificate and private key unchanged.
One-time setup from the existing alpha key
Requirements: Python 3.11+, a JDK with keytool (Java 21 recommended), and
authenticated GitHub CLI access with permission to manage repository
environments and secrets. Keep keystores outside the checkout.
From the repository root:
python3 scripts/configure-android-signing.py \
"$HOME/.local/share/flectar-mail/android/alpha-signing.keystore" \
--output "$HOME/.local/share/flectar-mail/android/app-signing.p12" \
--upload
Use the path to your existing backup if it is elsewhere. The current
legacy keystore password is android; press Enter for the same key password.
Choose and save a new password of at least 16 characters in your password
manager. The script does not display or save passwords in plaintext.
The script verifies the original certificate and access to its private key, imports that same entry into a new PKCS12 file with the new password, and verifies it again. It refuses to overwrite an existing destination. The original file is retained until you have verified your secure backup.
With --upload, it creates the android-release environment if absent,
restricts that environment to the repository's default branch and v* tags,
and sets these four environment secrets:
| Secret | Value |
|---|---|
FLECTAR_ANDROID_KEYSTORE_BASE64 |
Base64 encoding of the re-encrypted keystore |
FLECTAR_ANDROID_KEYSTORE_PASSWORD |
New keystore password |
FLECTAR_ANDROID_KEY_ALIAS |
Existing alias, normally androiddebugkey |
FLECTAR_ANDROID_KEY_PASSWORD |
New private-key password; same as the store password for this PKCS12 file |
An existing environment's protection rules are preserved. An environment with no branch restrictions is rejected. Review the environment's allowed branches and tags when configuring a different repository. An empty edit field on GitHub does not show whether a stored secret has a value: saved secret values are not returned for inspection. Base64 is encoding, not encryption.
The old repository secret FLECTAR_ANDROID_TEST_KEYSTORE_BASE64 belongs to
older workflow revisions. After the four environment secrets are configured
and the new workflows are merged, remove the old repository secret so the
same private key is no longer available outside the protected environment:
gh secret delete FLECTAR_ANDROID_TEST_KEYSTORE_BASE64 --repo flectar/mail
Do not dispatch releases from older workflow revisions after this migration;
some of those revisions could generate a replacement key when a secret was
missing. The setup script leaves the old secret in place so it can be run
before merging without breaking the workflow currently on main.
If upload fails after the new file was created, retry without re-encrypting:
python3 scripts/configure-android-signing.py \
"$HOME/.local/share/flectar-mail/android/app-signing.p12" --upload
Enter the new password this time. To update the secrets manually after configuring the environment:
set -o pipefail
base64 < "$HOME/.local/share/flectar-mail/android/app-signing.p12" |
gh secret set FLECTAR_ANDROID_KEYSTORE_BASE64 --repo flectar/mail --env android-release
gh secret set FLECTAR_ANDROID_KEYSTORE_PASSWORD --repo flectar/mail --env android-release
gh secret set FLECTAR_ANDROID_KEY_ALIAS --repo flectar/mail --env android-release
gh secret set FLECTAR_ANDROID_KEY_PASSWORD --repo flectar/mail --env android-release
The final three commands prompt for the corresponding value. Do not pass passwords on the command line or paste them into issues, chat, or logs.
Release behavior
Both workflows restore and validate the shared key before compilation, use
scripts/build-android-release.sh, verify the finished APK against the same
pin, and remove the runner's keystore before emulator tests. Signing passwords
are supplied only to the restore and build steps. Pull request checks use
temporary fixture keys and never receive the release environment's secrets.
The native artifact workflow publishes the signed APK for both prereleases
and stable tags. --preview permits missing bundled OAuth registrations only
for prerelease versions; it never changes the signing key. The production
candidate workflow requires complete OAuth registrations. Both emit an APK
and an AAB into target/android/release/; the native release publishes the APK.
Version codes are derived by scripts/android-version-code.py:
major × 100,000,000 + minor × 1,000,000 + patch × 10,000 + channel
channel: alpha.N = N; beta.N = 1,000 + N; rc.N = 2,000 + N; stable = 9,999
For example, 0.1.0-alpha.8 is 1000008 and 0.1.0 is 1009999.
The supported ranges are major 0–20, minor and patch 0–99, and prerelease
number 1–999; values outside these ranges fail rather than collide. Every
released version must be greater than the previous release on the update path.
Rebuilding the same version produces the same code; use a new version for a
new Play upload. Do not supply unrelated workflow run numbers or reset codes
when moving to stable.
Local scripts/build-android-test-apk.sh builds may still use the conventional
android password for a developer-only key. CI redirects that entry point to
the pinned release builder, and no published workflow uses the developer key.
Backups, recovery, and Google Play
Store the re-encrypted keystore in a secure backup outside this machine and GitHub, and store its password in your password manager. Test the backup with the verification/setup command before retiring the legacy copy. GitHub secrets are not a downloadable backup. If a secret is deleted, restore the same keystore and credentials. Never regenerate the key or change the pin to make a failed release pass.
When enrolling in Play App Signing, supply this existing app signing key so that Play and GitHub APKs have the same signing identity. Having Google generate a different app key would break this update path. Configure a separate upload key for later AAB submissions; that key authorizes uploads and must not replace the key signing GitHub APKs. The current AAB is signed with the app key for initial enrollment; separate upload-key automation must be configured when Play is introduced.
Before shipping, verify an actual update on an emulator or test device:
install a first signed APK, create test app data, and install the newer APK
without uninstalling (adb install -r newer.apk). Confirm that the data and
account settings survive and that both APK certificate fingerprints match
the committed pin. The keystore regression tests alone do not exercise the
Android package manager or application data migrations.
References: Android app signing, version codes, and GitHub Actions secrets.