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
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CI

on:
push:
branches:
- main
paths:
- 'android/**'
- '.github/workflows/ci.yml'
pull_request:
branches:
- main
paths:
- 'android/**'
- '.github/workflows/ci.yml'

jobs:
build:
name: Build Android (Debug)
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'

- name: Set up Android SDK
uses: android-actions/setup-android@v3

- name: Install Android SDK packages
run: |
yes | sdkmanager --licenses || true
sdkmanager "platforms;android-37.0" "build-tools;37.0.0"

- name: Build Debug APKs
working-directory: android
run: |
./gradlew :app:assembleGithubDebug :app:assembleFdroidDebug
176 changes: 176 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Release & Publish

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
play_track:
description: 'Google Play track'
required: true
default: 'internal'
type: choice
options:
- internal
- alpha
- beta
- production
publish_to_play:
description: 'Publish to Google Play (if service account configured)'
required: true
default: true
type: boolean

permissions:
contents: write

jobs:
release:
name: Build & Publish Release
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'

- name: Set up Android SDK
uses: android-actions/setup-android@v3

- name: Install Android SDK packages
run: |
yes | sdkmanager --licenses || true
sdkmanager "platforms;android-37.0" "build-tools;37.0.0"

- name: Set up Release Signing Key
id: setup_signing
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
if [ -z "$KEYSTORE_BASE64" ]; then
echo "::error::KEYSTORE_BASE64 secret is not configured. Release builds require release signing."
exit 1
fi

KEYSTORE_PATH="${GITHUB_WORKSPACE}/android/app/release.jks"
echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE_PATH"

cat <<EOF > android/keystore.properties
storeFile=$KEYSTORE_PATH
storePassword=$KEYSTORE_PASSWORD
keyAlias=$KEY_ALIAS
keyPassword=$KEY_PASSWORD
EOF

- name: Determine Version & Prepare Changelogs
id: version_info
run: |
VERSION=$(grep -E 'versionName\s*=\s*' android/app/build.gradle.kts | head -n1 | sed -E 's/.*"([^"]+)".*/\1/')
VERSION_CODE=$(grep -E 'versionCode\s*=\s*' android/app/build.gradle.kts | head -n1 | sed -E 's/.*=\s*([0-9]+).*/\1/')

TAG="${GITHUB_REF_NAME}"
if [ -z "$TAG" ] || [ "$TAG" = "main" ]; then
TAG="v${VERSION}"
fi

echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "version_code=${VERSION_CODE}" >> "$GITHUB_OUTPUT"

CHANGELOG_SRC="fastlane/metadata/android/en-US/changelogs/${VERSION_CODE}.txt"
if [ -f "$CHANGELOG_SRC" ]; then
echo "changelog_file=${CHANGELOG_SRC}" >> "$GITHUB_OUTPUT"
else
echo "changelog_file=" >> "$GITHUB_OUTPUT"
fi

# Prepare Google Play whatsnew directory format (whatsnew-<locale>)
mkdir -p distribution/whatsnew
if [ -f "$CHANGELOG_SRC" ]; then
cp "$CHANGELOG_SRC" distribution/whatsnew/whatsnew-en-US
fi

- name: Build Android Release Artifacts
working-directory: android
run: |
./gradlew :app:assembleRelease :app:bundleGithubRelease

- name: Package Release Assets
run: |
TAG="${{ steps.version_info.outputs.tag }}"
mkdir -p dist

# 1. GitHub flavor APK (includes bundled Pebble watchapp asset)
cp android/app/build/outputs/apk/github/release/app-github-release.apk "dist/PebbleRecorder-${TAG}.apk"

# 2. F-Droid flavor APK (without bundled Pebble watchapp, reference binary for reproducible build)
cp android/app/build/outputs/apk/fdroid/release/app-fdroid-release.apk "dist/PebbleRecorder-${TAG}-fdroid.apk"

# 3. Android App Bundle (AAB) for Google Play distribution
cp android/app/build/outputs/bundle/githubRelease/app-github-release.aab "dist/PebbleRecorder-${TAG}.aab"

