mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(linux): make AppImage work on Mesa 25+ / GLib 2.88 distros (#1567)
This commit is contained in:
+101
-24
@@ -455,14 +455,66 @@ jobs:
|
||||
name: Release Linux
|
||||
if: github.repository == 'block/buzz'
|
||||
runs-on: ubuntu-latest
|
||||
# Digest-pinned like the SHA-pinned actions below; Renovate keeps it fresh.
|
||||
container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
# AppImage tools (linuxdeploy, appimagetool) are themselves AppImages.
|
||||
# Containers lack FUSE, so we must use the extract-and-run fallback.
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
outputs:
|
||||
archive_name: ${{ steps.linux-artifacts.outputs.archive_name }}
|
||||
sig: ${{ steps.read-sig.outputs.sig }}
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
# Must run first: bare ubuntu:22.04 ships without curl, wget, git, or
|
||||
# ca-certificates. activate-hermit bootstraps via curl+HTTPS (needs
|
||||
# both), and actions/checkout falls back to a REST tarball without git.
|
||||
# Running as root — no sudo needed.
|
||||
apt-get update \
|
||||
-o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 \
|
||||
-o Acquire::https::Timeout=30
|
||||
apt-get install -y --no-install-recommends \
|
||||
-o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 \
|
||||
-o Acquire::https::Timeout=30 \
|
||||
-o DPkg::Lock::Timeout=120 \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
curl \
|
||||
file \
|
||||
git \
|
||||
libasound2-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
libgtk-3-dev \
|
||||
librsvg2-dev \
|
||||
libssl-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libxdo-dev \
|
||||
patchelf \
|
||||
pkg-config \
|
||||
wget
|
||||
# Install GitHub CLI — preinstalled on runners but absent in containers.
|
||||
# wget and ca-certificates are now available from the step above.
|
||||
mkdir -p -m 755 /etc/apt/keyrings
|
||||
wget -q --tries=3 --timeout=30 -O /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
https://cli.github.com/packages/githubcli-archive-keyring.gpg
|
||||
# Pin the keyring like appimagetool below. If GitHub rotates the
|
||||
# keyring this fails loudly — recompute and update the hash.
|
||||
echo "6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b /usr/share/keyrings/githubcli-archive-keyring.gpg" | sha256sum -c
|
||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
||||
> /etc/apt/sources.list.d/github-cli.list
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends gh
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
@@ -475,31 +527,40 @@ jobs:
|
||||
workspaces: desktop/src-tauri
|
||||
lookup-only: true
|
||||
|
||||
- name: Install Tauri dependencies (Linux)
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
sudo apt-get update \
|
||||
-o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 \
|
||||
-o Acquire::https::Timeout=30
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
-o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 \
|
||||
-o Acquire::https::Timeout=30 \
|
||||
-o DPkg::Lock::Timeout=120 \
|
||||
build-essential \
|
||||
curl \
|
||||
file \
|
||||
libasound2-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
libgtk-3-dev \
|
||||
librsvg2-dev \
|
||||
libssl-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libxdo-dev \
|
||||
patchelf \
|
||||
wget
|
||||
# Pin to an immutable release tag to avoid supply-chain drift from the
|
||||
# mutable `continuous` tag. Tag: 1.9.1, asset: appimagetool-<arch>.AppImage
|
||||
# (https://github.com/AppImage/appimagetool/releases/tag/1.9.1)
|
||||
case "$(uname -m)" in
|
||||
x86_64) ARCH_SUFFIX="x86_64" ;;
|
||||
aarch64) ARCH_SUFFIX="aarch64" ;;
|
||||
*)
|
||||
echo "::error::Unsupported architecture: $(uname -m)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
wget -q --tries=3 --timeout=30 -O /tmp/appimagetool \
|
||||
"https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-${ARCH_SUFFIX}.AppImage"
|
||||
# SHA256 integrity check. Refuse to run an unverified binary: if a new
|
||||
# arch (e.g. aarch64) is enabled in CI, compute its hash and add it here.
|
||||
if [[ "$ARCH_SUFFIX" == "x86_64" ]]; then
|
||||
echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 /tmp/appimagetool" | sha256sum -c
|
||||
else
|
||||
echo "::error::No pinned SHA256 for appimagetool-${ARCH_SUFFIX} — add it before enabling this architecture"
|
||||
exit 1
|
||||
fi
|
||||
install -m 755 /tmp/appimagetool /usr/local/bin/appimagetool
|
||||
# appimagetool otherwise fetches the AppImage type2 runtime from the
|
||||
# MUTABLE `continuous` tag at repack time — the runtime is the first
|
||||
# code users execute, so pin it too. Tag: 20251108, hash is for the
|
||||
# x86_64 asset (non-x86_64 already hard-fails above).
|
||||
# (https://github.com/AppImage/type2-runtime/releases/tag/20251108)
|
||||
wget -q --tries=3 --timeout=30 -O /tmp/appimage-runtime \
|
||||
"https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-${ARCH_SUFFIX}"
|
||||
echo "2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d /tmp/appimage-runtime" | sha256sum -c
|
||||
install -D -m 644 /tmp/appimage-runtime /usr/local/lib/appimage-runtime
|
||||
echo "APPIMAGETOOL_RUNTIME_FILE=/usr/local/lib/appimage-runtime" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install desktop dependencies
|
||||
run: just desktop-install-ci
|
||||
@@ -531,6 +592,22 @@ jobs:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Fix AppImage (remove infra libs, symlink system GStreamer)
|
||||
run: |
|
||||
mapfile -t APPIMAGES < <(find desktop/src-tauri/target/release/bundle/appimage -name '*.AppImage' -type f)
|
||||
if [[ ${#APPIMAGES[@]} -eq 0 ]]; then
|
||||
echo "::error::No AppImage found to post-process"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ${#APPIMAGES[@]} -gt 1 ]]; then
|
||||
echo "::error::Expected exactly one AppImage, found ${#APPIMAGES[@]}: ${APPIMAGES[*]}"
|
||||
exit 1
|
||||
fi
|
||||
bash desktop/scripts/fix-appimage.sh "${APPIMAGES[0]}"
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Locate Linux build artifacts
|
||||
id: linux-artifacts
|
||||
run: |
|
||||
|
||||
+16
-6
@@ -174,8 +174,9 @@ Each release produces two GitHub releases:
|
||||
(macOS).
|
||||
|
||||
2. **`buzz-desktop-latest`** — a rolling pre-release for the Tauri
|
||||
auto-updater containing `latest.json`, the signed `.tar.gz` archive,
|
||||
and its `.sig` signature.
|
||||
auto-updater containing `latest.json` and each platform's signed
|
||||
updater artifact plus its `.sig` signature (`.tar.gz` on macOS,
|
||||
`.AppImage` on Linux, and `_alpha-unsigned.exe` on Windows).
|
||||
|
||||
---
|
||||
|
||||
@@ -187,6 +188,15 @@ Silicon (`darwin-aarch64`, the `release` job) and Intel
|
||||
`.AppImage`. Both macOS DMGs are codesigned, notarized, and attached to
|
||||
the same `v<version>` release. Intel users download the `_x64.dmg`.
|
||||
|
||||
The Linux AppImage is post-processed by `desktop/scripts/fix-appimage.sh`,
|
||||
which strips infra libraries over-bundled by linuxdeploy (they crash on
|
||||
Mesa 25+ / GLib 2.88 distros — see
|
||||
[tauri-apps/tauri#15665](https://github.com/tauri-apps/tauri/issues/15665))
|
||||
and re-signs the artifact. As a result the AppImage relies on the
|
||||
host's Wayland/GStreamer/graphics stack and requires GLib >= 2.72
|
||||
(Ubuntu 22.04 or newer). The `release-linux` job builds inside a
|
||||
`ubuntu:22.04` container for broad GLIBC compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
@@ -219,7 +229,7 @@ The version string must be valid semver: `MAJOR.MINOR.PATCH` with an optional pr
|
||||
|
||||
### Auto-updater reports "no update available"
|
||||
Verify that the `buzz-desktop-latest` release exists and contains a
|
||||
valid `latest.json`. The auto-updater manifest currently lists
|
||||
`darwin-aarch64` only, so Intel and Linux users do not yet receive
|
||||
auto-updates — they download new versions manually from the release
|
||||
page. (Adding their entries to `latest.json` is a follow-up.)
|
||||
valid `latest.json`. The manifest covers all four platform keys
|
||||
(`darwin-aarch64`, `darwin-x86_64`, `linux-x86_64`,
|
||||
`windows-x86_64`); a missing entry usually means that platform's
|
||||
release job failed — check the workflow run.
|
||||
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
# fix-appimage.sh — Remove infra libs from a Tauri-produced AppImage that crash
|
||||
# on Mesa 25+ / GLib 2.88 distros (Ubuntu 26.04, Fedora 42+, etc.).
|
||||
#
|
||||
# Usage: fix-appimage.sh <path-to.AppImage>
|
||||
#
|
||||
# Set TAURI_SIGNING_PRIVATE_KEY / TAURI_SIGNING_PRIVATE_KEY_PASSWORD to
|
||||
# re-sign after repacking (CI release builds). Without them the script
|
||||
# repacks but skips signing, which is fine for local testing.
|
||||
#
|
||||
# Set APPIMAGETOOL_RUNTIME_FILE to a pre-downloaded AppImage type2 runtime to
|
||||
# avoid appimagetool fetching one from its mutable `continuous` tag (CI pins
|
||||
# this; unset is fine for local testing).
|
||||
#
|
||||
# Root cause — three interlocking failures (upstream: https://github.com/tauri-apps/tauri/issues/15665):
|
||||
#
|
||||
# 1. EGL crash: linuxdeploy bundles libwayland-client.so.0 (1.22) alongside
|
||||
# the app. Mesa 25's libEGL calls the bundled version at runtime; the version
|
||||
# skew causes eglGetDisplay to return EGL_BAD_PARAMETER under Wayland, which
|
||||
# WebKitWebProcess treats as fatal and aborts before the window ever appears.
|
||||
#
|
||||
# 2. GStreamer crash: linuxdeploy also bundles libgst*.so* (GStreamer core libs).
|
||||
# AppRun unconditionally sets GST_PLUGIN_SYSTEM_PATH_1_0 to a dir inside the
|
||||
# AppImage that the bundler never populates (bundleMediaFramework is false by
|
||||
# default), so GStreamer's plugin discovery yields an empty registry. The
|
||||
# "GStreamer element appsink not found" error kills the render process; as a
|
||||
# side effect the broken run poisons ~/.cache/gstreamer-1.0/registry.x86_64.bin.
|
||||
#
|
||||
# 3. WebKit helper mismatch (latent): the bundled WebKit helpers
|
||||
# (WebKitNetworkProcess/WebKitWebProcess) have RUNPATH=$ORIGIN only, and
|
||||
# linuxdeploy string-patches /usr -> ././ inside libwebkit2gtk so the helper
|
||||
# dir is resolved relative to the process cwd. AppRun's chdir($APPDIR/usr)
|
||||
# makes this work; any launch that bypasses AppRun (extracted-AppDir usage,
|
||||
# repack workflows, dbus/systemd activation with cwd=/) resolves the helpers
|
||||
# wrong -- spawning nothing, dying on unresolved bundled libs, or spawning
|
||||
# the system helpers -- and the window never appears.
|
||||
#
|
||||
# Fix: remove the offending libs so the app uses the system copies (which are
|
||||
# newer and ABI-compatible on any distro shipping glib >= 2.72 / Ubuntu 22.04+),
|
||||
# and symlink the system GStreamer plugin directory so discovery works correctly.
|
||||
# No tauri.conf.json knob can do this — bundle.linux.appimage only exposes
|
||||
# bundleMediaFramework, files (copy-only, no remove/symlink), and bundleXdgOpen.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: fix-appimage.sh <path-to.AppImage>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$1" ]]; then
|
||||
echo "Error: file not found: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPIMAGE_ABS="$(realpath "$1")"
|
||||
APPIMAGE_DIR="$(dirname "$APPIMAGE_ABS")"
|
||||
APPIMAGE_NAME="$(basename "$APPIMAGE_ABS")"
|
||||
|
||||
# Locate the desktop/ directory (this script lives at desktop/scripts/).
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DESKTOP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Detect multiarch triplet for GStreamer plugin path.
|
||||
case "$(uname -m)" in
|
||||
x86_64) MULTIARCH="x86_64-linux-gnu" ;;
|
||||
aarch64) MULTIARCH="aarch64-linux-gnu" ;;
|
||||
*)
|
||||
echo "Error: unsupported architecture: $(uname -m)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
WORKDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
echo "==> Extracting $APPIMAGE_NAME"
|
||||
(cd "$WORKDIR" && APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGE_ABS" --appimage-extract)
|
||||
|
||||
LIBDIR="$WORKDIR/squashfs-root/usr/lib"
|
||||
|
||||
# Guard against a bundler layout change: if the primary offending lib is not
|
||||
# where we expect it, the rm globs below would silently no-op and we'd ship
|
||||
# an unfixed artifact. Fail loudly instead so a tauri/linuxdeploy upgrade
|
||||
# that changes the bundled lib set gets noticed here, not by users.
|
||||
if ! compgen -G "$LIBDIR/libwayland-client.so*" > /dev/null; then
|
||||
echo "Error: libwayland-client not found in $LIBDIR — bundler layout changed; update fix-appimage.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Removing infra libs that conflict with system Mesa / GLib / GStreamer"
|
||||
rm -f \
|
||||
"$LIBDIR"/libwayland-client.so* \
|
||||
"$LIBDIR"/libwayland-cursor.so* \
|
||||
"$LIBDIR"/libwayland-egl.so* \
|
||||
"$LIBDIR"/libwayland-server.so* \
|
||||
"$LIBDIR"/libglib-2.0.so* \
|
||||
"$LIBDIR"/libgio-2.0.so* \
|
||||
"$LIBDIR"/libgobject-2.0.so* \
|
||||
"$LIBDIR"/libgmodule-2.0.so* \
|
||||
"$LIBDIR"/libmount.so* \
|
||||
"$LIBDIR"/libblkid.so* \
|
||||
"$LIBDIR"/libselinux.so* \
|
||||
"$LIBDIR"/libpcre2-8.so* \
|
||||
"$LIBDIR"/libgst*.so* \
|
||||
"$LIBDIR"/libzstd.so* \
|
||||
"$LIBDIR"/libelf.so* \
|
||||
"$LIBDIR"/libffi.so*
|
||||
|
||||
echo "==> Symlinking system GStreamer plugin directory"
|
||||
# On distros without the Debian multiarch layout (e.g. Arch), this symlink
|
||||
# dangles — GStreamer then falls back to its default plugin discovery, which
|
||||
# is a safe degradation (unlike the original empty in-bundle dir).
|
||||
rm -rf "$LIBDIR/gstreamer-1.0"
|
||||
ln -s "/usr/lib/$MULTIARCH/gstreamer-1.0" "$LIBDIR/gstreamer-1.0"
|
||||
|
||||
echo "==> Repacking AppImage"
|
||||
# Pass a pinned type2 runtime when provided (CI sets APPIMAGETOOL_RUNTIME_FILE);
|
||||
# without it appimagetool downloads the runtime from its mutable `continuous`
|
||||
# tag at repack time — acceptable for local testing, not for release builds.
|
||||
RUNTIME_ARGS=()
|
||||
if [[ -n "${APPIMAGETOOL_RUNTIME_FILE:-}" ]]; then
|
||||
RUNTIME_ARGS=(--runtime-file "$APPIMAGETOOL_RUNTIME_FILE")
|
||||
fi
|
||||
APPIMAGE_EXTRACT_AND_RUN=1 ARCH="$(uname -m)" appimagetool \
|
||||
"${RUNTIME_ARGS[@]}" \
|
||||
"$WORKDIR/squashfs-root" "$APPIMAGE_ABS"
|
||||
|
||||
# Re-sign after repack so the updater can verify the artifact.
|
||||
# Tauri 2.11 with createUpdaterArtifacts=true produces two possible formats:
|
||||
# New: <name>.AppImage + <name>.AppImage.sig (sign the AppImage directly)
|
||||
# Old: <name>.AppImage.tar.gz + .tar.gz.sig (tar-wrapped, then signed)
|
||||
# We handle both: always re-sign the AppImage; if a .tar.gz sibling exists
|
||||
# alongside it, recreate it from the freshly repacked AppImage and re-sign that.
|
||||
# Our release config pins createUpdaterArtifacts: true (build-release-config.mjs),
|
||||
# so the tar.gz branch is dead in CI today — kept deliberately because the
|
||||
# workflow's artifact-locate step prefers a tar.gz when one exists; dropping
|
||||
# this branch could publish a stale tarball containing the unfixed AppImage.
|
||||
if [[ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]]; then
|
||||
# `tauri signer sign` reads TAURI_SIGNING_PRIVATE_KEY and
|
||||
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD from the environment (same as the
|
||||
# macOS jobs in release.yml) — never pass the password via argv, where
|
||||
# it would be visible in /proc/<pid>/cmdline.
|
||||
echo "==> Re-signing AppImage"
|
||||
(cd "$DESKTOP_DIR" && pnpm tauri signer sign "$APPIMAGE_ABS")
|
||||
|
||||
TARBALL="$APPIMAGE_ABS.tar.gz"
|
||||
if [[ -f "$TARBALL" ]]; then
|
||||
echo "==> Recreating updater archive $TARBALL"
|
||||
tar -czf "$TARBALL" -C "$APPIMAGE_DIR" "$APPIMAGE_NAME"
|
||||
echo "==> Re-signing updater archive"
|
||||
(cd "$DESKTOP_DIR" && pnpm tauri signer sign "$TARBALL")
|
||||
fi
|
||||
else
|
||||
echo "==> TAURI_SIGNING_PRIVATE_KEY not set — skipping signing (local build)"
|
||||
fi
|
||||
|
||||
echo "==> Done: $APPIMAGE_ABS"
|
||||
Reference in New Issue
Block a user