Compare commits

...
Author SHA1 Message Date
dtourolle 9c75e74ea3 fix(ci): give the builder image what linuxdeploy needs for the AppImage
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 15m52s
🏗️ Build and Test JellyTau / Supply Chain (push) Failing after 29s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m35s
Traceability Validation / Check Requirement Traces (push) Successful in 11s
Build & Release / Run Tests (push) Successful in 14m53s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m22s
Build & Release / Build Linux (push) Successful in 20m53s
Build & Release / Build Windows (push) Successful in 15m41s
Build & Release / Build Android (push) Successful in 30m46s
Build & Release / Create Release (push) Successful in 38s
The v0.10.0 release build failed in Build Linux after 16 minutes:

  failed to bundle project: xdg-open binary not found
  /usr/bin/xdg-open: No such file or directory

linuxdeploy embeds xdg-open into the AppImage and aborts the whole bundle
when it is absent. deb and rpm had already bundled fine; only AppImage
was affected.

This is the one failure tonight that building locally could not have
caught, and the reason is worth writing down: a developer machine is a
desktop and always has xdg-utils, so the AppImage builds there and fails
on a minimal server image. The asymmetry is the bug. Every other release
defect this evening was found by building locally first; this one needed
the runner.

xdg-utils, desktop-file-utils and zsync are added together rather than
one at a time. Each round trip costs an image rebuild plus a failed
release build, and those three are what linuxdeploy commonly reaches for
(xdg-open, desktop-file-validate, and zsync for delta updates).

Workflows move to jellytau-builder:2026.08.1, built and pushed with all
three verified present inside it before this commit.

ci-operations.md gains two things learned here: that an apt addition
invalidates the layer above the cargo-install steps, so it is a ~20 minute
rebuild rather than the ~2 minutes the trailing layer normally gives; and
that Tauri's AppImage bundler downloads linuxdeploy, AppRun and two plugin
scripts from GitHub during the build, so an AppImage build depends on
GitHub being reachable from the runner.
2026-08-22 02:52:32 +02:00
dtourolle 76a2d9609b fix(release): produce updater artifacts, and point the manifest at them
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 15m32s
🏗️ Build and Test JellyTau / Supply Chain (push) Failing after 29s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m34s
Traceability Validation / Check Requirement Traces (push) Successful in 11s
Build & Release / Run Tests (push) Successful in 14m49s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m22s
Build & Release / Build Linux (push) Failing after 17m42s
Build & Release / Build Windows (push) Successful in 15m46s
Build & Release / Build Android (push) Successful in 30m54s
Build & Release / Create Release (push) Skipped
Two defects on the release path, both of which would have failed the
v0.10.0 build after all three platforms had already compiled -- caught by
running a real signed build locally instead of waiting for the tag.

**createUpdaterArtifacts was never set.** Without it Tauri emits only the
plain .AppImage and .exe: no signatures at all. The manifest step then
finds none and aborts by design, so the release dies at Create Release
having spent ~40 minutes building artifacts it cannot publish.

**The manifest looked for the wrong filename.** Tauri v2 signs the
.AppImage *itself* and writes <name>.AppImage.sig beside it. The
.AppImage.tar.gz form this workflow globbed for only exists under
createUpdaterArtifacts: "v1Compatible". A real signed build produced:

  154M JellyTau_0.10.0_amd64.AppImage
  420  JellyTau_0.10.0_amd64.AppImage.sig

so the glob would have matched nothing and the step would have aborted
for a second, entirely different reason. Both the artifact collection and
the manifest now use the v2 names, and the AppImage and its .sig ship
together -- a manifest referencing a signature that was never uploaded
fails only on the user's machine.

Verified before tagging rather than after: the manifest logic was run
against the real artifacts (420-char minisign signature read correctly)
and the resulting latest.json checked for validity and shape.

