Skip to content

Android: sign releases with one permanent key #5

Android: sign releases with one permanent key

Android: sign releases with one permanent key #5

Workflow file for this run

name: android

Check failure on line 1 in .github/workflows/android.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/android.yml

Invalid workflow file

(Line: 32, Col: 30): Unrecognized named-value: 'runner'. Located at position 1 within expression: runner.temp
on:
# No `paths` filter on push: a paths filter also applies to tag pushes, so a tag on a commit
# that did not touch android/ would never build or release. The workflow is cheap enough to
# run on every push to main; pull requests keep the filter.
push:
branches: [main, master]
tags:
- "android-v*"
pull_request:
paths:
- "android/**"
- ".github/workflows/android.yml"
workflow_dispatch:
permissions:
contents: write # needed only for the release step on tags
jobs:
build:
name: assembleRelease + assembleDebug + lint + unit tests
runs-on: ubuntu-latest # ships with the Android SDK; the build downloads platform 35 itself
defaults:
run:
working-directory: android
env:
# Release signing. The permanent key is the ANDROID_KEYSTORE_B64 secret (base64 of the
# .jks); it is decoded into the runner's temp dir below, never into the checkout. When the
# secrets are absent (a fork's pull request) the build still runs and produces an unsigned
# release APK; the signature check below then fails loudly instead of releasing it.
ANDROID_KEYSTORE_FILE: ${{ runner.temp }}/localflow.jks
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: "17"
- uses: gradle/actions/setup-gradle@v6
- name: Make the wrapper executable
run: chmod +x gradlew
- name: Decode the release keystore
env:
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
run: |
if [ -z "$ANDROID_KEYSTORE_B64" ]; then
echo "::warning::ANDROID_KEYSTORE_B64 secret not available; the release APK will be unsigned."
exit 0
fi
printf '%s' "$ANDROID_KEYSTORE_B64" | base64 -d > "$ANDROID_KEYSTORE_FILE"
chmod 600 "$ANDROID_KEYSTORE_FILE"
echo "Keystore decoded to $ANDROID_KEYSTORE_FILE ($(stat -c %s "$ANDROID_KEYSTORE_FILE") bytes)"
- name: Unit tests (WAV builder, text splice)
run: ./gradlew testDebugUnitTest --no-daemon --stacktrace
- name: Build debug APK
run: ./gradlew assembleDebug --no-daemon --stacktrace
- name: Build release APK (signed with the permanent key)
run: ./gradlew assembleRelease --no-daemon --stacktrace
- name: Lint
run: ./gradlew lintDebug --no-daemon
- name: Verify the release signature
# Fails if the APK is unsigned (secrets absent) or signed with anything but a valid key,
# and prints the certificate digest so every run shows which key signed it. The
# permanent LocalFlow key is documented in docs/ANDROID.md ("Verify the download").
run: |
if [ ! -f "$ANDROID_KEYSTORE_FILE" ]; then
if [[ "$GITHUB_REF" == refs/tags/android-v* ]]; then
echo "::error::No keystore on a release tag build; refusing to publish an unsigned APK."
exit 1
fi
echo "::warning::No keystore (secrets unavailable); skipping signature check. The release APK from this run is unsigned."
exit 0
fi
APKSIGNER=$(ls -1 "$ANDROID_HOME"/build-tools/*/apksigner | sort -V | tail -n 1)
echo "Using $APKSIGNER"
"$APKSIGNER" verify --print-certs app/build/outputs/apk/release/app-release.apk | tee apksigner.txt
DIGEST=$(grep -i 'SHA-256 digest' apksigner.txt | head -n 1 | awk '{print $NF}')
echo "Release APK signed by certificate SHA-256: $DIGEST"
echo "### Release APK signing certificate" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
cat apksigner.txt >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
- name: Rename release APK
run: cp app/build/outputs/apk/release/app-release.apk "LocalFlow-android-${GITHUB_REF_NAME//\//-}.apk"
- uses: actions/upload-artifact@v7
with:
name: app-release.apk
path: android/app/build/outputs/apk/release/app-release.apk
if-no-files-found: error
- uses: actions/upload-artifact@v7
with:
# Debug-key build for PR reviewers only; it is never attached to a release.
name: app-debug.apk
path: android/app/build/outputs/apk/debug/app-debug.apk
if-no-files-found: error
- uses: actions/upload-artifact@v7
with:
name: lint-report
path: android/app/build/reports/lint-results-debug.html
if-no-files-found: ignore
- name: Release on tag
if: startsWith(github.ref, 'refs/tags/android-v')
uses: softprops/action-gh-release@v3
with:
name: ${{ github.ref_name }}
files: android/LocalFlow-android-${{ github.ref_name }}.apk
generate_release_notes: true