feat: Enhance Android release signing process and add verification script
Build Android APK / build-apk (push) Failing after 9m6s
Build Android APK / build-apk (push) Failing after 9m6s
This commit is contained in:
@@ -14,6 +14,11 @@ jobs:
|
||||
GOOGLE_SERVER_CLIENT_ID: ${{ secrets.GOOGLE_SERVER_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
EXPECTED_ANDROID_SHA1: ${{ secrets.EXPECTED_ANDROID_SHA1 }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -58,6 +63,23 @@ jobs:
|
||||
- name: Install release tooling
|
||||
run: sudo apt-get update && sudo apt-get install -y jq
|
||||
|
||||
- name: Prepare Android release signing
|
||||
run: |
|
||||
if [ -n "${ANDROID_KEYSTORE_BASE64}" ] && [ -n "${ANDROID_KEYSTORE_PASSWORD}" ] && [ -n "${ANDROID_KEY_ALIAS}" ] && [ -n "${ANDROID_KEY_PASSWORD}" ]; then
|
||||
echo "Preparing release keystore from secrets"
|
||||
echo "${ANDROID_KEYSTORE_BASE64}" | base64 -d > android/release.keystore
|
||||
{
|
||||
echo "storeFile=release.keystore"
|
||||
echo "storePassword=${ANDROID_KEYSTORE_PASSWORD}"
|
||||
echo "keyAlias=${ANDROID_KEY_ALIAS}"
|
||||
echo "keyPassword=${ANDROID_KEY_PASSWORD}"
|
||||
} > android/key.properties
|
||||
else
|
||||
echo "Release signing secrets are required for tagged release builds."
|
||||
echo "Please configure: ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build release APK
|
||||
run: |
|
||||
BASE_URL_VALUE="${BASE_URL:-http://127.0.0.1:8000}"
|
||||
@@ -77,6 +99,36 @@ jobs:
|
||||
|
||||
"${FLUTTER_CMD[@]}"
|
||||
|
||||
- name: Verify APK signing certificate
|
||||
run: |
|
||||
APK_PATH="build/app/outputs/flutter-apk/app-release.apk"
|
||||
APKSIGNER_PATH="${ANDROID_SDK_ROOT}/build-tools/35.0.0/apksigner"
|
||||
|
||||
if [ ! -f "$APK_PATH" ]; then
|
||||
echo "APK not found at $APK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$APKSIGNER_PATH" ]; then
|
||||
echo "apksigner not found at $APKSIGNER_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CERT_OUTPUT="$($APKSIGNER_PATH verify --print-certs "$APK_PATH")"
|
||||
echo "$CERT_OUTPUT"
|
||||
|
||||
if [ -n "${EXPECTED_ANDROID_SHA1}" ]; then
|
||||
APK_SHA1=$(echo "$CERT_OUTPUT" | sed -n 's/.*certificate SHA-1 digest: //p' | head -n1 | tr '[:lower:]' '[:upper:]')
|
||||
EXPECTED_SHA1_UPPER=$(echo "${EXPECTED_ANDROID_SHA1}" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
if [ "$APK_SHA1" != "$EXPECTED_SHA1_UPPER" ]; then
|
||||
echo "APK SHA-1 mismatch"
|
||||
echo "Expected: $EXPECTED_SHA1_UPPER"
|
||||
echo "Actual : $APK_SHA1"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Create or update Gitea release and upload APK
|
||||
run: |
|
||||
if [ -z "${TOKEN}" ]; then
|
||||
|
||||
@@ -46,3 +46,8 @@ app.*.map.json
|
||||
|
||||
# Local mobile runtime defines
|
||||
.env.mobile
|
||||
|
||||
# Android release signing files (local/CI generated)
|
||||
android/key.properties
|
||||
android/*.jks
|
||||
android/*.keystore
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
@@ -6,6 +9,15 @@ plugins {
|
||||
id("com.google.gms.google-services")
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties()
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
val releaseStoreFile = keystoreProperties.getProperty("storeFile")
|
||||
val hasReleaseSigning = !releaseStoreFile.isNullOrBlank()
|
||||
|
||||
android {
|
||||
namespace = "com.example.reader_app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -31,11 +43,25 @@ android {
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
if (hasReleaseSigning) {
|
||||
create("release") {
|
||||
storeFile = file(releaseStoreFile!!)
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
// Use release keystore when available; fallback to debug for local dev builds.
|
||||
signingConfig = if (hasReleaseSigning) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ANDROID_DIR="$ROOT_DIR/android"
|
||||
KEY_PROPERTIES="$ANDROID_DIR/key.properties"
|
||||
APK_PATH="${1:-$ROOT_DIR/build/app/outputs/flutter-apk/app-release.apk}"
|
||||
|
||||
echo "== Android Release Signing Doctor =="
|
||||
echo "Project: $ROOT_DIR"
|
||||
|
||||
echo "\n[1] Checking key.properties"
|
||||
if [[ -f "$KEY_PROPERTIES" ]]; then
|
||||
echo "FOUND: $KEY_PROPERTIES"
|
||||
sed 's/password=.*/password=***HIDDEN***/' "$KEY_PROPERTIES"
|
||||
else
|
||||
echo "MISSING: $KEY_PROPERTIES"
|
||||
echo "Create this file for release signing."
|
||||
fi
|
||||
|
||||
echo "\n[2] Checking keystore from key.properties"
|
||||
if [[ -f "$KEY_PROPERTIES" ]]; then
|
||||
STORE_FILE_REL="$(grep '^storeFile=' "$KEY_PROPERTIES" | head -n1 | cut -d'=' -f2-)"
|
||||
STORE_FILE="$ANDROID_DIR/$STORE_FILE_REL"
|
||||
KEY_ALIAS="$(grep '^keyAlias=' "$KEY_PROPERTIES" | head -n1 | cut -d'=' -f2-)"
|
||||
STORE_PASS="$(grep '^storePassword=' "$KEY_PROPERTIES" | head -n1 | cut -d'=' -f2-)"
|
||||
KEY_PASS="$(grep '^keyPassword=' "$KEY_PROPERTIES" | head -n1 | cut -d'=' -f2-)"
|
||||
|
||||
if [[ -f "$STORE_FILE" ]]; then
|
||||
echo "FOUND keystore: $STORE_FILE"
|
||||
echo "Alias: $KEY_ALIAS"
|
||||
echo "Fingerprints:"
|
||||
keytool -list -v -keystore "$STORE_FILE" -alias "$KEY_ALIAS" -storepass "$STORE_PASS" -keypass "$KEY_PASS" | grep -E 'SHA1:|SHA256:'
|
||||
else
|
||||
echo "Keystore not found: $STORE_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "\n[3] Checking APK signature"
|
||||
if [[ -f "$APK_PATH" ]]; then
|
||||
echo "APK: $APK_PATH"
|
||||
if command -v apksigner >/dev/null 2>&1; then
|
||||
apksigner verify --print-certs "$APK_PATH"
|
||||
else
|
||||
echo "apksigner not found in PATH."
|
||||
echo "Try: \$ANDROID_HOME/build-tools/<version>/apksigner verify --print-certs $APK_PATH"
|
||||
fi
|
||||
else
|
||||
echo "APK not found at: $APK_PATH"
|
||||
fi
|
||||
|
||||
echo "\n[4] CI secrets required"
|
||||
echo "- TOKEN"
|
||||
echo "- BASE_URL"
|
||||
echo "- GOOGLE_SERVER_CLIENT_ID"
|
||||
echo "- ANDROID_KEYSTORE_BASE64"
|
||||
echo "- ANDROID_KEYSTORE_PASSWORD"
|
||||
echo "- ANDROID_KEY_ALIAS"
|
||||
echo "- ANDROID_KEY_PASSWORD"
|
||||
echo "- EXPECTED_ANDROID_SHA1 (optional but recommended)"
|
||||
Reference in New Issue
Block a user