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_SERVER_CLIENT_ID: ${{ secrets.GOOGLE_SERVER_CLIENT_ID }}
|
||||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||||
TOKEN: ${{ secrets.TOKEN }}
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -58,6 +63,23 @@ jobs:
|
|||||||
- name: Install release tooling
|
- name: Install release tooling
|
||||||
run: sudo apt-get update && sudo apt-get install -y jq
|
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
|
- name: Build release APK
|
||||||
run: |
|
run: |
|
||||||
BASE_URL_VALUE="${BASE_URL:-http://127.0.0.1:8000}"
|
BASE_URL_VALUE="${BASE_URL:-http://127.0.0.1:8000}"
|
||||||
@@ -77,6 +99,36 @@ jobs:
|
|||||||
|
|
||||||
"${FLUTTER_CMD[@]}"
|
"${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
|
- name: Create or update Gitea release and upload APK
|
||||||
run: |
|
run: |
|
||||||
if [ -z "${TOKEN}" ]; then
|
if [ -z "${TOKEN}" ]; then
|
||||||
|
|||||||
@@ -46,3 +46,8 @@ app.*.map.json
|
|||||||
|
|
||||||
# Local mobile runtime defines
|
# Local mobile runtime defines
|
||||||
.env.mobile
|
.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 {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
@@ -6,6 +9,15 @@ plugins {
|
|||||||
id("com.google.gms.google-services")
|
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 {
|
android {
|
||||||
namespace = "com.example.reader_app"
|
namespace = "com.example.reader_app"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
@@ -31,11 +43,25 @@ android {
|
|||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(releaseStoreFile!!)
|
||||||
|
storePassword = keystoreProperties.getProperty("storePassword")
|
||||||
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||||
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
// Use release keystore when available; fallback to debug for local dev builds.
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
signingConfig = if (hasReleaseSigning) {
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
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