# 4. Watchapp PBW asset
if [ -f watch/build/watch.pbw ]; then
cp watch/build/watch.pbw dist/watch.pbw
elif [ -f android/app/src/github/assets/watch.pbw ]; then
cp android/app/src/github/assets/watch.pbw dist/watch.pbw
fi

ls -lh dist/

- name: Verify F-Droid APK Signature
run: |
TAG="${{ steps.version_info.outputs.tag }}"
EXPECTED_FPRINT="3c7b738a4be737b5ed376eb686f9d507d4779d715e6d73a0306e2d7b7203852c"

FDROID_APK="dist/PebbleRecorder-${TAG}-fdroid.apk"
CERT_FPRINT=$(apksigner verify --print-certs "$FDROID_APK" | grep "SHA-256 digest:" | head -n1 | awk '{print $NF}')

echo "Expected certificate digest: $EXPECTED_FPRINT"
echo "Actual APK certificate digest: $CERT_FPRINT"

if [ "$CERT_FPRINT" != "$EXPECTED_FPRINT" ]; then
echo "::error::F-Droid APK signing certificate does not match packaging/fdroid/com.pebblerecorder.app.yml AllowedAPKSigningKeys!"
exit 1
fi

- name: Publish GitHub Release (Powers F-Droid Updates)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version_info.outputs.tag }}
name: ${{ steps.version_info.outputs.tag }}
body_path: ${{ steps.version_info.outputs.changelog_file }}
files: |
dist/PebbleRecorder-${{ steps.version_info.outputs.tag }}.apk
dist/PebbleRecorder-${{ steps.version_info.outputs.tag }}-fdroid.apk
dist/PebbleRecorder-${{ steps.version_info.outputs.tag }}.aab
dist/watch.pbw
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish to Google Play Store
if: ${{ (github.event_name == 'push' || inputs.publish_to_play) && secrets.PLAY_STORE_JSON_KEY != '' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_JSON_KEY }}
packageName: com.pebblerecorder.app
releaseFiles: dist/PebbleRecorder-${{ steps.version_info.outputs.tag }}.aab
track: ${{ inputs.play_track || vars.PLAY_STORE_TRACK || 'internal' }}
whatsNewDirectory: distribution/whatsnew

- name: Notice on Google Play Upload
if: ${{ (github.event_name == 'push' || inputs.publish_to_play) && secrets.PLAY_STORE_JSON_KEY == '' }}
run: |
echo "::notice::Google Play upload was skipped because PLAY_STORE_JSON_KEY secret is not configured."
18 changes: 18 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ rejects any extra signing block.
Kotlin DSL.
- **`buildToolsVersion` is pinned** to avoid AGP's default pick triggering a
download-and-license-accept flow.

## Automated Releases (GitHub Actions)

Releases can be built and published via `.github/workflows/release.yml` when a tag matching `v*` is pushed (or triggered manually via `workflow_dispatch`).

### Required GitHub Secrets

- `KEYSTORE_BASE64` — base64-encoded release `.jks` file (e.g. `base64 -w 0 ~/.android/pebblerecorder-release.jks`).
- `KEYSTORE_PASSWORD` — keystore password.
- `KEY_ALIAS` — key alias in the keystore.
- `KEY_PASSWORD` — key password.
- `PLAY_STORE_JSON_KEY` *(optional)* — Google Play Console service account credentials JSON. If omitted, Google Play upload is safely skipped with a notice.

### F-Droid & Google Play Distribution

- **F-Droid**: F-Droid does not have a direct upload API; it builds from source on its own servers. For reproducible builds, F-Droid's update bot (`UpdateCheckMode: Tags` in `packaging/fdroid/com.pebblerecorder.app.yml`) monitors GitHub Releases for `PebbleRecorder-vX.Y.Z-fdroid.apk`, verifies the build hash matches, and publishes it. The GitHub Release published by this workflow provides this required binary asset automatically.
- **Google Play**: The workflow builds the signed Android App Bundle (`PebbleRecorder-vX.Y.Z.aab`) and uploads it to the configured track (`internal`, `alpha`, `beta`, or `production`, defaulting to `internal` or repository variable `PLAY_STORE_TRACK`) along with release notes from `fastlane/metadata/android/en-US/changelogs/<versionCode>.txt`.

Loading