-
Notifications
You must be signed in to change notification settings - Fork 1
205 lines (185 loc) · 8.22 KB
/
Copy pathrelease.yml
File metadata and controls
205 lines (185 loc) · 8.22 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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
name: Build & Release Android
on:
push:
tags:
- 'v*'
concurrency:
group: android-release-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.41.9'
cache: true
- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: |
~/.pub-cache
.dart_tool
key: pub-${{ hashFiles('pubspec.lock') }}
restore-keys: pub-
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('android/**/*.gradle*', 'android/**/gradle-wrapper.properties') }}
restore-keys: gradle-
- name: Install dependencies
run: flutter pub get
- name: Analyze
run: flutter analyze --no-fatal-infos
- name: Decode Keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/upload-keystore.jks
- name: Build arm64-v8a APK
env:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
run: |
set -euo pipefail
VERSION="${{ github.ref_name }}"
VERSION_NUM="${VERSION#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "${VERSION_NUM%%-*}"
BUILD_NUMBER=$((MAJOR * 10000 + MINOR * 100 + ${PATCH:-0}))
GIT_REF="${{ github.ref_name }}@$(echo '${{ github.sha }}' | cut -c1-7)"
# Only ship arm64-v8a — covers every modern Android device.
# 32-bit armeabi-v7a and x86_64 emulator builds were dropped
# 2026-05-07: install share was negligible vs. release-asset
# bloat and the post-Play-Store 64-bit-only policy.
#
# --split-per-abi is REQUIRED even with a single target. The
# Flutter Gradle plugin adds an ABI offset to versionCode
# only when split-per-abi is on (arm64-v8a → +4000). v1.0.381
# dropped the flag, which dropped the on-device versionCode
# from 14378 back to 10381 — Android refused the install with
# "higher version already installed." Keep the flag so the
# offset stays consistent with every prior release.
#
# --obfuscate + --split-debug-info strips Dart symbols for a
# few more MB; the mapping lives under build/symbols/.
#
# Retry loop: Gradle plugin-marker resolution
# (org.gradle.kotlin.kotlin-dsl, the Flutter Android Gradle
# plugin) is one network hop away from us and has flaked at
# least once in production (v1.0.370-alpha, 2026-05-06). The
# failure mode is "Plugin … was not found in any of the
# following sources" which is fatal but transient. Retry up
# to 3 times with backoff before failing the build for real.
for attempt in 1 2 3; do
if flutter build apk --release --split-per-abi \
--target-platform=android-arm64 \
--obfuscate --split-debug-info=build/symbols \
--build-name="$VERSION_NUM" \
--build-number="$BUILD_NUMBER" \
--dart-define=APP_VERSION="$VERSION" \
--dart-define=GIT_REF="$GIT_REF"; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "::error::APK build failed after 3 attempts"
exit 1
fi
echo "::warning::APK build attempt $attempt failed; retrying after backoff"
sleep $((attempt * 30))
done
- name: Rename APK
run: |
mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
"build/app/outputs/flutter-apk/termipod-${{ github.ref_name }}-arm64-v8a.apk"
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache-dependency-path: hub/go.sum
- name: Build server-side binaries + per-binary tarballs
# Cross-compile hub-server and host-runner for the four
# platforms operators actually deploy to (linux/{amd64,arm64}
# for VPS / Pi-style hosts; darwin/{amd64,arm64} for dev
# machines). modernc.org/sqlite is pure Go so CGO=0 keeps
# cross-compilation honest with no toolchain juggling.
#
# Per ADR-028 D-4 / plan W5.5 each binary ships in its OWN
# tarball — eight per release tag — so `self-update` pulls
# only the ~15MB it needs instead of a ~30MB dual-binary
# bundle. Each tarball expands to a single bare binary. One
# SHA256SUMS covers all eight; `self-update` verifies its
# artifact against the matching entry. Same version tag for
# both binaries — D-6 lockstep is preserved.
run: |
set -euo pipefail
VERSION="${{ github.ref_name }}"
OUT_DIR="dist/hub"
mkdir -p "$OUT_DIR"
cd hub
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do
os="${target%/*}"
arch="${target#*/}"
for bin in hub-server host-runner; do
echo "::group::build $bin for $os/$arch"
stage="$(mktemp -d)"
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" \
go build -trimpath -ldflags="-s -w" \
-o "$stage/$bin" "./cmd/$bin"
tar -C "$stage" -czf \
"../$OUT_DIR/termipod-${bin}-${VERSION}-${os}-${arch}.tar.gz" \
"$bin"
rm -rf "$stage"
echo "::endgroup::"
done
done
cd ..
# Single SHA256SUMS over all eight tarballs; entries are
# basenames so `self-update` matches by artifact name.
( cd "$OUT_DIR" && sha256sum termipod-*.tar.gz > SHA256SUMS )
ls -lh "$OUT_DIR"
echo "::group::SHA256SUMS"
cat "$OUT_DIR/SHA256SUMS"
echo "::endgroup::"
- name: Create Release
uses: softprops/action-gh-release@v3
with:
files: |
build/app/outputs/flutter-apk/termipod-${{ github.ref_name }}-arm64-v8a.apk
dist/hub/termipod-hub-server-${{ github.ref_name }}-linux-amd64.tar.gz
dist/hub/termipod-hub-server-${{ github.ref_name }}-linux-arm64.tar.gz
dist/hub/termipod-hub-server-${{ github.ref_name }}-darwin-amd64.tar.gz
dist/hub/termipod-hub-server-${{ github.ref_name }}-darwin-arm64.tar.gz
dist/hub/termipod-host-runner-${{ github.ref_name }}-linux-amd64.tar.gz
dist/hub/termipod-host-runner-${{ github.ref_name }}-linux-arm64.tar.gz
dist/hub/termipod-host-runner-${{ github.ref_name }}-darwin-amd64.tar.gz
dist/hub/termipod-host-runner-${{ github.ref_name }}-darwin-arm64.tar.gz
dist/hub/SHA256SUMS
body: |
**Mobile (Android):**
- `termipod-${{ github.ref_name }}-arm64-v8a.apk` — every modern Android phone/tablet (Android 8+). 32-bit and x86_64 builds are no longer published.
**Server-side binaries** (statically linked, no CGO) — one
tarball per binary, per ADR-028. Each expands to a single
bare binary:
- `termipod-hub-server-*` — for the host running the hub daemon
- `termipod-host-runner-*` — for every host that executes agents
Platforms: `linux-amd64` (VPS, x86_64) · `linux-arm64`
(ARM servers, Raspberry Pi 4/5, Ampere) · `darwin-amd64`
(Intel Macs) · `darwin-arm64` (Apple Silicon Macs).
`SHA256SUMS` covers all eight tarballs; `hub-server
self-update` / `host-runner self-update` verify their
artifact against it. Drop the binaries on `$PATH` and run
`hub-server help`.
generate_release_notes: true