# 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 builds - `krow_staff_staging.jks` - Staging builds - `krow_staff_prod.jks` - Production builds **Client Mobile:** - `krow_client_dev.jks` - Development builds - `krow_client_staging.jks` - Staging builds - `krow_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: ```bash # 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: ```kotlin 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 1. **Decode Keystore**: Convert base64 secret back to `.jks` file 2. **Set Environment Variables**: Provide the same env vars CodeMagic uses 3. **Build APK**: Flutter build automatically uses the signing config 4. **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: ```bash # 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: 1. Detect the environment (dev/stage/prod) 2. Use the appropriate keystore secret 3. Decode it before building 4. Sign the APK automatically --- ## ✅ Verification ### Check APK Signature After building, verify the APK is signed: ```bash # 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 `.jks` files for staging/prod --- ## 📝 Keystore Management Commands ### Generate New Keystore ```bash keytool -genkey -v \ -keystore krow_staff_prod.jks \ -alias krow_staff_prod \ -keyalg RSA \ -keysize 2048 \ -validity 10000 \ -storetype JKS ``` ### View Keystore Info ```bash keytool -list -v -keystore krow_staff_prod.jks ``` ### Get SHA-1 and SHA-256 Fingerprints ```bash 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 - [Product Release Workflow](./MOBILE_RELEASE_PLAN.md) - [Hotfix Process](./HOTFIX_PROCESS.md) - [CodeMagic Configuration](/codemagic.yaml) - [Android App Signing (Google Docs)](https://developer.android.com/studio/publish/app-signing) --- ## 🔄 Migration from CodeMagic If you want to use GitHub Actions instead of CodeMagic: 1. Export all keystores from CodeMagic 2. Convert to base64 (as shown above) 3. Add to GitHub Secrets 4. Test with a dev build first 5. Verify signatures match previous releases 6. Deploy staging build for testing 7. Only then use for production **Important**: Make sure the GitHub Actions builds produce the SAME signature as CodeMagic builds, otherwise app updates will fail!