-
Notifications
You must be signed in to change notification settings - Fork 0
412 lines (369 loc) · 19.6 KB
/
Copy pathtauri-release.yml
File metadata and controls
412 lines (369 loc) · 19.6 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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
name: Desktop release
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: write
# Serialise simultaneous runs against the same tag so two parallel workflow
# invocations can't race on the same draft release (which is what created the
# orphaned v0.5.0 + missing macOS Intel .dmg + corrupted latest.json mess).
concurrency:
group: tauri-release-${{ github.ref }}
cancel-in-progress: false
jobs:
# v0.8.6 — single-job draft creation. Pre-v0.8.6 each matrix
# platform job called tauri-action with `releaseDraft: true`, and
# tauri-action's "find or create" race window let two parallel
# platforms each create their own draft for the same tag (the
# v0.8.4 incident: 6 Linux assets in one draft, 10 mac+windows in
# another). This serialises draft creation into ONE pre-step job
# whose output (release_id) every matrix build then targets via
# tauri-action's `releaseId` parameter. No race possible.
#
# If the draft already exists (workflow re-run after a flake), we
# reuse it instead of creating a duplicate.
create-draft:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
outputs:
release_id: ${{ steps.create.outputs.release_id }}
steps:
- name: Find or create draft release
id: create
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: |
set -e
# First: is there already a draft for this tag? (Re-run case.)
RELEASE_ID=$(gh api "repos/$REPO/releases?per_page=50" \
--jq "[.[] | select(.tag_name==\"$TAG\")] | sort_by(.created_at) | reverse | .[0].id // empty")
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "null" ]; then
echo "Reusing existing release id $RELEASE_ID for tag $TAG"
else
echo "Creating new draft for $TAG..."
RELEASE_ID=$(gh api -X POST "repos/$REPO/releases" \
-f tag_name="$TAG" \
-f name="🐿️ TAMIAS $TAG" \
-f body="Build in progress — see CHANGELOG.md for what's in this release." \
-F draft=true \
-F prerelease=false \
--jq '.id')
echo "Created release id $RELEASE_ID"
fi
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
build:
needs: create-draft
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
target: aarch64-apple-darwin
artifact: tamias-macos-arm64
- platform: macos-latest
target: x86_64-apple-darwin
artifact: tamias-macos-x64
- platform: ubuntu-22.04
target: x86_64-unknown-linux-gnu
artifact: tamias-linux-x64
- platform: windows-latest
target: x86_64-pc-windows-msvc
artifact: tamias-windows-x64
runs-on: ${{ matrix.platform }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
src-tauri/target
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('src-tauri/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-cargo-
- name: Linux system dependencies
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
- name: Install npm dependencies
run: npm ci --no-audit --no-fund
- name: Build frontend
run: npm run build
- name: Tauri build (signed + notarised)
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Tauri updater signing — the in-app auto-updater verifies each
# bundle against the public key embedded in tauri.conf.json.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# Apple Developer code signing + notarisation (macOS only).
# tauri-action picks these up automatically; the build still runs
# if they are absent (bundle ships unsigned with a Gatekeeper
# warning). See docs/SIGNING.md to provision.
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Windows code signing (Authenticode). Optional. tauri.conf.json
# references env vars in bundle.windows.signCommand when set, OR
# tauri-action's WINDOWS_CERTIFICATE flow can be used. Add the
# secrets and uncomment the relevant tauri.conf.json lines per
# docs/SIGNING.md when ready.
# WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
# WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
with:
# v0.8.6 — point tauri-action at the existing draft created
# by the `create-draft` job. With `releaseId` set,
# tauri-action uploads to the existing release instead of
# going through its racy "find by tagName, else create"
# path. tagName + releaseName are kept for tauri-action
# body-update fallback when releaseId is empty.
releaseId: ${{ needs.create-draft.outputs.release_id }}
tagName: ${{ github.ref_name }}
releaseName: '🐿️ TAMIAS ${{ github.ref_name }}'
releaseBody: |
## 🐿️ TAMIAS ${{ github.ref_name }}
Browser-only PWA for medical-imaging ONNX inference, packaged as a desktop app.
**No upload · No telemetry · WebGPU on every GPU vendor.**
### 📥 Download
| Platform | Installer |
|---|---|
| 🍎 **macOS** Apple Silicon | [`Tamias_aarch64.dmg`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_aarch64.dmg) |
| 🍎 **macOS** Intel | [`Tamias_x64.dmg`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_x64.dmg) |
| 🪟 **Windows** (NSIS) | [`Tamias_x64-setup.exe`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_x64-setup.exe) |
| 🪟 **Windows** (MSI) | [`Tamias_x64_en-US.msi`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_x64_en-US.msi) |
| 🐧 **Linux** AppImage | [`Tamias_amd64.AppImage`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_amd64.AppImage) |
| 🐧 **Linux** Debian/Ubuntu | [`Tamias_amd64.deb`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_amd64.deb) |
| 🐧 **Linux** Fedora/RHEL | [`Tamias_x86_64.rpm`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Tamias_x86_64.rpm) |
| 🧪 **Smoke-test kit** | [`tamias-examples.zip`](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tamias-examples.zip) (CT NIfTI + tiny ONNX + manifest, ~470 KB) |
### 🔐 What's signed
- 🍎 macOS bundles are **code-signed** with our Apple Developer ID and **notarised by Apple** — installs cleanly without Gatekeeper warnings.
- 🔄 All bundles are **signed with our Tauri updater Ed25519 key** — the in-app auto-updater verifies the signature before applying.
- 🪟 Windows is currently distributed unsigned — SmartScreen will show a "More info → Run anyway" warning the first time. Authenticode signing is on the roadmap.
### 🔄 Auto-update
Installed users on `< ${{ github.ref_name }}` will be offered this update on next launch (or within ~6 hours via the in-app **About → Check for updates**). Bundles are verified against `latest.json` (in this release) before installing.
### 🧠 What you'll need to actually run inference
TAMIAS doesn't ship medical AI models — bring your own ONNX. Or click **Examples → Download** in the app to fetch a tiny smoke-test kit and run an end-to-end inference in seconds. See [`examples/README.md`](https://github.com/${{ github.repository }}/blob/main/examples/README.md) for sources of real medical models (MONAI Model Zoo, TotalSegmentator, MedSAM, …).
### 📚 Learn more
- 🌐 [Try the browser PWA](https://github.com/${{ github.repository }}#%EF%B8%8F-deploy-on-a-server-clone--run) (no install)
- 📦 [Deploy on a server](https://github.com/${{ github.repository }}/blob/main/docs/DEPLOY.md)
- 🛠️ [Auto-updater + signing setup](https://github.com/${{ github.repository }}/blob/main/docs/UPDATER.md)
- 🗺️ [Roadmap](https://github.com/${{ github.repository }}/blob/main/docs/ROADMAP.md)
- 📜 [Changelog](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)
### ⚠️ Research Use Only
TAMIAS is for research, education, and demonstration purposes. **Not cleared for clinical decision-making.** Every export is stamped accordingly.
releaseDraft: true
prerelease: false
# Disabled: tauri-action's per-job latest.json upload races across the
# matrix. We reassemble it once in alias-assets after all builds are
# done — see "Reassemble latest.json from all .sig files" below.
includeUpdaterJson: false
args: --target ${{ matrix.target }}
# Republish each platform-specific bundle under a stable, version-less alias
# so the README download buttons always point at
# /releases/latest/download/TAMIAS_<arch>.<ext>
# without needing a per-release README edit. Also attach the example test
# kit as release artifacts so the in-app "Download examples" button has a
# consistent fetch URL.
alias-assets:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Resolve release id (works for draft + published)
id: rel
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
set -e
# `gh api releases/tags/{tag}` ONLY finds PUBLISHED releases — drafts return 404.
# tauri-action ships releases as drafts (releaseDraft: true), so we must look
# them up by listing /releases and filtering on tag_name. We also pick the
# NEWEST matching release (by created_at desc) so a re-run that creates a
# second draft for the same tag still resolves to the latest one.
RELEASE_ID=$(gh api "repos/${{ github.repository }}/releases?per_page=50" \
--jq "[.[] | select(.tag_name==\"$TAG\")] | sort_by(.created_at) | reverse | .[0].id")
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
echo "::error::No release found for tag $TAG (draft or published)."
exit 1
fi
echo " resolved release id: $RELEASE_ID for tag $TAG"
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
- name: Mirror versioned bundles → versionless aliases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
RELEASE_ID: ${{ steps.rel.outputs.release_id }}
run: |
set -e
# List assets via the release-id endpoint so this works on draft releases too.
gh api "repos/${{ github.repository }}/releases/$RELEASE_ID/assets?per_page=100" --jq '.[].name' > /tmp/assets.txt
echo "Assets in $TAG (release id $RELEASE_ID):"
sed 's|^| |' /tmp/assets.txt
mkdir -p /tmp/dl
# Patterns are productName-agnostic — `tauri-action` uses whatever
# `productName` is in tauri.conf.json (TAMIAS, Tamias, Foo, …) as
# the file prefix. Matching on a generic capture group keeps the
# alias step working when the productName casing changes (which is
# what broke v0.5.6: TAMIAS → Tamias). The capture group preserves
# the actual prefix so alias names stay consistent with the rest
# of the release.
while IFS= read -r name; do
case "$name" in
# `<Prefix>_<X.Y.Z>_<arch>.<ext>` — strip the version segment.
*_*.dmg | \
*_*-setup.exe | *_*-setup.exe.sig | \
*_*_en-US.msi | *_*_en-US.msi.sig | \
*_*.AppImage | *_*.AppImage.sig | \
*_*.deb | *_*.deb.sig)
alias=$(echo "$name" | sed -E 's|^([A-Za-z0-9]+)_[0-9]+\.[0-9]+\.[0-9]+_|\1_|')
;;
# RPM uses dash separators + a release number:
# `<Prefix>-<X.Y.Z>-<release>.x86_64.rpm[.sig]`
*-[0-9]*-[0-9]*.x86_64.rpm | *-[0-9]*-[0-9]*.x86_64.rpm.sig)
alias=$(echo "$name" | sed -E 's|^([A-Za-z0-9]+)-[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.x86_64|\1_x86_64|')
;;
*)
alias="$name"
;;
esac
if [ "$alias" = "$name" ]; then
echo " skip (already alias / no version): $name"
continue
fi
echo " alias: $name → $alias"
gh release download "$TAG" -p "$name" -D /tmp/dl --clobber
mv "/tmp/dl/$name" "/tmp/dl/$alias"
gh release upload "$TAG" "/tmp/dl/$alias" --repo "${{ github.repository }}" --clobber
rm -f "/tmp/dl/$alias"
done < /tmp/assets.txt
# Single-writer reassembly of latest.json from every .sig produced by the
# matrix builds. This replaces tauri-action's per-job latest.json upload
# (disabled above with includeUpdaterJson:false) which races across the
# parallel matrix and lost macOS entries on v0.5.0.
- name: Reassemble latest.json from all .sig files
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
RELEASE_ID: ${{ steps.rel.outputs.release_id }}
run: |
set -e
REPO=${{ github.repository }}
BASE_URL="https://github.com/$REPO/releases/download/$TAG"
VERSION=${TAG#v}
mkdir -p /tmp/sigs
rm -f /tmp/sigs/*
# Fetch every .sig present on the release. Order doesn't matter.
gh api "repos/$REPO/releases/$RELEASE_ID/assets?per_page=100" \
--jq '.[] | select(.name|endswith(".sig")) | .name' > /tmp/sig_names.txt
echo ".sig files on release:"
sed 's|^| |' /tmp/sig_names.txt
while IFS= read -r sig; do
gh release download "$TAG" --repo "$REPO" -p "$sig" -D /tmp/sigs --clobber
done < /tmp/sig_names.txt
# Pull the release body so latest.json's `notes` field stays in sync.
gh api "repos/$REPO/releases/$RELEASE_ID" --jq '.body' > /tmp/notes.md
# Helper: jq-merge one platform entry only if its .sig actually exists.
PLATFORMS='{}'
add_platform() {
local key="$1" sig_file="$2" bundle="$3"
if [ -f "/tmp/sigs/$sig_file" ]; then
local sig_b64
sig_b64=$(cat "/tmp/sigs/$sig_file")
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg k "$key" --arg s "$sig_b64" --arg u "$BASE_URL/$bundle" \
'.[$k] = {signature: $s, url: $u}')
echo " + $key → $bundle"
else
echo " - $key → $sig_file MISSING (skipped)"
fi
}
# Discover the productName prefix from one of the .sig files we
# just downloaded. tauri-action prefixes EVERY artifact with
# whatever `productName` is set in tauri.conf.json (TAMIAS, Tamias,
# Foo, …). Hardcoding the prefix here is what broke v0.5.6 when
# productName was changed from "TAMIAS" to "Tamias" — the alias
# mirror + this latest.json reassembly silently no-op'd because
# the patterns no longer matched. Derive it from the AppImage
# .sig (Linux always builds, has a fully-versioned filename).
PREFIX=$(ls /tmp/sigs/ 2>/dev/null \
| grep -E "_${VERSION}_amd64\.AppImage\.sig$" \
| head -1 \
| sed -E "s|_${VERSION}_amd64\.AppImage\.sig||")
if [ -z "$PREFIX" ]; then
# Fallback — try the macOS .app.tar.gz.sig (versionless).
PREFIX=$(ls /tmp/sigs/ 2>/dev/null \
| grep -E '_aarch64\.app\.tar\.gz\.sig$' \
| head -1 \
| sed -E 's|_aarch64\.app\.tar\.gz\.sig$||')
fi
if [ -z "$PREFIX" ]; then
echo "::warning::could not derive productName prefix from .sig files; latest.json will be empty"
else
echo " resolved productName prefix: '$PREFIX'"
fi
# Tauri v2 platform keys + their updater bundles. .dmg files are NOT
# the updater target on macOS — .app.tar.gz is. On Linux the AppImage
# is the canonical updater bundle. On Windows we point at the NSIS
# installer (matches tauri-action's default).
add_platform "darwin-aarch64" "${PREFIX}_aarch64.app.tar.gz.sig" "${PREFIX}_aarch64.app.tar.gz"
add_platform "darwin-x86_64" "${PREFIX}_x64.app.tar.gz.sig" "${PREFIX}_x64.app.tar.gz"
add_platform "linux-x86_64" "${PREFIX}_${VERSION}_amd64.AppImage.sig" "${PREFIX}_${VERSION}_amd64.AppImage"
add_platform "windows-x86_64" "${PREFIX}_${VERSION}_x64-setup.exe.sig" "${PREFIX}_${VERSION}_x64-setup.exe"
jq -n \
--arg v "$VERSION" \
--arg pd "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
--rawfile notes /tmp/notes.md \
--argjson p "$PLATFORMS" \
'{version:$v, notes:$notes, pub_date:$pd, platforms:$p}' \
> /tmp/latest.json
echo "--- latest.json platform keys ---"
jq '.platforms | keys' /tmp/latest.json
gh release upload "$TAG" /tmp/latest.json --repo "$REPO" --clobber
- name: Attach example test kit to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
set -e
# Pack the smoke-test kit into a single zip + upload individual files
# so the in-app downloader has a stable URL.
mkdir -p /tmp/kit
cp examples/CT_AVM.nii.gz examples/threshold_seg.onnx examples/threshold_seg.json /tmp/kit/
(cd /tmp/kit && zip -j tamias-examples.zip CT_AVM.nii.gz threshold_seg.onnx threshold_seg.json)
for f in tamias-examples.zip CT_AVM.nii.gz threshold_seg.onnx threshold_seg.json; do
gh release upload "$TAG" "/tmp/kit/$f" --repo "${{ github.repository }}" --clobber
echo " uploaded $f"
done