mirror of
https://github.com/usetrmnl/trmnl-android.git
synced 2026-04-29 13:35:26 -07:00
[UPDATE] Enable android app release signing from CI
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
+13
-10
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user