mirror of
https://github.com/usetrmnl/trmnl-android.git
synced 2026-04-29 13:35:26 -07:00
Merge pull request #132 from usetrmnl/121-enable-release-key-signing
[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
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
name: F-Droid Build
|
||||
|
||||
# Builds a signed F-Droid APK using the production keystore.
|
||||
# F-Droid will verify the signature during their reproducible build process.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
# This allows manual triggering of the workflow, which results in making F-Droid APK build
|
||||
# This allows manual triggering of the workflow, which results in making a signed F-Droid APK build
|
||||
# Go to the "Actions" tab the repository, select this workflow, and click the "Run workflow" button to run it manually.
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -25,9 +28,16 @@ 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 F-Droid APK
|
||||
run: ./gradlew buildFDroid
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
|
||||
- name: Extract version name
|
||||
id: version
|
||||
@@ -39,13 +49,13 @@ jobs:
|
||||
- name: Rename APK
|
||||
run: |
|
||||
mkdir -p artifact
|
||||
cp app/build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk artifact/trmnl-mirror-fdroid-v${{ steps.version.outputs.VERSION }}-unsigned.apk
|
||||
cp app/build/outputs/apk/fdroid/release/app-fdroid-release.apk artifact/trmnl-mirror-fdroid-v${{ steps.version.outputs.VERSION }}.apk
|
||||
|
||||
- name: Upload F-Droid APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trmnl-mirror-fdroid-unsigned
|
||||
path: artifact/trmnl-mirror-fdroid-v${{ steps.version.outputs.VERSION }}-unsigned.apk
|
||||
name: trmnl-mirror-fdroid-signed
|
||||
path: artifact/trmnl-mirror-fdroid-v${{ steps.version.outputs.VERSION }}.apk
|
||||
# Use maximum allowed retention period
|
||||
# https://github.com/actions/upload-artifact?tab=readme-ov-file#retention-period
|
||||
retention-days: 90
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
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 standard release build
|
||||
echo "Building standard release APK with production keystore..."
|
||||
if ./gradlew assembleStandardRelease \
|
||||
-PKEYSTORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \
|
||||
-PKEY_ALIAS="${{ secrets.KEY_ALIAS }}" > keystore-test/gradle-build-standard.txt 2>&1; then
|
||||
echo "✅ Android standard release build succeeded with production keystore"
|
||||
|
||||
# Verify the APK exists
|
||||
if [ -f "app/build/outputs/apk/standard/release/app-standard-release.apk" ]; then
|
||||
echo "✅ Standard release APK generated successfully"
|
||||
else
|
||||
echo "❌ Standard release APK not found"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Android standard release build failed"
|
||||
echo "Build output:"
|
||||
cat keystore-test/gradle-build-standard.txt
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test the F-Droid release build
|
||||
echo ""
|
||||
echo "Building F-Droid release APK with production keystore..."
|
||||
if ./gradlew assembleFdroidRelease \
|
||||
-PKEYSTORE_PASSWORD="${{ secrets.KEYSTORE_PASSWORD }}" \
|
||||
-PKEY_ALIAS="${{ secrets.KEY_ALIAS }}" > keystore-test/gradle-build-fdroid.txt 2>&1; then
|
||||
echo "✅ Android F-Droid release build succeeded with production keystore"
|
||||
|
||||
# Verify the APK exists
|
||||
if [ -f "app/build/outputs/apk/fdroid/release/app-fdroid-release.apk" ]; then
|
||||
echo "✅ F-Droid release APK generated successfully"
|
||||
echo "✅ Both standard and F-Droid builds are now properly signed"
|
||||
else
|
||||
echo "❌ F-Droid release APK not found"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Android F-Droid release build failed"
|
||||
echo "Build output:"
|
||||
cat keystore-test/gradle-build-fdroid.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
|
||||
+4
-2
@@ -9,7 +9,7 @@ The app includes specific configurations for F-Droid compatibility:
|
||||
1. A dedicated `fdroid` product flavor that excludes Google Fonts
|
||||
2. A specific `fdroidRelease` build type
|
||||
3. System fonts are used instead of Google Fonts for the F-Droid version
|
||||
4. The F-Droid build is **not signed** (as per [PR #106](https://github.com/usetrmnl/trmnl-android/pull/106)) - F-Droid handles the signing process
|
||||
4. The F-Droid build is **signed** with the production keystore for consistency across all distribution channels
|
||||
|
||||
## Building the F-Droid Version
|
||||
|
||||
@@ -19,7 +19,9 @@ To build the F-Droid version locally:
|
||||
./gradlew assembleFdroidRelease
|
||||
```
|
||||
|
||||
This will generate an unsigned APK in `app/build/outputs/apk/fdroid/release/` that is suitable for F-Droid submission. Unlike the standard release build, the F-Droid build variant does not have a signing configuration, as F-Droid's build system will handle the signing process.
|
||||
This will generate a signed APK in `app/build/outputs/apk/fdroid/release/` using the production keystore. The F-Droid build variant now uses the same signing configuration as the standard release build, ensuring consistency across all distribution channels.
|
||||
|
||||
> **Note**: F-Droid's reproducible build process will verify that the APK can be rebuilt with the same signature, ensuring the integrity of the build process.
|
||||
|
||||
Alternatively, you can use the convenience task:
|
||||
|
||||
|
||||
+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 set to the same value as storePassword because this PKCS12 keystore
|
||||
// requires the store password to be used 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 = System.getenv("KEYSTORE_PASSWORD") ?: project.findProperty("KEYSTORE_PASSWORD") as String?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-8
@@ -1,18 +1,50 @@
|
||||
# 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 all release builds (both standard and F-Droid flavors) and is stored as a base64-encoded secret in GitHub Actions. The keystore is decoded during CI/CD builds.
|
||||
|
||||
### Build Flavors Using Production Keystore
|
||||
|
||||
- **Standard Release**: Signs APKs for general distribution
|
||||
- **F-Droid Release**: Signs APKs for F-Droid distribution (F-Droid will verify the signature during their reproducible build process)
|
||||
|
||||
### 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, both `storePassword` and `keyPassword` are set to the same value in the build configuration
|
||||
- **Solution**: The Android Gradle Plugin requires both passwords to be explicitly set, so we 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 (used for both store and key access)
|
||||
- `KEY_ALIAS`: Alias of the signing key within the keystore
|
||||
|
||||
### CI/CD Workflows Using Production Keystore
|
||||
|
||||
- **`android-release.yml`**: Builds and signs standard release APKs
|
||||
- **`fdroid-build.yml`**: Builds and signs F-Droid release APKs
|
||||
|
||||
Both workflows decode the keystore from the base64 secret and provide the necessary environment variables for signing.
|
||||
|
||||
## Related Resources
|
||||
- https://developer.android.com/studio/publish/app-signing
|
||||
- https://source.android.com/docs/security/features/apksigning
|
||||
@@ -41,7 +41,7 @@ Builds:
|
||||
subdir: app
|
||||
gradle:
|
||||
- fdroid
|
||||
output: build/outputs/apk/fdroid/release/app-fdroid-release-unsigned.apk
|
||||
output: build/outputs/apk/fdroid/release/app-fdroid-release.apk
|
||||
|
||||
MaintainerNotes: This app uses the F-Droid flavor for gradle build.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user