-
Notifications
You must be signed in to change notification settings - Fork 131
269 lines (249 loc) · 10.1 KB
/
Copy pathrelease.yml
File metadata and controls
269 lines (249 loc) · 10.1 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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
name: Release
on:
push:
tags:
- 'v3.*'
workflow_dispatch:
inputs:
publish_release:
description: 'Publish a GitHub release with the artifacts'
type: boolean
default: false
permissions:
contents: write
env:
PYTHON_VERSION: '3.12.13'
PBS_RELEASE: '20260414'
UV_VERSION: '0.11.8'
jobs:
guard:
name: Verify tag is on v3 branch
# Skip the branch check on manual dispatch — the user picks the ref
# themselves in the dispatch UI, and there is no tag to verify.
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check tag reachable from origin/v3
run: |
set -euo pipefail
git fetch origin v3
if ! git merge-base --is-ancestor "${GITHUB_SHA}" origin/v3; then
echo "tag ${GITHUB_REF} commit ${GITHUB_SHA} is not on the v3 branch" >&2
exit 1
fi
build:
needs: guard
if: ${{ always() && (needs.guard.result == 'success' || needs.guard.result == 'skipped') }}
name: ${{ matrix.target }}
strategy:
fail-fast: false
matrix:
include:
# Linux: ubuntu-22.04 keeps glibc old enough that the binaries
# run on most user machines without forced glibc upgrades.
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: macos-14
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
# macos-14 ships a Homebrew `cargo` shim at /opt/homebrew/bin/cargo
# that forwards to `rustup-init`, and Homebrew's bin precedes
# ~/.cargo/bin in PATH — so plain `cargo <subcmd>` (including
# `cargo tauri build`) resolves to rustup-init and fails with
# "unexpected argument 'tauri'". Prepend the dtolnay-installed cargo
# to GITHUB_PATH so every later step sees the real binary first.
- name: Prefer ~/.cargo/bin over Homebrew rustup-init (macOS)
if: runner.os == 'macOS'
shell: bash
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ matrix.target }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
# build.rs runs prost-build to compile liqi.proto and needs protoc
# on PATH. arduino/setup-protoc handles all three OSes.
- name: Setup protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# tauri-cli build is slow (~3 min). Pull the prebuilt binary via
# taiki-e/install-action instead — sidesteps GitHub Actions' cache
# scope rules (tag pushes can only read caches from the tag ref or
# the default branch, which here is v2 and never runs this workflow,
# so a cargo-install cache never hits). We previously used
# cargo-bins/cargo-binstall directly, but on macos-14 runners with
# Homebrew rustup pre-installed the `cargo` proxy resolves to
# rustup-init and `cargo binstall` fails; taiki-e/install-action
# invokes the binstall binary with an absolute path and avoids that.
- name: Install tauri-cli
uses: taiki-e/install-action@v2
with:
tool: tauri-cli@2.11.1
- name: Linux system deps
if: runner.os == 'Linux'
# Hard cap the step: apt has no stall-timeout for downloads, so a
# single wedged mirror connection can hang until the 6 h runner
# limit (it did on the v3.2.0 run, stuck mid-fetch of the 27 MB
# libwebkit2gtk package). 12 min is ample for a ~55 MB install.
timeout-minutes: 12
env:
DEBIAN_FRONTEND: noninteractive
run: |
set -euo pipefail
# Bound each socket to 30 s and retry, so a stalled download
# aborts and re-fetches instead of trickling forever.
apt_opts=(-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30)
sudo apt-get "${apt_opts[@]}" update
sudo apt-get "${apt_opts[@]}" install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libssl-dev \
patchelf \
libfuse2
# Cache fetched python + uv tarballs by version triple. The runtime
# is shipped inside every zip, so the fetch is unconditional.
- name: Cache bundled runtime
uses: actions/cache@v4
with:
path: |
runtime/python/${{ matrix.target }}
runtime/uv/${{ matrix.target }}
key: runtime-${{ matrix.target }}-py${{ env.PYTHON_VERSION }}-pbs${{ env.PBS_RELEASE }}-uv${{ env.UV_VERSION }}
- name: Fetch python+uv
shell: bash
env:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
PBS_RELEASE: ${{ env.PBS_RELEASE }}
UV_VERSION: ${{ env.UV_VERSION }}
run: bash scripts/fetch-runtime.sh "${{ matrix.target }}"
# `--no-bundle` skips the (now empty) installer phase. We keep
# `cargo tauri build` (vs. plain `cargo build`) so the
# `beforeBuildCommand` in tauri.conf.json — `npm ci && npm run
# build` — still runs and the frontend is rebuilt fresh.
- name: Build
shell: bash
env:
NO_STRIP: '1'
run: cargo tauri build --no-bundle --target ${{ matrix.target }}
- name: Package zip
shell: bash
run: bash scripts/package-zip.sh "${{ matrix.target }}"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: akagi-${{ matrix.target }}
path: dist/*.zip
if-no-files-found: error
retention-days: 14
# Sign every zip with the project minisign key. The in-app updater
# requires these signatures whenever the download went through an
# accelerator mirror (see src/github/signing.rs), and the trusted
# comment (= asset filename) stops a mirror from answering with a
# validly-signed *older* release. Missing secret is a warning, not a
# failure, so releases stay publishable during key setup — but they
# ship unsigned and mirror-based updates will refuse them.
#
# A separate job (not a step of `release`) so a plain
# workflow_dispatch — which skips `release` — still exercises the
# signing path end-to-end without publishing anything.
sign:
name: Sign artifacts
needs: build
# `always()` because the implicit success() condition requires every
# *transitive* ancestor to have succeeded — and on workflow_dispatch
# the guard job is skipped, which would silently skip this job too
# (same reason `build` carries it).
if: ${{ always() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
# Needed for minisign.pub, which the fresh signatures are verified
# against before anything ships.
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Sign and verify (minisign)
env:
MINISIGN_SECRET_KEY: ${{ secrets.MINISIGN_SECRET_KEY }}
run: |
set -euo pipefail
if [[ -z "${MINISIGN_SECRET_KEY:-}" ]]; then
# A tagged release must never silently ship unsigned: the
# in-app updater refuses unsigned assets over mirrors, so the
# breakage would surface only for the users this feature
# exists for, one release too late. Manual dispatch keeps
# the soft path for pre-secret experimentation.
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
echo "::error::MINISIGN_SECRET_KEY secret is not set — refusing to publish an unsigned tagged release"
exit 1
fi
echo "::warning::MINISIGN_SECRET_KEY secret is not set — assets will be UNSIGNED"
exit 0
fi
cargo install rsign2 --locked
keyfile="$(mktemp)"
printf '%s\n' "$MINISIGN_SECRET_KEY" > "$keyfile"
for f in artifacts/*.zip; do
name="$(basename "$f")"
rsign sign -W -s "$keyfile" -x "$f.minisig" -t "$name" "$f"
done
rm -f "$keyfile"
# Verify against the tracked public key — catches a mismatched
# or rotated secret before anything ships.
for f in artifacts/*.zip; do
rsign verify -p minisign.pub -x "$f.minisig" "$f"
done
- name: Upload signatures
uses: actions/upload-artifact@v4
with:
name: signatures
path: artifacts/*.minisig
# Empty when the secret is missing (unsigned publish is allowed).
if-no-files-found: warn
retention-days: 14
release:
name: Publish GitHub release
needs: sign
# Only publish on tag push, or when manually dispatched with the
# publish_release input checked. Plain workflow_dispatch (no tag, no
# input) just leaves artifacts + signatures on the run page.
# `always() && sign == success` for the same skipped-guard reason as
# `sign` — and a failed sign job must block publishing.
if: ${{ always() && needs.sign.result == 'success' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish_release)) }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# Pulls the platform zips and the `signatures` artifact into one
# flat directory.
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- run: ls -la artifacts
- uses: softprops/action-gh-release@v2
with:
files: artifacts/**/*
generate_release_notes: true
fail_on_unmatched_files: true
# make_latest: false