update build.yml #18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Signed Android APK/AAB | ||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| build_type: | ||
| description: 'Build type' | ||
| required: true | ||
| default: 'release' | ||
| type: choice | ||
| options: | ||
| - debug | ||
| - release | ||
| push: | ||
| tags: | ||
| - 'v*.*.*' | ||
| jobs: | ||
| build-android: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Set up Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'npm' | ||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '17' | ||
| - name: Install dependencies | ||
| run: npm install --legacy-peer-deps | ||
| - name: Expo Prebuild | ||
| run: npx expo prebuild --platform android --clean | ||
| - name: Setup Gradle cache | ||
| uses: gradle/actions/setup-gradle@v4 # or latest version | ||
| with: | ||
| gradle-home-cache-cleanup: true | ||
| - name: Decode Keystore | ||
| run: | | ||
| mkdir -p android/app | ||
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/release.keystore | ||
| ls -lh android/app/release.keystore | ||
| echo "✅ Keystore decoded" | ||
| - name: Setup signing in gradle.properties | ||
| run: | | ||
| echo "📝 Setting up gradle.properties..." | ||
| cat >> android/gradle.properties << EOF | ||
| MYAPP_RELEASE_STORE_FILE=release.keystore | ||
| MYAPP_RELEASE_KEY_ALIAS=${{ secrets.KEY_ALIAS }} | ||
| MYAPP_RELEASE_STORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }} | ||
| MYAPP_RELEASE_KEY_PASSWORD=${{ secrets.KEY_PASSWORD }} | ||
| EOF | ||
| echo "✅ gradle.properties configured" | ||
| echo "" | ||
| echo "📋 Verification (with secrets masked):" | ||
| cat android/gradle.properties | grep MYAPP_RELEASE_STORE_FILE | ||
| echo "MYAPP_RELEASE_KEY_ALIAS=***" | ||
| echo "MYAPP_RELEASE_STORE_PASSWORD=***" | ||
| echo "MYAPP_RELEASE_KEY_PASSWORD=***" | ||
| - name: Configure build.gradle for signing | ||
| run: | | ||
| BUILD_GRADLE="android/app/build.gradle" | ||
| echo "📝 Configuring signing..." | ||
| # Create backup | ||
| cp "$BUILD_GRADLE" "${BUILD_GRADLE}.backup" | ||
| # Step 1: Comment out Expo's debug signing in release buildType | ||
| sed -i 's/signingConfig signingConfigs\.debug/\/\/ signingConfig signingConfigs.debug (disabled)/g' "$BUILD_GRADLE" | ||
| # Step 2: Create signing config block with proper property syntax | ||
| { | ||
| echo "" | ||
| echo " signingConfigs {" | ||
| echo " release {" | ||
| echo " storeFile file('release.keystore')" | ||
| echo " storePassword project.property('MYAPP_RELEASE_STORE_PASSWORD')" | ||
| echo " keyAlias project.property('MYAPP_RELEASE_KEY_ALIAS')" | ||
| echo " keyPassword project.property('MYAPP_RELEASE_KEY_PASSWORD')" | ||
| echo " }" | ||
| echo " }" | ||
| } > /tmp/signing_block.gradle | ||
| # Step 3: Insert signing config after defaultConfig | ||
| LINE_NUM=$(grep -n "^ }$" "$BUILD_GRADLE" | head -1 | cut -d: -f1) | ||
| if [ -n "$LINE_NUM" ]; then | ||
| head -n "$LINE_NUM" "$BUILD_GRADLE" > "${BUILD_GRADLE}.tmp" | ||
| cat /tmp/signing_block.gradle >> "${BUILD_GRADLE}.tmp" | ||
| tail -n +$((LINE_NUM + 1)) "$BUILD_GRADLE" >> "${BUILD_GRADLE}.tmp" | ||
| mv "${BUILD_GRADLE}.tmp" "$BUILD_GRADLE" | ||
| echo "✅ Signing config inserted" | ||
| fi | ||
| # Step 4: Add signingConfig to release buildType | ||
| sed -i '/buildTypes {/,/release {/ { /release {/ a\ signingConfig signingConfigs.release | ||
| }' "$BUILD_GRADLE" | ||
| echo "✅ Signing configuration complete" | ||
| echo "" | ||
| echo "📋 Final signing configuration:" | ||
| grep -A 8 "signingConfigs {" "$BUILD_GRADLE" | head -10 | ||
| - name: Make gradlew executable | ||
| run: chmod +x android/gradlew | ||
| - name: Clean previous builds | ||
| run: cd android && ./gradlew clean | ||
| - name: Verify gradle.properties before build | ||
| run: | | ||
| echo "📋 Checking gradle.properties..." | ||
| cat android/gradle.properties | grep MYAPP_ || echo "⚠️ Properties not found in gradle.properties" | ||
| echo "" | ||
| echo "📋 Checking if keystore exists..." | ||
| ls -lh android/app/release.keystore || echo "❌ Keystore not found!" | ||
| echo "" | ||
| echo "📋 Testing property access..." | ||
| cd android | ||
| ./gradlew properties | grep MYAPP_ || echo "⚠️ Gradle cannot see MYAPP properties" | ||
| - name: Build Signed APK with explicit signing | ||
| run: | | ||
| cd android | ||
| echo "🔨 Building with signing..." | ||
| ./gradlew assembleRelease \ | ||
| -PMYAPP_RELEASE_STORE_FILE=release.keystore \ | ||
| -PMYAPP_RELEASE_KEY_ALIAS="${{ secrets.KEY_ALIAS }}" \ | ||
| -PMYAPP_RELEASE_STORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \ | ||
| -PMYAPP_RELEASE_KEY_PASSWORD="${{ secrets.KEY_PASSWORD }}" \ | ||
| --no-daemon \ | ||
| --stacktrace \ | ||
| --info 2>&1 | tee build.log | ||
| echo "" | ||
| echo "📋 Checking build log for signing..." | ||
| if grep -i "signing" build.log | grep -i "release"; then | ||
| echo "✅ Signing configuration was used" | ||
| else | ||
| echo "⚠️ Warning: No signing activity detected" | ||
| fi | ||
| - name: Build Signed AAB with explicit signing | ||
| run: | | ||
| cd android | ||
| ./gradlew bundleRelease \ | ||
| -PMYAPP_RELEASE_STORE_FILE=release.keystore \ | ||
| -PMYAPP_RELEASE_KEY_ALIAS="${{ secrets.KEY_ALIAS }}" \ | ||
| -PMYAPP_RELEASE_STORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \ | ||
| -PMYAPP_RELEASE_KEY_PASSWORD="${{ secrets.KEY_PASSWORD }}" \ | ||
| --no-daemon \ | ||
| --stacktrace | ||
| - name: Verify APK signature | ||
| run: | | ||
| APK_PATH=$(find android/app/build/outputs/apk/release -name "*.apk" | head -1) | ||
| if [ ! -f "$APK_PATH" ]; then | ||
| echo "❌ APK not found!" | ||
| exit 1 | ||
| fi | ||
| echo "📦 Verifying: $APK_PATH" | ||
| echo "" | ||
| # List all META-INF files | ||
| echo "=== META-INF Directory Contents ===" | ||
| unzip -l "$APK_PATH" | grep "META-INF/" || { | ||
| echo "❌ No META-INF directory found - APK is completely unsigned!" | ||
| exit 1 | ||
| } | ||
| echo "" | ||
| # Find certificate file | ||
| CERT_FILE=$(unzip -l "$APK_PATH" | grep -o "META-INF/[A-Z0-9]*\.RSA" | head -1) | ||
| if [ -z "$CERT_FILE" ]; then | ||
| echo "❌ No RSA certificate found in APK!" | ||
| echo "This means the APK was not signed at all." | ||
| echo "" | ||
| echo "DEBUG: Checking build.gradle signing configuration..." | ||
| grep -A 15 "signingConfigs" android/app/build.gradle | ||
| exit 1 | ||
| fi | ||
| echo "=== Certificate Information ===" | ||
| echo "Certificate file: $CERT_FILE" | ||
| unzip -p "$APK_PATH" "$CERT_FILE" | keytool -printcert | ||
| echo "" | ||
| echo "=== SHA1 Fingerprint ===" | ||
| SHA1=$(unzip -p "$APK_PATH" "$CERT_FILE" | keytool -printcert | grep "SHA1:" | awk '{print $2}') | ||
| echo "Current SHA1: $SHA1" | ||
| echo "Expected SHA1: 59:3D:24:BE:25:91:DF:54:6E:A8:06:17:DC:A1:73:81:3E:71:4C:A0" | ||
| if [ "$SHA1" == "59:3D:24:BE:25:91:DF:54:6E:A8:06:17:DC:A1:73:81:3E:71:4C:A0" ]; then | ||
| echo "✅ ✅ ✅ CORRECT KEYSTORE! ✅ ✅ ✅" | ||
| else | ||
| echo "⚠️ WARNING: This is a different keystore!" | ||
| echo "Google Play will reject this upload." | ||
| fi | ||
| - name: Upload APK artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: app-release-apk-${{ github.run_number }} | ||
| path: android/app/build/outputs/apk/release/*.apk | ||
| retention-days: 30 | ||
| - name: Upload AAB artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: app-release-aab-${{ github.run_number }} | ||
| path: android/app/build/outputs/bundle/release/*.aab | ||
| retention-days: 30 | ||
| - name: Upload debug files on failure | ||
| if: failure() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: debug-files-${{ github.run_number }} | ||
| path: | | ||
| android/build.log | ||
| android/app/build.gradle | ||
| android/app/build.gradle.backup | ||
| android/gradle.properties | ||
| retention-days: 7 | ||
| - name: Cleanup sensitive files | ||
| if: always() | ||
| run: | | ||
| rm -f android/app/release.keystore | ||
| rm -f android/gradle.properties | ||
| echo "✅ Cleanup completed" | ||