Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,22 @@ jobs:
- name: ✅ Run tests
run: flutter test --coverage

- name: 🔐 Decode Keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
run: |
if [ -n "$KEYSTORE_BASE64" ]; then
echo $KEYSTORE_BASE64 | base64 --decode > android/app/upload-keystore.jks
else
echo "No KEYSTORE_BASE64 secret provided, skipping secure signing setup."
fi

- name: 🏗️ Build APKs
env:
KEYSTORE_PATH: "upload-keystore.jks"
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: flutter build apk --release --split-per-abi

- name: 📦 Prepare artifacts
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,37 @@ This project uses the following github actions -
* https://github.com/marketplace/actions/create-release

For a complete guide on implemenatation read the tutorial on [Medium](https://medium.com/better-programming/ci-cd-for-flutter-apps-using-github-actions-b833f8f7aac)

## 🔐 Secure Release Signing

To automatically sign your release APK with your own keystore using this GitHub Action, you need to configure your repository secrets.

1. **Generate your keystore**
If you don't already have a `.jks` keystore file, you can generate one using the `keytool` utility (which comes bundled with Java or Android Studio). Run this in your terminal:
```bash
keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
```
*(**Note for Mac users:** If you get a "command not found" error because Java isn't installed system-wide, you can use the version bundled with Android Studio by typing `/Applications/Android\ Studio.app/Contents/jbr/Contents/Home/bin/keytool` instead of just `keytool`)*
2. **Encode your keystore to Base64**:
- **Mac:**
```bash
base64 -b 0 -i "path/to/your/keystore.jks" > keystore.txt
```
- **Linux:**
```bash
base64 -w 0 "path/to/your/keystore.jks" > keystore.txt
```
- **Windows (PowerShell):**
```powershell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("path\to\your\keystore.jks")) | Out-File keystore.txt
```
3. **Add GitHub Secrets** to your repository (Settings -> Secrets and variables -> Actions):
- `KEYSTORE_BASE64`: The contents of the `keystore.txt` file you just created.
- `KEYSTORE_PASSWORD`: The keystore password you typed when running the `keytool` command.
- `KEY_ALIAS`: The alias you used (e.g., `upload` if you copied the command exactly).
- `KEY_PASSWORD`: The key password you typed when running the `keytool` command.

The GitHub action will automatically detect these secrets and use them to securely sign the release APK. If these secrets are not provided, it will gracefully fall back to the default debug signing keys.

> [!WARNING]
> **Never commit your `.jks` keystore file, `keystore.txt`, or any of your passwords directly to your git repository!** Always add them securely via GitHub Secrets and make sure your `.gitignore` is configured to ignore `.jks` and `.txt` key files.
24 changes: 21 additions & 3 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,29 @@ android {
versionName = flutter.versionName
}

signingConfigs {
create("release") {
val keystorePath = System.getenv("KEYSTORE_PATH")
if (keystorePath != null && file(keystorePath).exists()) {
storeFile = file(keystorePath)
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
}

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")
val keystorePath = System.getenv("KEYSTORE_PATH")
if (keystorePath != null && file(keystorePath).exists()) {
println("🚀 SIGNING WITH CUSTOM SECURE RELEASE KEY!")
signingConfig = signingConfigs.getByName("release")
} else {
// Fallback to debug keys if secrets are not provided
println("⚠️ NO SECURE KEY FOUND. SIGNING WITH DEFAULT DEBUG KEY.")
signingConfig = signingConfigs.getByName("debug")
}
}
}
}
Expand Down