Skip to content

Commit 280f99b

Browse files
committed
ci: distribute signed APK to Firebase App Distribution on tags
New workflow triggered by v* tag push (or manual dispatch): builds the signed release APK and pushes it to the 'testers' group via firebase appdistribution:distribute. Models the mapozy distribute.yml, adapted to this repo's Capacitor build and keystore-via-env signing.
1 parent 93f798f commit 280f99b

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

.github/workflows/distribute.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Distribute to Firebase App Distribution
2+
3+
# Builds a signed release APK and pushes it to the "testers" group, delivered
4+
# through the Firebase App Tester app on testers' phones.
5+
#
6+
# Triggers:
7+
# - pushing a tag like v0.1.0 (the usual release path)
8+
# - manual run from the Actions tab (optionally with custom release notes)
9+
#
10+
# This is separate from the "Release APK" workflow: that one attaches the APK
11+
# to a published GitHub Release, this one ships the same signed APK to testers.
12+
# A tag push runs this; publishing a GitHub Release runs the other.
13+
#
14+
# One-time setup
15+
# --------------
16+
# 1. Firebase project + Android app (gives the App ID):
17+
# - https://console.firebase.google.com → use an existing project (the
18+
# mapozy one works) or create a new one → Add app → Android.
19+
# - Android package name: com.paultranvan.solitaire (must match exactly).
20+
# - google-services.json is NOT needed for App Distribution — the App ID
21+
# is enough. Copy it; it looks like 1:1234567890:android:abc0123456789.
22+
# - Open App Distribution in the left nav and accept the terms.
23+
# - Create a tester group named "testers" (matches TESTER_GROUP below) and
24+
# add tester emails.
25+
#
26+
# 2. CI token (firebase CLI is already installed locally):
27+
# firebase login:ci # opens a browser, prints a token
28+
# The token is per-user and works across all your Firebase projects, so the
29+
# same one you use for mapozy is fine.
30+
#
31+
# 3. Repo secrets (Settings → Secrets and variables → Actions):
32+
# FIREBASE_APP_ID the 1:...:android:... App ID from step 1
33+
# FIREBASE_TOKEN the token from step 2
34+
# ANDROID_KEYSTORE_BASE64 base64 of the release .keystore
35+
# ANDROID_KEYSTORE_PASSWORD store password
36+
# ANDROID_KEY_ALIAS key alias
37+
# ANDROID_KEY_PASSWORD key password
38+
# The four ANDROID_* secrets are the same ones the Release APK workflow uses.
39+
40+
on:
41+
push:
42+
tags:
43+
- 'v*'
44+
workflow_dispatch:
45+
inputs:
46+
tag:
47+
description: Tag to derive the version number from (defaults to the current ref)
48+
required: false
49+
notes:
50+
description: Release notes (defaults to the latest commit subject)
51+
required: false
52+
53+
concurrency:
54+
group: distribute-${{ github.ref }}
55+
cancel-in-progress: false
56+
57+
env:
58+
TESTER_GROUP: testers
59+
60+
jobs:
61+
build-distribute:
62+
runs-on: ubuntu-latest
63+
timeout-minutes: 40
64+
steps:
65+
- name: Checkout
66+
uses: actions/checkout@v6
67+
68+
- name: Resolve version numbers from tag
69+
id: ver
70+
run: |
71+
TAG="${{ github.event.inputs.tag || github.ref_name }}"
72+
VERSION_NAME="${TAG#v}"
73+
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_NAME"
74+
VERSION_CODE=$(( 10#${MAJOR:-0} * 10000 + 10#${MINOR:-0} * 100 + 10#${PATCH:-0} ))
75+
if [ "$VERSION_CODE" -le 0 ]; then
76+
echo "Ref '$TAG' is not numeric semver (vMAJOR.MINOR.PATCH)" >&2; exit 1
77+
fi
78+
echo "name=$VERSION_NAME" >> "$GITHUB_OUTPUT"
79+
echo "code=$VERSION_CODE" >> "$GITHUB_OUTPUT"
80+
81+
- name: Setup Node
82+
uses: actions/setup-node@v6
83+
with:
84+
node-version: 22
85+
cache: npm
86+
87+
- name: Install dependencies
88+
run: npm ci
89+
90+
- name: Build web bundle
91+
run: npm run build
92+
93+
- name: Setup JDK
94+
uses: actions/setup-java@v5
95+
with:
96+
distribution: temurin
97+
java-version: 21
98+
cache: gradle
99+
100+
- name: Sync Capacitor android project
101+
run: npx cap sync android
102+
103+
- name: Decode keystore
104+
env:
105+
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
106+
run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
107+
108+
- name: Build signed release APK
109+
working-directory: android
110+
env:
111+
ANDROID_KEYSTORE_PATH: ${{ runner.temp }}/release.keystore
112+
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
113+
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
114+
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
115+
ANDROID_VERSION_NAME: ${{ steps.ver.outputs.name }}
116+
ANDROID_VERSION_CODE: ${{ steps.ver.outputs.code }}
117+
run: ./gradlew assembleRelease
118+
119+
- name: Verify APK is release-signed (not debug)
120+
run: |
121+
APK=android/app/build/outputs/apk/release/app-release.apk
122+
test -f "$APK" || { echo "APK not found at $APK"; exit 1; }
123+
APKSIGNER=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -1)
124+
echo "Using $APKSIGNER"
125+
SIGNER=$("$APKSIGNER" verify --print-certs "$APK" | grep -m1 "Signer #1 certificate DN" || true)
126+
echo "$SIGNER"
127+
if [ -z "$SIGNER" ]; then
128+
echo "ERROR: APK is unsigned — release keystore secrets missing?"; exit 1
129+
fi
130+
if echo "$SIGNER" | grep -qi "androiddebugkey"; then
131+
echo "ERROR: APK is debug-signed — release keystore was not applied"; exit 1
132+
fi
133+
134+
- name: Distribute to Firebase App Distribution
135+
env:
136+
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
137+
FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }}
138+
run: |
139+
NOTES="${{ github.event.inputs.notes }}"
140+
if [ -z "$NOTES" ]; then NOTES="$(git log -1 --pretty=%s)"; fi
141+
echo "Release notes: $NOTES"
142+
npx -y firebase-tools appdistribution:distribute \
143+
android/app/build/outputs/apk/release/app-release.apk \
144+
--app "$FIREBASE_APP_ID" \
145+
--groups "$TESTER_GROUP" \
146+
--release-notes "$NOTES" \
147+
--token "$FIREBASE_TOKEN"

0 commit comments

Comments
 (0)