diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ab5f115..e265845 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -502,11 +502,31 @@ class NetworkRepository @Inject constructor( 1. **Create Release Branch**: `git checkout -b release/vX.Y.Z` (e.g., `release/v1.17`) 2. **Update Version**: Increment `versionCode` and `versionName` in `app/build.gradle.kts` 3. **Update Release Notes**: Add new release section in `project-resources/google-play/release-notes.md` -4. **Build & Test**: Run `./gradlew clean assembleRelease bundleRelease` +4. **Build & Test**: Run `./gradlew clean assembleRelease bundleRelease` **on your local machine** 5. **Commit Changes**: Commit version bump and release notes on the release branch 6. **Create PR**: Open pull request from release branch to `main` 7. **Merge & Tag**: After PR approval, merge to `main` and create git tag `vX.Y.Z` -8. **Upload to Play Store**: Upload AAB to Google Play Console +8. **Upload to Play Store**: Upload the locally-built AAB (`app/release/app-release.aab`) to Google Play Console + +### ⚠️ Critical: App Bundle Signing + +**DO NOT upload release builds from GitHub Actions.** The release AAB must be built locally with the correct signing certificate registered in Google Play Console. + +**Why:** GitHub Actions builds and local builds use different keystores, which causes signing key mismatch errors when uploading to Google Play Console. See [Issue #438](https://github.com/hossain-khan/android-remote-notify/issues/438) for details. + +**Correct Process:** +- Build release bundle locally on your machine with correct `local.properties` keystore path +- Verify `local.properties` contains the correct keystore location and password +- Upload the generated `app/release/app-release.aab` directly from your local machine +- This ensures the bundle is signed with the certificate registered in Google Play + +**Troubleshooting Signing Errors:** +If you encounter "Your App Bundle is signed with the wrong key" error: +1. Verify the SHA1 fingerprint in Google Play Console +2. Check that `local.properties` points to the correct keystore file +3. Ensure the keystore password is correct +4. Rebuild locally: `./gradlew clean bundleRelease` +5. Re-upload the newly generated AAB ### Version Management - Update `versionCode` (increment by 1) and `versionName` in `app/build.gradle.kts` @@ -519,7 +539,7 @@ class NetworkRepository @Inject constructor( - [ ] Update version code and name in `app/build.gradle.kts` - [ ] Update `release-notes.md` with new release section - [ ] Check `local.properties` for correct keystore path and password -- [ ] Build release: `./gradlew clean assembleRelease bundleRelease` +- [ ] Build release **locally**: `./gradlew clean assembleRelease bundleRelease` - [ ] Test release build locally - [ ] Run full test suite: `./gradlew test` - [ ] Check ProGuard/R8 obfuscation @@ -528,17 +548,19 @@ class NetworkRepository @Inject constructor( - [ ] Create pull request to `main` - [ ] After merge, create git tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` - [ ] Push tag: `git push origin vX.Y.Z` -- [ ] Upload AAB to Firebase Test Lab -- [ ] Upload AAB to Google Play Console +- [ ] **Upload AAB to Google Play Console from your local machine** (not from GitHub Actions) + - Verify keystore SHA1 matches Google Play Console certificate fingerprint + - Upload from `app/release/app-release.aab` - [ ] Create GitHub release from tag with release notes ## Troubleshooting ### Common Issues 1. **Build Failures**: Ensure `local.properties` contains required API keys -2. **WorkManager Issues**: Check device battery optimization settings -3. **Firebase Issues**: Verify `google-services.json` is present and valid -4. **Compose Issues**: Clean and rebuild project, check Compose compiler version +2. **Signing Key Mismatch**: If Google Play rejects your bundle with "signed with the wrong key" error, ensure you built locally with the correct keystore. Never upload AAB from GitHub Actions (see Issue #438) +3. **WorkManager Issues**: Check device battery optimization settings +4. **Firebase Issues**: Verify `google-services.json` is present and valid +5. **Compose Issues**: Clean and rebuild project, check Compose compiler version ### Debugging Tools - Use `Timber.d()` for debug logging diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 6f840b7..109b73e 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -4,11 +4,25 @@ name: Android Release Build # 1. Snapshot builds: Each time code changes on main branch # 2. Release builds: When a GitHub release is published (automatically attaches APK and AAB to release) # This allows developers and testers to easily download the latest release APK. -# AAB (Android App Bundle) is provided for Google Play Store distribution. +# AAB (Android App Bundle) is provided for GitHub release artifacts. # -# See: +# ⚠️ IMPORTANT: App Bundle Signing for Google Play Store +# ===================================================== +# The AAB built in this workflow is signed with a CI keystore, NOT the keystore registered +# in Google Play Console. DO NOT upload this AAB directly to Google Play Store. +# +# For Play Store releases, you MUST: +# 1. Build locally: ./gradlew bundleRelease +# 2. Verify local.properties has the correct keystore path and password +# 3. Upload app/release/app-release.aab from your local machine to Google Play Console +# +# See Issue #438 for more details on signing key mismatches: +# https://github.com/hossain-khan/android-remote-notify/issues/438 +# +# References: # - https://github.com/hossain-khan/android-remote-notify/tree/main/keystore -# - https://github.com/hossain-khan/android-remote-notify/blob/main/app/build.gradle.kts (signing configuration) +# - https://github.com/hossain-khan/android-remote-notify/blob/main/app/build.gradle.kts (signing config) +# - https://github.com/hossain-khan/android-remote-notify/.github/copilot-instructions.md (release process) on: push: @@ -62,20 +76,48 @@ jobs: id: keystore run: | if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then - echo "⚠️ KEYSTORE_BASE64 secret is not set, using debug keystore for signing" + echo "⚠️ WARNING: KEYSTORE_BASE64 secret is not set" + echo "Using debug keystore for signing instead of release keystore" + echo "This build should NOT be uploaded to Google Play Store" echo "USE_RELEASE_KEYSTORE=false" >> $GITHUB_OUTPUT else + echo "📝 Attempting to decode release keystore from base64..." echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore/remote-notify-release.keystore if [ ! -s keystore/remote-notify-release.keystore ]; then - echo "❌ Failed to decode keystore file" + echo "❌ ERROR: Failed to decode keystore file or keystore is empty" exit 1 fi - echo "✅ Release keystore decoded successfully" + # Verify keystore file + KEYSTORE_SIZE=$(stat -f%z keystore/remote-notify-release.keystore 2>/dev/null || stat -c%s keystore/remote-notify-release.keystore 2>/dev/null) + echo "✅ Release keystore decoded successfully (size: $KEYSTORE_SIZE bytes)" + echo "⚠️ NOTE: AAB signed with CI keystore, not Google Play registr​ed keystore" + echo "For Play Store uploads, use locally-built AAB with correct keystore" echo "USE_RELEASE_KEYSTORE=true" >> $GITHUB_OUTPUT fi - name: Build Release APK and AAB - run: ./gradlew assembleRelease bundleRelease --parallel --daemon + run: | + echo "🏗️ Building Release APK and AAB..." + if [ "${{ steps.keystore.outputs.USE_RELEASE_KEYSTORE }}" = "true" ]; then + echo "📦 Signing with CI release keystore (NOT Google Play registered keystore)" + else + echo "📦 Signing with debug keystore (development only)" + fi + + ./gradlew assembleRelease bundleRelease --parallel --daemon + + if [ -f app/build/outputs/apk/release/app-release.apk ]; then + APK_SIZE=$(stat -f%z app/build/outputs/apk/release/app-release.apk 2>/dev/null || stat -c%s app/build/outputs/apk/release/app-release.apk 2>/dev/null) + echo "✅ APK built successfully (size: $APK_SIZE bytes)" + fi + + if [ -f app/build/outputs/bundle/release/app-release.aab ]; then + AAB_SIZE=$(stat -f%z app/build/outputs/bundle/release/app-release.aab 2>/dev/null || stat -c%s app/build/outputs/bundle/release/app-release.aab 2>/dev/null) + echo "✅ AAB built successfully (size: $AAB_SIZE bytes)" + echo "⚠️ REMINDER: This AAB is signed with CI keystore" + echo "⚠️ DO NOT upload this AAB to Google Play Store" + echo "✅ For Play Store, build locally: ./gradlew bundleRelease" + fi env: # Only set KEYSTORE_FILE when release keystore was decoded successfully # Otherwise, build.gradle.kts will fall back to debug keystore @@ -113,6 +155,20 @@ jobs: path: artifact/remote-notify-v${{ steps.version.outputs.VERSION }}.aab retention-days: 30 + # ⚠️ IMPORTANT: Signing & Google Play Store Upload + # ================================================ + # The APK and AAB built here are for GitHub releases and testing ONLY. + # They are signed with the CI keystore, NOT the keystore registered in Google Play Console. + # + # FOR GOOGLE PLAY STORE RELEASES: + # 1. Do NOT use the AAB from this workflow + # 2. Build locally on your machine: ./gradlew bundleRelease + # 3. Verify local.properties has correct keystore path and password + # 4. Verify keystore SHA1 fingerprint matches Google Play Console + # 5. Upload app/release/app-release.aab to Google Play Console from your local machine + # + # See Issue #438 and copilot-instructions.md for detailed release process. + # If this is running due to a GitHub release, attach the APK and AAB to the release - name: Attach APK and AAB to GitHub Release if: github.event_name == 'release' @@ -182,3 +238,20 @@ jobs: cat upload_response_aab.json exit 1 fi + + echo "" + echo "==================================" + echo "🔐 SIGNING NOTICE" + echo "==================================" + echo "✅ APK and AAB successfully built and attached to GitHub release" + echo "⚠️ These artifacts are signed with the CI keystore" + echo "⚠️ They are for testing and GitHub releases ONLY" + echo "❌ DO NOT upload to Google Play Store from this workflow" + echo "" + echo "📝 For Google Play Store releases:" + echo " 1. Build locally: ./gradlew bundleRelease" + echo " 2. Upload from local machine: app/release/app-release.aab" + echo "" + echo "📖 See .github/copilot-instructions.md for detailed release process" + echo "🔗 Issue #438: https://github.com/hossain-khan/android-remote-notify/issues/438" + echo "=================================="