[UPDATE] Enable android app release signing from CI

This commit is contained in:
Hossain Khan
2025-06-28 12:47:01 -04:00
parent 02456a48ac
commit edc6efc600
5 changed files with 164 additions and 18 deletions
+7
View File
@@ -33,8 +33,15 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Decode keystore from base64
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore/trmnl-app-release.keystore
- name: Build Release APK
run: ./gradlew assembleStandardRelease
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
- name: Extract version name
id: version
+114
View File
@@ -0,0 +1,114 @@
name: Test Keystore Configuration
# This workflow tests the production keystore configuration to ensure it works properly
# with the Android build system. It validates that the keystore can be decoded, passwords
# are correct, and jarsigner can successfully sign APKs.
on:
workflow_dispatch:
jobs:
test-keystore:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '23'
distribution: 'temurin'
- name: Create test directory
run: mkdir -p keystore-test
- name: Decode keystore from base64
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore-test/release.keystore
- name: Verify keystore basic properties
run: |
echo "=== Keystore Basic Information ==="
keytool -list -keystore keystore-test/release.keystore -storepass "${{ secrets.KEYSTORE_PASSWORD }}" | grep -v "Certificate fingerprint"
- name: Check alias exists
run: |
echo "=== Checking Alias ==="
if keytool -list -keystore keystore-test/release.keystore -storepass "${{ secrets.KEYSTORE_PASSWORD }}" -alias "${{ secrets.KEY_ALIAS }}" > /dev/null 2>&1; then
echo "✅ Alias '${{ secrets.KEY_ALIAS }}' exists in keystore"
else
echo "❌ Alias '${{ secrets.KEY_ALIAS }}' not found in keystore"
exit 1
fi
- name: Test jarsigner functionality
run: |
echo "=== Testing jarsigner ==="
# Create a simple JAR to test signing
echo "Test content" > keystore-test/test.txt
jar cf keystore-test/test.jar keystore-test/test.txt
# Test signing WITHOUT -keypass (the working approach)
echo "Testing jarsigner without explicit key password..."
if jarsigner -keystore keystore-test/release.keystore \
-storepass "${{ secrets.KEYSTORE_PASSWORD }}" \
keystore-test/test.jar \
"${{ secrets.KEY_ALIAS }}" > keystore-test/jarsigner.txt 2>&1; then
echo "✅ jarsigner succeeded (store password used for both store and key)"
echo "✅ This confirms the keystore is compatible with our build configuration"
else
echo "❌ jarsigner failed even without explicit key password"
cat keystore-test/jarsigner.txt
exit 1
fi
# Verify the signed JAR
if jarsigner -verify keystore-test/test.jar > keystore-test/verify.txt 2>&1; then
echo "✅ Signed JAR verification successful"
else
echo "❌ Signed JAR verification failed"
cat keystore-test/verify.txt
exit 1
fi
- name: Test Android build configuration
run: |
echo "=== Testing Android Build Configuration ==="
# Decode keystore to the expected location
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore/trmnl-app-release.keystore
# Set up Gradle
chmod +x gradlew
# Test the release build (this will use the new signing configuration)
echo "Building release APK with production keystore..."
if ./gradlew assembleStandardRelease \
-PKEYSTORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \
-PKEY_ALIAS="${{ secrets.KEY_ALIAS }}" > keystore-test/gradle-build.txt 2>&1; then
echo "✅ Android release build succeeded with production keystore"
echo "✅ APK should be properly signed"
# Verify the APK exists
if [ -f "app/build/outputs/apk/standard/release/app-standard-release.apk" ]; then
echo "✅ Release APK generated successfully"
else
echo "❌ Release APK not found"
exit 1
fi
else
echo "❌ Android release build failed"
echo "Build output:"
cat keystore-test/gradle-build.txt
exit 1
fi
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: keystore-test-results
path: keystore-test/
retention-days: 7
View File
+13 -10
View File
@@ -42,16 +42,16 @@ android {
}
create("release") {
// Uses the same debug keystore for release builds to enable CLI building
// ⚠️ This is temporary solution as app is still under development
// It allows early adopters to test drive the app without us worrying about signing key.
// - 📚 https://github.com/usetrmnl/trmnl-android/blob/main/keystore/README.md
storeFile = file("${rootProject.projectDir}/keystore/debug.keystore")
// Production keystore for release builds
// The keystore file should be decoded from KEYSTORE_BASE64 secret in CI/CD
storeFile = file("${rootProject.projectDir}/keystore/trmnl-app-release.keystore")
// ️ When time comes, these values should come from `secret.properties` file or CI/CD secrets.
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
// Values come from CI/CD secrets or local secret.properties file
// Note: keyPassword is intentionally omitted - this keystore works when
// jarsigner uses the store password for both store and key access
storePassword = System.getenv("KEYSTORE_PASSWORD") ?: project.findProperty("KEYSTORE_PASSWORD") as String?
keyAlias = System.getenv("KEY_ALIAS") ?: project.findProperty("KEY_ALIAS") as String?
// keyPassword = ... // Intentionally omitted - see note above
}
}
@@ -99,7 +99,10 @@ android {
// F-Droid specific configuration
// No non-free dependencies
buildConfigField("Boolean", "FDROID_BUILD", "true")
// ️ No signing config for F-Droid flavor (F-Droid handles signing)
// ️ Also sign the F-Droid build with the release keystore
// F-Droid will use reproducible builds process to validate authenticity
signingConfig = signingConfigs.getByName("release")
}
}
+30 -8
View File
@@ -1,18 +1,40 @@
# Debug Keystore
# App Signing Keystores
The debug keystore file is added to the repository to make it easier for developers to build and run
This directory contains the keystores used for signing the Android app.
## Debug Keystore
The debug keystore file (`debug.keystore`) is added to the repository to make it easier for developers to build and run
the app without having to generate a new keystore file each time. The debug keystore is used for
signing the app during development and [CI builds](https://github.com/usetrmnl/trmnl-android/actions/workflows/android-release.yml),
which allows early adopters test drive the app.
This solution is **not intended for production use**, and can't be used to publish the app to the
Google Play Store. If that time ever comes, aside from creating release keystore, there needs to be
a clear strategy for release and maintenance of the keystore file with passcodes.
signing the app during development.
> [!NOTE]
> The debug keystore is generated automatically by Android Studio
> and copied from the `$HOME/.android/debug.keystore` location.
## Production Keystore
The production keystore (`trmnl-app-release.keystore`) is used for release builds and is stored as a base64-encoded secret in GitHub Actions. The keystore is decoded during CI/CD builds.
### Important Notes About the Production Keystore
The production keystore has a specific configuration quirk that's important to understand:
- **Store Password**: Used to access the keystore file
- **Key Password**: The keystore was created with a key password, but due to PKCS12 format behavior, the private key is only accessible when jarsigner uses the store password for both store and key access
- **Solution**: The `keyPassword` parameter is intentionally omitted from the Android build configuration, allowing the Android build system to use the store password for both purposes
This is a known characteristic of certain PKCS12 keystores where explicit key passwords can cause "key associated with alias not a private key" errors, even when the keystore is completely valid.
### Secrets Configuration
The following GitHub Actions secrets are required:
- `KEYSTORE_BASE64`: Base64-encoded production keystore file
- `KEYSTORE_PASSWORD`: Password for accessing the keystore
- `KEY_ALIAS`: Alias of the signing key within the keystore
Note that `KEY_PASSWORD` is not used in the build configuration due to the keystore behavior described above.
## Related Resources
- https://developer.android.com/studio/publish/app-signing
- https://source.android.com/docs/security/features/apksigning