-
Notifications
You must be signed in to change notification settings - Fork 0
164 lines (149 loc) · 7.59 KB
/
Copy pathandroid-release.yml
File metadata and controls
164 lines (149 loc) · 7.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Build a SIGNED release AAB (Android App Bundle) for Google Play closed/production testing.
#
# This is DELIBERATELY separate from android-apk.yml:
# - android-apk.yml -> debug-signed APK, auto-built on every main push, for sideload sharing.
# - android-release.yml (this) -> release-signed AAB, MANUAL only, for uploading to Play.
# Play requires an AAB (not APK), a recent targetSdk, and a real release key — none of which the
# debug pipeline produces. The app targets SDK 35 (our standard) so it clears Play's minimum-target
# bar. The AAB is uploaded ONLY as a workflow artifact — never to
# the public android-latest release (an AAB is not directly installable, and Play uploads are a
# deliberate human step, not an automatic push).
#
# Required repo secrets (the UPLOAD key — Google re-signs with the Play-managed app key on top):
# ANDROID_RELEASE_KEYSTORE_B64 base64 of the upload keystore (`base64 -i upload.jks`)
# ANDROID_RELEASE_STORE_PASSWORD keystore password
# ANDROID_RELEASE_KEY_ALIAS key alias inside the keystore
# ANDROID_RELEASE_KEY_PASSWORD key password
# The uploaded AAB carries only the public cert + signature, so the artifact is safe to download.
# Manual trigger only, so these secrets are never exposed to untrusted (fork PR) code.
name: android-release
on:
workflow_dispatch:
inputs:
abi:
description: "Target ABI (arm64 = real phones; x86_64 = emulator only)"
type: choice
options: ["arm64", "x86_64"]
default: "arm64"
version_code:
description: "Play versionCode (must increase every upload; blank = use run number)"
required: false
default: ""
version_name:
description: "User-visible version name (blank = keep gradle default)"
required: false
default: ""
assets_run_id:
description: "android-assets run id to reuse (blank = latest successful)"
required: false
default: ""
permissions:
contents: read
actions: read
jobs:
build:
runs-on: ubuntu-latest
env:
ABI: ${{ github.event.inputs.abi || 'arm64' }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
# Fail fast if the release signing secrets are missing — otherwise gradle would produce an
# UNSIGNED AAB (signingConfig resolves to null), which Play rejects with a confusing error.
- name: Require release signing secrets
env:
KS_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}
STORE_PW: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
KEY_PW: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
run: |
missing=""
[ -z "$KS_B64" ] && missing="$missing ANDROID_RELEASE_KEYSTORE_B64"
[ -z "$STORE_PW" ] && missing="$missing ANDROID_RELEASE_STORE_PASSWORD"
[ -z "$KEY_ALIAS" ] && missing="$missing ANDROID_RELEASE_KEY_ALIAS"
[ -z "$KEY_PW" ] && missing="$missing ANDROID_RELEASE_KEY_PASSWORD"
if [ -n "$missing" ]; then
echo "::error::Missing release signing secrets:$missing (see android-release.yml header)"
exit 1
fi
# Lightweight, arch-independent JS — rebuilt every run so the AAB carries the latest UI.
- uses: pnpm/action-setup@v4
with: { version: 9 }
- uses: actions/setup-node@v4
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm --filter agentnet-localhost build
- run: pnpm --filter agentnet-webview build
# Reuse the rootfs + jniLibs from the latest android-assets run (no QEMU rebuild). Same
# resolution logic as android-apk.yml: walk recent successful runs, take the first whose
# per-ABI artifact hasn't expired.
- name: Resolve android-assets run
id: assets
run: |
ID="${{ github.event.inputs.assets_run_id }}"
if [ -z "$ID" ]; then
for cand in $(gh run list --workflow=android-assets.yml --status success -L 15 \
--json databaseId -q '.[].databaseId' || true); do
live=$(gh api "repos/${{ github.repository }}/actions/runs/$cand/artifacts" \
-q ".artifacts[] | select(.name==\"android-assets-${ABI}\" and .expired==false) | .id" \
2>/dev/null | head -1 || true)
if [ -n "$live" ]; then ID="$cand"; break; fi
done
fi
if [ -z "$ID" ]; then
echo "::error::No android-assets run with a live android-assets-${ABI} artifact. Run android-assets first."
exit 1
fi
echo "Using android-assets run $ID"
echo "id=$ID" >> "$GITHUB_OUTPUT"
- name: Download rootfs + jniLibs
run: gh run download "${{ steps.assets.outputs.id }}" -n "android-assets-${ABI}" -D ci-assets
- name: Stage assets into the source tree
run: |
set -e
mkdir -p surfaces/android/app/src/main/assets surfaces/android/rootfs/src/main/assets
rm -rf surfaces/android/app/src/main/jniLibs
cp -R ci-assets/jniLibs surfaces/android/app/src/main/jniLibs
# rootfs -> Play Asset Delivery pack module (:rootfs), NOT base assets — this is what
# keeps the release AAB's base module under Play's 500MB per-module download cap.
cp ci-assets/assets/rootfs-*.tar surfaces/android/rootfs/src/main/assets/
STAGE="$(mktemp -d)/server-bundle"
mkdir -p "$STAGE/webview"
cp -R surfaces/localhost/dist/. "$STAGE/"
cp -R surfaces/webview/dist/. "$STAGE/webview/"
tar -cf surfaces/android/app/src/main/assets/agentnet-server.tar -C "$STAGE" .
ls -lh surfaces/android/app/src/main/assets/
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: 17 }
# Decode the upload keystore into a path gradle reads via ANDROID_RELEASE_KEYSTORE. Removed at
# job end; GitHub-hosted runners are ephemeral and isolated.
- name: Install release keystore
env:
KS_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}
run: printf '%s' "$KS_B64" | base64 -d > surfaces/android/agentnet-upload.keystore
- name: Build signed release AAB (targetSdk 35)
working-directory: surfaces/android
env:
ANDROID_RELEASE_KEYSTORE: agentnet-upload.keystore
ANDROID_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }}
ANDROID_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
ANDROID_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
ANDROID_VERSION_CODE: ${{ github.event.inputs.version_code || github.run_number }}
ANDROID_VERSION_NAME: ${{ github.event.inputs.version_name }}
run: ./gradlew --no-daemon bundleRelease
- name: Remove keystore
if: always()
run: rm -f surfaces/android/agentnet-upload.keystore
# Confirm the AAB really is signed before handing it off (catches a silently-unsigned build).
- name: Verify AAB is signed
run: |
AAB=surfaces/android/app/build/outputs/bundle/release/app-release.aab
ls -lh "$AAB"
unzip -l "$AAB" | grep -E "META-INF/.*\.(RSA|EC|DSA)" \
|| { echo "::error::AAB has no signature block — check the release secrets"; exit 1; }
- uses: actions/upload-artifact@v4
with:
name: agentnet-aab-${{ env.ABI }}
path: surfaces/android/app/build/outputs/bundle/release/app-release.aab
retention-days: 30
if-no-files-found: error