The Windows side already used the correct pattern (<installer>.exe.sig),
which is why only Linux needed the change.
2026-08-21 23:14:16 +02:00
7 changed files with 59 additions and 15 deletions
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
if: "!startsWith(github.event.head_commit.message, 'chore(release)')" if: "!startsWith(github.event.head_commit.message, 'chore(release)')"
runs-on: linux/amd64 runs-on: linux/amd64
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -187,7 +187,7 @@ jobs:
runs-on: linux/amd64 runs-on: linux/amd64
needs: test needs: test
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
env: env:
ANDROID_HOME: /opt/android-sdk ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk ANDROID_SDK_ROOT: /opt/android-sdk
@@ -256,7 +256,7 @@ jobs:
name: Supply Chain name: Supply Chain
runs-on: linux/amd64 runs-on: linux/amd64
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
+18 -10
View File
@@ -21,7 +21,7 @@ jobs:
name: Run Tests name: Run Tests
runs-on: linux/amd64 runs-on: linux/amd64
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -94,7 +94,7 @@ jobs:
runs-on: linux/amd64 runs-on: linux/amd64
needs: test needs: test
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -190,12 +190,15 @@ jobs:
# Without nullglob an unmatched pattern stays literal, so test each # Without nullglob an unmatched pattern stays literal, so test each
# candidate instead. Same POSIX-only rule as traceability-check.yml. # candidate instead. Same POSIX-only rule as traceability-check.yml.
# #
# The .AppImage.tar.gz + .sig pair is what the updater downloads and # Tauri v2 signs the .AppImage ITSELF and writes <name>.AppImage.sig
# verifies; the plain .AppImage is what a human downloads. Both ship. # beside it -- there is no .AppImage.tar.gz unless
# bundle.createUpdaterArtifacts is set to "v1Compatible". The updater
# downloads the same AppImage a human does and verifies that .sig, so
# both files must ship or the manifest points at a signature nobody
# can fetch.
for bundle in \ for bundle in \
src-tauri/target/release/bundle/appimage/*.AppImage \ src-tauri/target/release/bundle/appimage/*.AppImage \
src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz \ src-tauri/target/release/bundle/appimage/*.AppImage.sig \
src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz.sig \
src-tauri/target/release/bundle/deb/*.deb \ src-tauri/target/release/bundle/deb/*.deb \
src-tauri/target/release/bundle/rpm/*.rpm; do src-tauri/target/release/bundle/rpm/*.rpm; do
[ -e "$bundle" ] || continue [ -e "$bundle" ] || continue
@@ -232,7 +235,7 @@ jobs:
# baked into the builder image. No toolchain installs here — the image has # baked into the builder image. No toolchain installs here — the image has
# cargo-xwin, clang/clang-cl, lld, llvm, nsis and the msvc target. # cargo-xwin, clang/clang-cl, lld, llvm, nsis and the msvc target.
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -305,7 +308,7 @@ jobs:
runs-on: linux/amd64 runs-on: linux/amd64
needs: test needs: test
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
env: env:
ANDROID_HOME: /opt/android-sdk ANDROID_HOME: /opt/android-sdk
ANDROID_SDK_ROOT: /opt/android-sdk ANDROID_SDK_ROOT: /opt/android-sdk
@@ -408,7 +411,7 @@ jobs:
needs: [build-linux, build-windows, build-android] needs: [build-linux, build-windows, build-android]
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -499,8 +502,13 @@ jobs:
APPIMAGE_URL="" APPIMAGE_URL=""
NSIS_URL="" NSIS_URL=""
for f in artifacts/linux/*.AppImage.tar.gz; do # Tauri v2 signs the AppImage itself; <name>.AppImage.sig sits beside
# it. Verified against a real signed build before tagging -- the
# v1-style .AppImage.tar.gz is never produced with
# createUpdaterArtifacts: true.
for f in artifacts/linux/*.AppImage; do
[ -e "$f" ] || continue [ -e "$f" ] || continue
case "$f" in *.sig) continue;; esac
APPIMAGE_URL="${BASE}/$(basename "$f")" APPIMAGE_URL="${BASE}/$(basename "$f")"
[ -e "$f.sig" ] && APPIMAGE_SIG="$(cat "$f.sig")" [ -e "$f.sig" ] && APPIMAGE_SIG="$(cat "$f.sig")"
done done
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
name: Build & publish docs to gitea-pages name: Build & publish docs to gitea-pages
runs-on: linux/amd64 runs-on: linux/amd64
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout code - name: Checkout code
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: linux/amd64 runs-on: linux/amd64
name: Check Requirement Traces name: Check Requirement Traces
container: container:
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08 image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
steps: steps:
- name: Checkout repository - name: Checkout repository
+11
View File
@@ -141,6 +141,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
lld \ lld \
llvm \ llvm \
nsis \ nsis \
# AppImage bundling. linuxdeploy embeds xdg-open into the AppImage and
# aborts the whole bundle if it is missing:
# failed to bundle project: xdg-open binary not found
# It is present on most desktop distros, which is why the AppImage built on
# a developer machine and failed here. desktop-file-utils and zsync are the
# other two linuxdeploy commonly wants (desktop-file-validate, and zsync for
# delta updates), added together so a missing one does not cost another
# image rebuild and another failed release build.
xdg-utils \
desktop-file-utils \
zsync \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
# Ubuntu's clang package ships clang but NOT the clang-cl alias that cc-rs # Ubuntu's clang package ships clang but NOT the clang-cl alias that cc-rs
# invokes for MSVC targets. clang-cl is the same binary in MSVC-compat mode, # invokes for MSVC targets. clang-cl is the same binary in MSVC-compat mode,
+24
View File
@@ -61,6 +61,11 @@ filled. Keep a couple of dated tags live and prune the rest.
The order matters — CI breaks if the workflow lands before the image exists. The order matters — CI breaks if the workflow lands before the image exists.
A caveat learned the hard way: the *trailing* layer is only fast for `cargo
install` tools. Adding an **apt** package invalidates the packaging layer, which
sits above the `cargo-xwin`/`cargo-deny` installs, so those recompile too — a
~20 minute rebuild rather than ~2.
```bash ```bash
# 1. Edit Dockerfile.builder. Put new tools in the TRAILING layer: it exists so # 1. Edit Dockerfile.builder. Put new tools in the TRAILING layer: it exists so
# a tool change is a ~2 min rebuild instead of ~15. # a tool change is a ~2 min rebuild instead of ~15.
@@ -103,6 +108,25 @@ transitive upgrade (bumping `tauri-plugin-log` to 2.9.0 also moved `wry`,
therefore video playback. That is a change to make deliberately, with a full therefore video playback. That is a change to make deliberately, with a full
build and a playback check — not one to slip into a release. build and a playback check — not one to slip into a release.
## AppImage needs more than the Rust toolchain
`linuxdeploy` (which Tauri downloads at build time to assemble the AppImage)
shells out to distro tools that a minimal server image does not have. It aborts
the whole bundle on the first one missing:
```
failed to bundle project: xdg-open binary not found
```
The image therefore carries `xdg-utils`, `desktop-file-utils` and `zsync`. This
is a class of failure that **cannot be caught by building locally**: a developer
machine is a desktop and has all three, so the AppImage builds there and fails in
CI. It cost one release build to find.
Tauri's AppImage bundler also downloads `linuxdeploy`, `AppRun` and two plugin
scripts from GitHub during the build. That is Tauri's behaviour, not ours, but it
means an AppImage build depends on GitHub being reachable from the runner.
## Secrets ## Secrets
Managed with the `tea` CLI (`tea actions secrets list`) or the repo settings UI. Managed with the `tea` CLI (`tea actions secrets list`) or the repo settings UI.
+1
View File
@@ -44,6 +44,7 @@
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"createUpdaterArtifacts": true,
"targets": [ "targets": [
"deb", "deb",
"rpm", "rpm",