- Updated Makefile to include new command for setting up mobile CI secrets. - Enhanced tools.mk with setup-mobile-ci-secrets target. - Created setup-mobile-github-secrets.sh script for configuring GitHub Secrets for APK signing. - Added APK signing implementation summary documentation. - Created detailed APK signing setup guide. - Added GitHub secrets checklist for easy reference.
8.3 KiB
APK Signing Setup for GitHub Actions
For Worker Mobile & Client Mobile Apps
📋 Overview
This document explains how to set up APK signing for automated builds in GitHub Actions. The same keystore files used in CodeMagic will be used here.
🔑 Understanding App Signing
Why Sign APKs?
- Required by Google Play: All Android apps must be signed before distribution
- App Identity: The signature identifies your app across updates
- Security: Ensures the APK hasn't been tampered with
Keystore Files
Each app and environment combination has its own keystore:
Worker Mobile (Staff App):
krow_staff_dev.jks- Development buildskrow_staff_staging.jks- Staging buildskrow_staff_prod.jks- Production builds
Client Mobile:
krow_client_dev.jks- Development buildskrow_client_staging.jks- Staging buildskrow_client_prod.jks- Production builds
Current State
- ✅ Dev keystores are committed to the repository (in
apps/mobile/apps/*/android/app/) - ⚠️ Staging/Prod keystores are stored securely in CodeMagic (NOT in repo)
🔐 GitHub Secrets Setup
Step 1: Export Keystores as Base64
For staging and production keystores (stored in CodeMagic), you'll need to:
# On your local machine with access to the keystore files:
# For Worker Mobile - Staging
base64 -i krow_staff_staging.jks -o krow_staff_staging.jks.base64
# For Worker Mobile - Production
base64 -i krow_staff_prod.jks -o krow_staff_prod.jks.base64
# For Client Mobile - Staging
base64 -i krow_client_staging.jks -o krow_client_staging.jks.base64
# For Client Mobile - Production
base64 -i krow_client_prod.jks -o krow_client_prod.jks.base64
Step 2: Create GitHub Secrets
Go to: GitHub Repository → Settings → Secrets and variables → Actions → New repository secret
Create the following secrets:
Worker Mobile (Staff App) Secrets
| Secret Name | Value | Environment |
|---|---|---|
WORKER_KEYSTORE_DEV_BASE64 |
(base64 of dev keystore) | dev |
WORKER_KEYSTORE_STAGING_BASE64 |
(base64 of staging keystore) | stage |
WORKER_KEYSTORE_PROD_BASE64 |
(base64 of prod keystore) | prod |
WORKER_KEYSTORE_PASSWORD_DEV |
krowwithus |
dev |
WORKER_KEYSTORE_PASSWORD_STAGING |
(actual staging password) | stage |
WORKER_KEYSTORE_PASSWORD_PROD |
(actual prod password) | prod |
WORKER_KEY_ALIAS_DEV |
krow_staff_dev |
dev |
WORKER_KEY_ALIAS_STAGING |
(actual staging alias) | stage |
WORKER_KEY_ALIAS_PROD |
(actual prod alias) | prod |
WORKER_KEY_PASSWORD_DEV |
krowwithus |
dev |
WORKER_KEY_PASSWORD_STAGING |
(actual staging key password) | stage |
WORKER_KEY_PASSWORD_PROD |
(actual prod key password) | prod |
Client Mobile Secrets
| Secret Name | Value | Environment |
|---|---|---|
CLIENT_KEYSTORE_DEV_BASE64 |
(base64 of dev keystore) | dev |
CLIENT_KEYSTORE_STAGING_BASE64 |
(base64 of staging keystore) | stage |
CLIENT_KEYSTORE_PROD_BASE64 |
(base64 of prod keystore) | prod |
CLIENT_KEYSTORE_PASSWORD_DEV |
krowwithus |
dev |
CLIENT_KEYSTORE_PASSWORD_STAGING |
(actual staging password) | stage |
CLIENT_KEYSTORE_PASSWORD_PROD |
(actual prod password) | prod |
CLIENT_KEY_ALIAS_DEV |
krow_client_dev |
dev |
CLIENT_KEY_ALIAS_STAGING |
(actual staging alias) | stage |
CLIENT_KEY_ALIAS_PROD |
(actual prod alias) | prod |
CLIENT_KEY_PASSWORD_DEV |
krowwithus |
dev |
CLIENT_KEY_PASSWORD_STAGING |
(actual staging key password) | stage |
CLIENT_KEY_PASSWORD_PROD |
(actual prod key password) | prod |
⚙️ How It Works in GitHub Actions
Build Configuration Detection
The build.gradle.kts files check for CI=true environment variable:
signingConfigs {
create("release") {
if (System.getenv()["CI"] == "true") {
// CI environment (CodeMagic or GitHub Actions)
storeFile = file(System.getenv()["CM_KEYSTORE_PATH_STAFF"] ?: "")
storePassword = System.getenv()["CM_KEYSTORE_PASSWORD_STAFF"]
keyAlias = System.getenv()["CM_KEY_ALIAS_STAFF"]
keyPassword = System.getenv()["CM_KEY_PASSWORD_STAFF"]
} else {
// Local development (uses key.properties)
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String?
}
}
}
GitHub Actions Workflow Steps
- Decode Keystore: Convert base64 secret back to
.jksfile - Set Environment Variables: Provide the same env vars CodeMagic uses
- Build APK: Flutter build automatically uses the signing config
- Verify Signature: Optionally verify the APK is signed correctly
🚀 Usage
For Development Builds
Dev keystores are already in the repo, so GitHub Actions will automatically use them:
# No special setup needed for dev builds
# They use committed keystores: krow_with_us_staff_dev.jks
For Staging/Production Builds
Once GitHub Secrets are configured (Step 2 above), the workflow will:
- Detect the environment (dev/stage/prod)
- Use the appropriate keystore secret
- Decode it before building
- Sign the APK automatically
✅ Verification
Check APK Signature
After building, verify the APK is signed:
# Using keytool (part of Java JDK)
keytool -printcert -jarfile app-release.apk
# Expected output should show certificate info with your key alias
Check Build Logs
In GitHub Actions, look for:
✅ Keystore decoded successfully
✅ APK signed with: krow_staff_prod
✅ APK built successfully: /path/to/app-release.apk
🔒 Security Best Practices
DO:
- ✅ Store production keystores ONLY in GitHub Secrets (encrypted)
- ✅ Use different keystores for dev/staging/prod
- ✅ Rotate passwords periodically
- ✅ Limit access to repository secrets (use environment protection rules)
- ✅ Keep keystore files backed up securely offline
DON'T:
- ❌ Never commit staging/production keystores to Git
- ❌ Never share keystore passwords in plain text
- ❌ Never use production keystores for development
- ❌ Never commit
.jksfiles for staging/prod
📝 Keystore Management Commands
Generate New Keystore
keytool -genkey -v \
-keystore krow_staff_prod.jks \
-alias krow_staff_prod \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-storetype JKS
View Keystore Info
keytool -list -v -keystore krow_staff_prod.jks
Get SHA-1 and SHA-256 Fingerprints
keytool -list -v -keystore krow_staff_prod.jks -alias krow_staff_prod
These fingerprints are needed for:
- Firebase project configuration
- Google Maps API key restrictions
- Google Play Console app signing
🆘 Troubleshooting
"keystore not found" Error
Problem: GitHub Actions can't find the decoded keystore
Solution: Check the decode step in the workflow creates the file in the correct location
"wrong password" Error
Problem: Keystore password doesn't match
Solution: Verify the GitHub Secret value matches the actual keystore password
APK Not Signed
Problem: APK builds but isn't signed
Solution: Ensure CI=true is set before building
Certificate Mismatch
Problem: "App not installed" when updating
Solution: You're using a different keystore than previous builds. Use the same keystore for all versions.
📚 Related Documentation
🔄 Migration from CodeMagic
If you want to use GitHub Actions instead of CodeMagic:
- Export all keystores from CodeMagic
- Convert to base64 (as shown above)
- Add to GitHub Secrets
- Test with a dev build first
- Verify signatures match previous releases
- Deploy staging build for testing
- Only then use for production
Important: Make sure the GitHub Actions builds produce the SAME signature as CodeMagic builds, otherwise app updates will fail!