From 11bbd8c87a9557ff0e2af9a1a03db31ab3a0b75c Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Thu, 5 Mar 2026 14:02:26 -0500 Subject: [PATCH] feat: Refactor APK signing and verification process into separate scripts --- .github/scripts/attach-apk-to-release.sh | 60 ++++++++++ .github/scripts/setup-apk-signing.sh | 102 ++++++++++++++++ .github/scripts/verify-apk-signature.sh | 59 ++++++++++ .github/workflows/product-release.yml | 144 ++--------------------- 4 files changed, 232 insertions(+), 133 deletions(-) create mode 100755 .github/scripts/attach-apk-to-release.sh create mode 100755 .github/scripts/setup-apk-signing.sh create mode 100755 .github/scripts/verify-apk-signature.sh diff --git a/.github/scripts/attach-apk-to-release.sh b/.github/scripts/attach-apk-to-release.sh new file mode 100755 index 00000000..174023aa --- /dev/null +++ b/.github/scripts/attach-apk-to-release.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# ============================================================================= +# Attach APK to GitHub Release +# ============================================================================= +# This script attaches a built APK to a GitHub Release with proper naming +# +# Usage: +# ./attach-apk-to-release.sh +# +# Arguments: +# tag_name - Git tag name (e.g., krow-withus-worker-mobile/dev-v0.1.0) +# app - worker-mobile-app or client-mobile-app +# app_name - staff or client (internal build folder name) +# version - Version number (e.g., 0.1.0) +# environment - dev, stage, or prod +# +# Environment Variables: +# GH_TOKEN - GitHub token for gh CLI authentication +# ============================================================================= + +set -e + +TAG_NAME="$1" +APP="$2" +APP_NAME="$3" +VERSION="$4" +ENV="$5" + +if [ -z "$TAG_NAME" ] || [ -z "$APP" ] || [ -z "$APP_NAME" ] || [ -z "$VERSION" ] || [ -z "$ENV" ]; then + echo "❌ Error: Missing required arguments" + echo "Usage: $0 " + exit 1 +fi + +# Find APK in build output +APK_PATH="apps/mobile/apps/${APP_NAME}/build/app/outputs/flutter-apk/app-release.apk" + +if [ ! -f "$APK_PATH" ]; then + echo "❌ Error: APK not found at $APK_PATH" + echo "Searching for APK files..." + find apps/mobile/apps/${APP_NAME} -name "*.apk" + exit 1 +fi + +# Create proper APK name based on app type +if [ "$APP" = "worker-mobile-app" ]; then + APK_NAME="krow-withus-worker-mobile-${ENV}-v${VERSION}.apk" +else + APK_NAME="krow-withus-client-mobile-${ENV}-v${VERSION}.apk" +fi + +# Copy APK with proper name +cp "$APK_PATH" "/tmp/$APK_NAME" + +# Upload to GitHub Release +echo "📤 Uploading $APK_NAME to release $TAG_NAME..." +gh release upload "$TAG_NAME" "/tmp/$APK_NAME" --clobber + +echo "✅ APK attached to release: $APK_NAME" diff --git a/.github/scripts/setup-apk-signing.sh b/.github/scripts/setup-apk-signing.sh new file mode 100755 index 00000000..fe982f6a --- /dev/null +++ b/.github/scripts/setup-apk-signing.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# ============================================================================= +# Setup APK Signing for GitHub Actions +# ============================================================================= +# This script configures Android APK signing by decoding keystores from +# GitHub Secrets and setting up environment variables for build.gradle.kts +# +# Usage: +# ./setup-apk-signing.sh +# +# Arguments: +# app - worker-mobile-app or client-mobile-app +# environment - dev, stage, or prod +# temp_dir - Temporary directory for keystore files (e.g., ${{ runner.temp }}) +# +# Environment Variables (must be set): +# WORKER_KEYSTORE_DEV_BASE64, WORKER_KEYSTORE_STAGING_BASE64, WORKER_KEYSTORE_PROD_BASE64 +# WORKER_KEYSTORE_PASSWORD_DEV, WORKER_KEYSTORE_PASSWORD_STAGING, WORKER_KEYSTORE_PASSWORD_PROD +# WORKER_KEY_ALIAS_DEV, WORKER_KEY_ALIAS_STAGING, WORKER_KEY_ALIAS_PROD +# WORKER_KEY_PASSWORD_DEV, WORKER_KEY_PASSWORD_STAGING, WORKER_KEY_PASSWORD_PROD +# CLIENT_KEYSTORE_DEV_BASE64, CLIENT_KEYSTORE_STAGING_BASE64, CLIENT_KEYSTORE_PROD_BASE64 +# CLIENT_KEYSTORE_PASSWORD_DEV, CLIENT_KEYSTORE_PASSWORD_STAGING, CLIENT_KEYSTORE_PASSWORD_PROD +# CLIENT_KEY_ALIAS_DEV, CLIENT_KEY_ALIAS_STAGING, CLIENT_KEY_ALIAS_PROD +# CLIENT_KEY_PASSWORD_DEV, CLIENT_KEY_PASSWORD_STAGING, CLIENT_KEY_PASSWORD_PROD +# ============================================================================= + +set -e + +APP="$1" +ENV="$2" +TEMP_DIR="$3" + +if [ -z "$APP" ] || [ -z "$ENV" ] || [ -z "$TEMP_DIR" ]; then + echo "❌ Error: Missing required arguments" + echo "Usage: $0 " + exit 1 +fi + +echo "🔐 Setting up Android signing for $APP in $ENV environment..." + +# Determine which keystore to use +if [ "$APP" = "worker-mobile-app" ]; then + APP_TYPE="WORKER" + APP_NAME="STAFF" # CodeMagic uses STAFF in env var names +else + APP_TYPE="CLIENT" + APP_NAME="CLIENT" +fi + +# Convert environment to uppercase for env var names +ENV_UPPER=$(echo "$ENV" | tr '[:lower:]' '[:upper:]') +if [ "$ENV_UPPER" = "STAGE" ]; then + ENV_UPPER="STAGING" # CodeMagic uses STAGING instead of STAGE +fi + +# Get the keystore secret name dynamically +KEYSTORE_BASE64_VAR="${APP_TYPE}_KEYSTORE_${ENV_UPPER}_BASE64" +KEYSTORE_PASSWORD_VAR="${APP_TYPE}_KEYSTORE_PASSWORD_${ENV_UPPER}" +KEY_ALIAS_VAR="${APP_TYPE}_KEY_ALIAS_${ENV_UPPER}" +KEY_PASSWORD_VAR="${APP_TYPE}_KEY_PASSWORD_${ENV_UPPER}" + +# Get values using indirect expansion +KEYSTORE_BASE64="${!KEYSTORE_BASE64_VAR}" +KEYSTORE_PASSWORD="${!KEYSTORE_PASSWORD_VAR}" +KEY_ALIAS="${!KEY_ALIAS_VAR}" +KEY_PASSWORD="${!KEY_PASSWORD_VAR}" + +# Check if secrets are configured +if [ -z "$KEYSTORE_BASE64" ]; then + echo "⚠️ WARNING: Keystore secret $KEYSTORE_BASE64_VAR is not configured!" + echo "⚠️ APK will be built UNSIGNED for $ENV environment." + echo "⚠️ Please configure GitHub Secrets as documented in docs/RELEASE/APK_SIGNING_SETUP.md" + exit 0 +fi + +# Create temporary directory for keystore +KEYSTORE_DIR="${TEMP_DIR}/keystores" +mkdir -p "$KEYSTORE_DIR" +KEYSTORE_PATH="$KEYSTORE_DIR/release.jks" + +# Decode keystore from base64 +echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE_PATH" + +if [ ! -f "$KEYSTORE_PATH" ]; then + echo "❌ Failed to decode keystore!" + exit 1 +fi + +echo "✅ Keystore decoded successfully" +echo "📦 Keystore size: $(ls -lh "$KEYSTORE_PATH" | awk '{print $5}')" + +# Export environment variables for build.gradle.kts +# Using CodeMagic-compatible variable names +echo "CI=true" >> $GITHUB_ENV +echo "CM_KEYSTORE_PATH_${APP_NAME}=$KEYSTORE_PATH" >> $GITHUB_ENV +echo "CM_KEYSTORE_PASSWORD_${APP_NAME}=$KEYSTORE_PASSWORD" >> $GITHUB_ENV +echo "CM_KEY_ALIAS_${APP_NAME}=$KEY_ALIAS" >> $GITHUB_ENV +echo "CM_KEY_PASSWORD_${APP_NAME}=$KEY_PASSWORD" >> $GITHUB_ENV + +echo "✅ Signing environment configured for $APP_NAME ($ENV environment)" +echo "🔑 Using key alias: $KEY_ALIAS" diff --git a/.github/scripts/verify-apk-signature.sh b/.github/scripts/verify-apk-signature.sh new file mode 100755 index 00000000..16832d02 --- /dev/null +++ b/.github/scripts/verify-apk-signature.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# ============================================================================= +# Verify APK Signature +# ============================================================================= +# This script verifies that an APK is properly signed and displays +# certificate information +# +# Usage: +# ./verify-apk-signature.sh +# +# Arguments: +# apk_path - Path to the APK file to verify +# ============================================================================= + +set -e + +APK_PATH="$1" + +if [ -z "$APK_PATH" ]; then + echo "❌ Error: Missing APK path" + echo "Usage: $0 " + exit 1 +fi + +if [ ! -f "$APK_PATH" ]; then + echo "❌ APK not found at: $APK_PATH" + exit 1 +fi + +echo "🔍 Verifying APK signature..." + +# Check if APK is signed +if jarsigner -verify -verbose "$APK_PATH" 2>&1 | grep -q "jar verified"; then + echo "✅ APK is properly signed!" + + # Extract certificate details + echo "" + echo "📜 Certificate Details:" + jarsigner -verify -verbose -certs "$APK_PATH" 2>&1 | grep -A 3 "X.509" || true + + # Get signer info + echo "" + echo "🔑 Signer Information:" + keytool -printcert -jarfile "$APK_PATH" | head -n 15 + +else + echo "⚠️ WARNING: APK signature verification failed or APK is unsigned!" + echo "" + echo "This may happen if:" + echo " 1. GitHub Secrets are not configured for this environment" + echo " 2. Keystore credentials are incorrect" + echo " 3. Build configuration didn't apply signing" + echo "" + echo "See: docs/RELEASE/APK_SIGNING_SETUP.md for setup instructions" + + # Don't fail the build, just warn + # exit 1 +fi diff --git a/.github/workflows/product-release.yml b/.github/workflows/product-release.yml index 962f3bb2..f3f3930c 100644 --- a/.github/workflows/product-release.yml +++ b/.github/workflows/product-release.yml @@ -227,72 +227,10 @@ jobs: CLIENT_KEY_PASSWORD_STAGING: ${{ secrets.CLIENT_KEY_PASSWORD_STAGING }} CLIENT_KEY_PASSWORD_PROD: ${{ secrets.CLIENT_KEY_PASSWORD_PROD }} run: | - APP="${{ github.event.inputs.app }}" - ENV="${{ github.event.inputs.environment }}" - - echo "🔐 Setting up Android signing for $APP in $ENV environment..." - - # Determine which keystore to use - if [ "$APP" = "worker-mobile-app" ]; then - APP_TYPE="WORKER" - APP_NAME="STAFF" # CodeMagic uses STAFF in env var names - else - APP_TYPE="CLIENT" - APP_NAME="CLIENT" - fi - - # Convert environment to uppercase for env var names - ENV_UPPER=$(echo "$ENV" | tr '[:lower:]' '[:upper:]') - if [ "$ENV_UPPER" = "STAGE" ]; then - ENV_UPPER="STAGING" # CodeMagic uses STAGING instead of STAGE - fi - - # Get the keystore secret name dynamically - KEYSTORE_BASE64_VAR="${APP_TYPE}_KEYSTORE_${ENV_UPPER}_BASE64" - KEYSTORE_PASSWORD_VAR="${APP_TYPE}_KEYSTORE_PASSWORD_${ENV_UPPER}" - KEY_ALIAS_VAR="${APP_TYPE}_KEY_ALIAS_${ENV_UPPER}" - KEY_PASSWORD_VAR="${APP_TYPE}_KEY_PASSWORD_${ENV_UPPER}" - - # Get values using indirect expansion - KEYSTORE_BASE64="${!KEYSTORE_BASE64_VAR}" - KEYSTORE_PASSWORD="${!KEYSTORE_PASSWORD_VAR}" - KEY_ALIAS="${!KEY_ALIAS_VAR}" - KEY_PASSWORD="${!KEY_PASSWORD_VAR}" - - # Check if secrets are configured - if [ -z "$KEYSTORE_BASE64" ]; then - echo "⚠️ WARNING: Keystore secret $KEYSTORE_BASE64_VAR is not configured!" - echo "⚠️ APK will be built UNSIGNED for $ENV environment." - echo "⚠️ Please configure GitHub Secrets as documented in docs/RELEASE/APK_SIGNING_SETUP.md" - exit 0 - fi - - # Create temporary directory for keystore - KEYSTORE_DIR="${{ runner.temp }}/keystores" - mkdir -p "$KEYSTORE_DIR" - KEYSTORE_PATH="$KEYSTORE_DIR/release.jks" - - # Decode keystore from base64 - echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE_PATH" - - if [ ! -f "$KEYSTORE_PATH" ]; then - echo "❌ Failed to decode keystore!" - exit 1 - fi - - echo "✅ Keystore decoded successfully" - echo "📦 Keystore size: $(ls -lh "$KEYSTORE_PATH" | awk '{print $5}')" - - # Export environment variables for build.gradle.kts - # Using CodeMagic-compatible variable names - echo "CI=true" >> $GITHUB_ENV - echo "CM_KEYSTORE_PATH_${APP_NAME}=$KEYSTORE_PATH" >> $GITHUB_ENV - echo "CM_KEYSTORE_PASSWORD_${APP_NAME}=$KEYSTORE_PASSWORD" >> $GITHUB_ENV - echo "CM_KEY_ALIAS_${APP_NAME}=$KEY_ALIAS" >> $GITHUB_ENV - echo "CM_KEY_PASSWORD_${APP_NAME}=$KEY_PASSWORD" >> $GITHUB_ENV - - echo "✅ Signing environment configured for $APP_NAME ($ENV environment)" - echo "🔑 Using key alias: $KEY_ALIAS" + .github/scripts/setup-apk-signing.sh \ + "${{ github.event.inputs.app }}" \ + "${{ github.event.inputs.environment }}" \ + "${{ runner.temp }}" - name: 🏗️ Build APK id: build_apk @@ -330,42 +268,7 @@ jobs: - name: ✅ Verify APK Signature run: | - APK_PATH="${{ steps.build_apk.outputs.apk_path }}" - - if [ ! -f "$APK_PATH" ]; then - echo "❌ APK not found at: $APK_PATH" - exit 1 - fi - - echo "🔍 Verifying APK signature..." - - # Check if APK is signed - if jarsigner -verify -verbose "$APK_PATH" 2>&1 | grep -q "jar verified"; then - echo "✅ APK is properly signed!" - - # Extract certificate details - echo "" - echo "📜 Certificate Details:" - jarsigner -verify -verbose -certs "$APK_PATH" 2>&1 | grep -A 3 "X.509" || true - - # Get signer info - echo "" - echo "🔑 Signer Information:" - keytool -printcert -jarfile "$APK_PATH" | head -n 15 - - else - echo "⚠️ WARNING: APK signature verification failed or APK is unsigned!" - echo "" - echo "This may happen if:" - echo " 1. GitHub Secrets are not configured for this environment" - echo " 2. Keystore credentials are incorrect" - echo " 3. Build configuration didn't apply signing" - echo "" - echo "See: docs/RELEASE/APK_SIGNING_SETUP.md for setup instructions" - - # Don't fail the build, just warn - # exit 1 - fi + .github/scripts/verify-apk-signature.sh "${{ steps.build_apk.outputs.apk_path }}" - name: 📤 Upload APK as Artifact uses: actions/upload-artifact@v4 @@ -380,34 +283,9 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - TAG_NAME="${{ needs.validate-and-create-release.outputs.tag_name }}" - APP="${{ github.event.inputs.app }}" - APP_NAME="${{ steps.build_apk.outputs.app_name }}" - VERSION="${{ needs.validate-and-create-release.outputs.version }}" - ENV="${{ github.event.inputs.environment }}" - - # Find APK in build output - APK_PATH="apps/mobile/apps/${APP_NAME}/build/app/outputs/flutter-apk/app-release.apk" - - if [ ! -f "$APK_PATH" ]; then - echo "❌ Error: APK not found at $APK_PATH" - echo "Searching for APK files..." - find apps/mobile/apps/${APP_NAME} -name "*.apk" - exit 1 - fi - - # Create proper APK name based on app type - if [ "$APP" = "worker-mobile-app" ]; then - APK_NAME="krow-withus-worker-mobile-${ENV}-v${VERSION}.apk" - else - APK_NAME="krow-withus-client-mobile-${ENV}-v${VERSION}.apk" - fi - - # Copy APK with proper name - cp "$APK_PATH" "/tmp/$APK_NAME" - - # Upload to GitHub Release - echo "📤 Uploading $APK_NAME to release $TAG_NAME..." - gh release upload "$TAG_NAME" "/tmp/$APK_NAME" --clobber - - echo "✅ APK attached to release: $APK_NAME" + .github/scripts/attach-apk-to-release.sh \ + "${{ needs.validate-and-create-release.outputs.tag_name }}" \ + "${{ github.event.inputs.app }}" \ + "${{ steps.build_apk.outputs.app_name }}" \ + "${{ needs.validate-and-create-release.outputs.version }}" \ + "${{ github.event.inputs.environment }}"