-
Notifications
You must be signed in to change notification settings - Fork 1
325 lines (308 loc) · 16.1 KB
/
release.yml
File metadata and controls
325 lines (308 loc) · 16.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
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
name: Release
on:
push:
tags:
- "v*"
jobs:
release:
name: release (${{ matrix.os }})
strategy:
# Never fail-fast — a Windows bundle failure should not cancel the
# Linux release. Users depend on the Linux AppImage/deb even if the
# Windows NSIS installer is broken for a given tag.
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-latest]
# ubuntu-22.04 is pinned (not ubuntu-latest) so the AppImage stays
# compatible with the oldest reasonably-supported glibc. When
# ubuntu-latest rolls over to 24.04, the AppImage would silently
# require a newer glibc and break users on older distros.
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libwebkit2gtk-4.1-dev \
build-essential \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libfuse2 \
file
# libfuse2 is required by tauri build's AppImage packager; kept here
# (unlike ci.yml which excludes it) because this workflow produces
# actual bundles, not just type-checks.
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
# Installs the stable toolchain for whichever OS the job is running
# on — MSVC on windows-latest, GNU on ubuntu-22.04. No toolchain
# override needed.
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri -> target
# Per-OS cache key so Linux and Windows release artifacts never
# collide in the same cache entry. Matches the convention used
# by ci.yml.
key: ${{ matrix.os }}
- name: Setup Node
uses: actions/setup-node@v5
with:
# Node 22 for the project's npm scripts. The action itself is on
# @v5 (Node 24 runtime) so it won't trip GitHub's Node-20-actions
# deprecation that takes effect 2026-06-02.
# See: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
node-version: 22
cache: npm
- name: Install frontend dependencies
run: npm ci
- name: Setup Bun
# The claude-agent sidecar (sidecar/claude-agent/) is a small
# TypeScript project compiled to a standalone binary with `bun
# build --compile`. Installed here so the stage step below can
# produce the per-target sidecar binary before tauri-action
# picks it up via `externalBin`.
#
# Pinned to 1.2.21 (not `latest`) because Bun 1.3+ compiles its
# standalone binaries against glibc 2.36+ symbols. The Linux
# release runner is ubuntu-22.04 (glibc 2.35), and linuxdeploy's
# AppImage step runs `ldd` on every executable in usr/bin/ — which
# invokes the dynamic loader. If the binary needs glibc symbols
# the loader can't resolve, ldd exits 1 and linuxdeploy aborts
# with `Failed to run ldd: exited with code 1`. Surfaced as the
# opaque "failed to run linuxdeploy" without --verbose. Bun 1.2.21
# targets glibc 2.31+, which the runner satisfies.
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.2.21"
- name: Stage claude-agent sidecar binary
# Mirrors the agent-browser stage below: explicit pre-stage
# rather than relying on tauri's beforeBuildCommand, so
# failures show up in a dedicated log step.
shell: bash
run: |
bash scripts/build-claude-sidecar.sh
TARGET="${CARGO_BUILD_TARGET:-$(rustc -vV | grep host | cut -d' ' -f2)}"
case "$TARGET" in
*windows*) DEST="src-tauri/binaries/codemux-claude-sidecar-$TARGET.exe" ;;
*) DEST="src-tauri/binaries/codemux-claude-sidecar-$TARGET" ;;
esac
if [ ! -s "$DEST" ]; then
echo "::error::build-claude-sidecar.sh did not produce a non-empty binary at $DEST"
echo "::error::Target: $TARGET"
ls -la src-tauri/binaries/ || true
exit 1
fi
- name: Stage agent-browser sidecar binary
# `shell: bash` works on both Linux and windows-latest because Git
# for Windows (which ships bash.exe) is preinstalled in the runner
# image. Pre-staging explicitly — rather than relying on tauri's
# beforeBuildCommand to invoke the script — makes failures easier
# to diagnose in the logs and matches the pattern established in
# ci.yml.
shell: bash
run: |
bash scripts/copy-agent-browser.sh
# Sanity-check: the script MUST place a real binary at the expected
# path for the current target. A missing file would let tauri build
# silently produce an installer with no sidecar, which would fail
# at runtime on the user's machine. Fail fast here instead.
TARGET="${CARGO_BUILD_TARGET:-$(rustc -vV | grep host | cut -d' ' -f2)}"
case "$TARGET" in
*windows*) DEST="src-tauri/binaries/agent-browser-$TARGET.exe" ;;
*) DEST="src-tauri/binaries/agent-browser-$TARGET" ;;
esac
if [ ! -s "$DEST" ]; then
echo "::error::copy-agent-browser.sh did not produce a non-empty binary at $DEST"
echo "::error::Target: $TARGET"
ls -la src-tauri/binaries/ || true
ls -la node_modules/agent-browser/bin/ || true
exit 1
fi
- name: Build codemux-remote binary (Linux x86_64 only)
# Used by the push-to-host (cloud-push) feature: this binary is
# scp'd to the user's remote SSH host the first time they push a
# workspace. Bundled as a Tauri resource so the laptop app can
# find it via app.path().resource_dir() at install time.
#
# Scoped to ubuntu-22.04 + x86_64-unknown-linux-gnu for v1:
# - Windows Codemux can't push (remote daemon is #[cfg(unix)])
# - ARM64 Linux + macOS remotes need additional rustup targets
# and (for macOS) a macOS runner — deferred until demand
# If those expand later, add `--target` flags here for each
# extra triple and stage them at the matching path.
if: matrix.os == 'ubuntu-22.04'
shell: bash
run: |
# Chicken-and-egg: `codemux-remote` is a [[bin]] inside the same
# Cargo package as `codemux` (the Tauri app), so `cargo build
# --bin codemux-remote` triggers that package's build.rs
# (tauri_build::build()). The Tauri build script scans
# tauri.conf.json's `bundle.resources = ["binaries/codemux-remote-*"]`
# glob and panics if nothing matches — but on a fresh CI checkout,
# nothing matches *until* we build the binary. Plant an empty stub
# at the glob target first so the resource scan passes; we
# overwrite it with the real binary on the next line.
mkdir -p src-tauri/binaries
touch src-tauri/binaries/codemux-remote-x86_64-unknown-linux-gnu
cargo build --release --bin codemux-remote --manifest-path src-tauri/Cargo.toml
cp src-tauri/target/release/codemux-remote \
src-tauri/binaries/codemux-remote-x86_64-unknown-linux-gnu
chmod +x src-tauri/binaries/codemux-remote-x86_64-unknown-linux-gnu
# Sanity-check: the file must be non-empty and executable. A
# broken binary here would ship a release whose cloud-push
# feature dies at first use with "Codemux build doesn't
# include codemux-remote for x86_64-unknown-linux-gnu".
if [ ! -s src-tauri/binaries/codemux-remote-x86_64-unknown-linux-gnu ]; then
echo "::error::codemux-remote build did not produce a non-empty binary"
exit 1
fi
ls -la src-tauri/binaries/codemux-remote-*
- name: Configure git identity
# Some tauri build code paths call `git rev-parse` to embed a commit
# hash into the binary metadata. Those calls succeed on a detached
# tag checkout, but git also warns on missing user.email/user.name
# in some subcommands — preempting that here with a throwaway
# identity is cheap and avoids noise in the logs. Works on both
# platforms because `git config --global` writes to $HOME on Linux
# and %USERPROFILE% on Windows.
shell: bash
run: |
git config --global user.email "release@codemux.dev"
git config --global user.name "Codemux Release"
# ── Multi-platform updater signing notes ─────────────────────────
#
# Both platforms pass the SAME Ed25519 updater key and both set
# `includeUpdaterJson: true`. This works — despite each job
# producing its own per-platform signature set — because
# `tauri-action`'s `uploadVersionJSON` step (source:
# https://github.com/tauri-apps/tauri-action/blob/dev/src/upload-version-json.ts)
# is designed to MERGE with any pre-existing `latest.json` on the
# release:
#
# 1. listReleaseAssets() → looks for an existing `latest.json`
# 2. If found, downloads it and pre-seeds `versionContent.platforms`
# with the existing `platforms` object
# 3. Adds THIS job's platform entries (e.g. `linux-x86_64` or
# `windows-x86_64`) to that object
# 4. deleteReleaseAsset() + uploadAssets() the merged file
#
# The result is a single `latest.json` with both Linux and Windows
# entries, which matches the Tauri v2 updater schema:
# https://v2.tauri.app/plugin/updater/
#
# Race window: if both jobs call listReleaseAssets() before EITHER
# has uploaded its `latest.json`, the second upload will clobber
# the first (last-writer-wins). In practice the race is unlikely —
# ubuntu-22.04 completes the build several minutes before
# windows-latest (Rust compile + NSIS bundling are significantly
# slower on Windows). Worst case: one release has a stale
# `latest.json` missing one platform; users on that platform miss
# a single update cycle but are not bricked, and the next release
# recovers automatically. We accept this over adding a third
# merge-updater job because tauri-action's built-in merge is more
# reliable than anything we'd write ourselves.
#
# The Ed25519 key is cross-platform. The same private key signs
# Linux AppImage/deb/rpm and Windows NSIS `.exe` bundles; clients
# verify per-platform signatures out of the single `latest.json`.
# This is DISTINCT from Windows Authenticode code signing, which
# is not yet configured (see Windows step TODO below).
- name: Pre-cache stable linuxdeploy + plugin-appimage
if: matrix.os == 'ubuntu-22.04'
# Tauri's bundler downloads `linuxdeploy-plugin-appimage` fresh from
# the `:continuous` tag on every build. That tag is mutable and was
# rebuilt on 2026-05-01 with breaking changes that crash on startup
# against tauri's vendored 2022-era linuxdeploy — surfaced as the
# opaque "failed to run linuxdeploy" because tauri-bundler captures
# but discards linuxdeploy's stderr on non-zero exit.
#
# Workaround: pre-place a known-good version in ~/.cache/tauri/ so
# tauri's "download if missing" check finds the file and skips the
# broken `:continuous` fetch. We pin to `1-alpha-20250213-1`, the
# most recent immutable tag (Feb 2025), which matches the version
# active during the v0.1.33 release on 2026-04-26.
#
# Linuxdeploy itself is downloaded from tauri-apps/binary-releases
# (vendored, frozen since 2022) so we leave that alone — only the
# plugin needs pinning.
run: |
mkdir -p ~/.cache/tauri
curl -fsSL -o ~/.cache/tauri/linuxdeploy-plugin-appimage.AppImage \
https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x ~/.cache/tauri/linuxdeploy-plugin-appimage.AppImage
ls -la ~/.cache/tauri/
- name: Build and release (Linux)
if: matrix.os == 'ubuntu-22.04'
uses: tauri-apps/tauri-action@action-v0.6.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# Defensive: skip linuxdeploy's strip step. webkit2gtk 2.50.4 ships
# libs with .relr.dyn ELF sections that older binutils strip can
# mangle. Not the root cause of the v0.2.0 failure (that was the
# plugin-appimage `:continuous` rebuild — see pre-cache step above)
# but cheap insurance against a future similar incident.
NO_STRIP: "true"
with:
tagName: ${{ github.ref_name }}
releaseName: "Codemux ${{ github.ref_name }}"
releaseDraft: false
prerelease: false
includeUpdaterJson: true
# Diagnostic: tauri build's bundler captures linuxdeploy's stderr
# but only prints it when verbose. Without this flag, linuxdeploy
# failures appear as the opaque "failed to run linuxdeploy" with
# no clue as to the underlying cause. Worth the ~50 KB of extra
# log noise — keeps future incidents debuggable.
args: --verbose
- name: Build and release (Windows)
if: matrix.os == 'windows-latest'
uses: tauri-apps/tauri-action@action-v0.6.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Same Ed25519 updater key as the Linux job. Tauri CLI on
# Windows uses it to sign the NSIS `.exe` and produce the
# matching `.sig` file that tauri-action then references in
# the merged `latest.json` under the `windows-x86_64` key.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ github.ref_name }}
releaseName: "Codemux ${{ github.ref_name }}"
releaseDraft: false
prerelease: false
# Now true on Windows — tauri-action will merge this job's
# `windows-x86_64` entry into whatever `latest.json` the Linux
# job already uploaded. See the long comment above the two
# Build and release steps for the merge mechanics and race
# analysis.
includeUpdaterJson: true
# Restrict to the NSIS bundle. `bundle.targets = "all"` in
# tauri.conf.json would otherwise build both NSIS and MSI on
# Windows, and MSI requires the WiX Toolset which is NOT
# preinstalled on windows-latest runners — the MSI step would
# fail with "wix not found". NSIS is bundled with tauri-cli and
# needs no additional setup.
args: --bundles nsis
# TODO: Windows code signing — unsigned builds trigger SmartScreen
# warning on user machines. To enable, purchase an OV or EV
# Authenticode certificate and add:
# "certificateThumbprint": "...",
# "digestAlgorithm": "sha256",
# "timestampUrl": "http://timestamp.digicert.com"
# to tauri.conf.json under `bundle.windows`, and surface the
# cert via a GitHub secret + signtool step before tauri-action.
# Authenticode is DISTINCT from the Ed25519 updater key above
# — Authenticode is what Windows SmartScreen checks on install,
# Ed25519 is what the Tauri auto-updater checks before applying
# an update.