fix(desktop): make Linux AppImage GStreamer work on non-Debian distros (#2176)

This commit is contained in:
Daniel Cadenas
2026-07-25 23:49:30 -03:00
committed by GitHub
parent 5d1233e841
commit cc6c4d3471
4 changed files with 92 additions and 29 deletions

View File

@@ -629,7 +629,7 @@ 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)
- name: Fix AppImage (remove infra libs, shim host GStreamer)
run: |
mapfile -t APPIMAGES < <(find desktop/src-tauri/target/release/bundle/appimage -name '*.AppImage' -type f)
if [[ ${#APPIMAGES[@]} -eq 0 ]]; then

View File

@@ -19,12 +19,19 @@
# 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.
# 2. GStreamer crash: linuxdeploy's compiled AppRun.wrapped force-sets
# GST_PLUGIN_SYSTEM_PATH_1_0 to $APPDIR/usr/lib/gstreamer-1.0 -- a dir the
# bundler never populates (bundleMediaFramework is off, and we strip the
# bundled libgst* core below to use the host's). Crucially, once that variable
# is set it *replaces* GStreamer's compiled-in default search path rather than
# adding to it, so the app finds ZERO plugins on every distro:
# "GStreamer element appsink not found" kills the WebKitWebProcess and the
# window never paints. An earlier revision of this script hid the failure on
# Debian only by symlinking usr/lib/gstreamer-1.0 to the Debian multiarch dir
# (/usr/lib/x86_64-linux-gnu/gstreamer-1.0); that symlink dangles on Arch and
# Fedora, and the "safe fallback to default discovery" it assumed does not
# exist -- a set GST_PLUGIN_SYSTEM_PATH_1_0 disables the default. A broken run
# also 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
@@ -35,11 +42,17 @@
# 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.
# Fix: (a) remove the offending libs so the app uses the system copies (newer and
# ABI-compatible on any distro shipping glib >= 2.72 / Ubuntu 22.04+), and
# (b) install a launcher shim in front of the app binary that strips the
# bundle-pointing GST_PLUGIN_* overrides AppRun.wrapped injects, letting the host
# GStreamer resolve plugins via its own default path (correct on Debian, Arch, and
# Fedora alike). The shim has to run *after* AppRun.wrapped: the wrapper rewrites
# the variable last -- after every apprun-hook -- so any value set before it is
# discarded (verified empirically; a runtime GST_PLUGIN_SYSTEM_PATH_1_0 passed
# into the AppImage does not survive). 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
@@ -61,16 +74,6 @@ APPIMAGE_NAME="$(basename "$APPIMAGE_ABS")"
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
@@ -107,12 +110,58 @@ rm -f \
"$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 "==> Installing GStreamer launcher shim on the app binary"
# AppRun.wrapped force-sets GST_PLUGIN_SYSTEM_PATH_1_0 (and the 0.10-era
# GST_PLUGIN_SYSTEM_PATH) to $APPDIR/usr/lib/gstreamer-1.0 — a dir we bundle no
# plugins into. Because a set path *replaces* GStreamer's default instead of
# extending it, the app finds zero plugins on any distro and WebKit aborts. The
# wrapper rewrites the variable after every apprun-hook, so the only place to undo
# it is a shim between AppRun.wrapped and the real binary. First confirm the
# wrapper still injects the override; if a tauri/linuxdeploy bump drops it, the
# shim becomes a harmless no-op, but we want a human to re-verify rather than
# silently ship — so fail loudly (mirrors the libwayland guard above).
APPRUN_WRAPPED="$WORKDIR/squashfs-root/AppRun.wrapped"
if ! grep -aq "GST_PLUGIN_SYSTEM_PATH_1_0" "$APPRUN_WRAPPED"; then
echo "Error: AppRun.wrapped is missing or no longer references GST_PLUGIN_SYSTEM_PATH_1_0 — GStreamer path injection changed; re-verify fix-appimage.sh" >&2
exit 1
fi
APP_BIN="$WORKDIR/squashfs-root/usr/bin/buzz-desktop"
if [[ ! -f "$APP_BIN" ]]; then
echo "Error: app binary usr/bin/buzz-desktop not found — bundler layout changed; update fix-appimage.sh" >&2
exit 1
fi
if [[ -e "$APP_BIN.bin" ]]; then
echo "Error: usr/bin/buzz-desktop.bin already exists — shim already installed?" >&2
exit 1
fi
# The real binary moves aside; buzz-desktop becomes a shim AppRun.wrapped execs.
mv "$APP_BIN" "$APP_BIN.bin"
cat > "$APP_BIN" <<'SHIM'
#!/usr/bin/env bash
# GStreamer shim installed by desktop/scripts/fix-appimage.sh.
#
# linuxdeploy's AppRun.wrapped force-sets GST_PLUGIN_SYSTEM_PATH_1_0 to an empty
# in-bundle dir ($APPDIR/usr/lib/gstreamer-1.0). A set path *replaces* the host's
# default GStreamer search path, so the app finds zero plugins and WebKit aborts
# (blank window). Drop the bundle-pointing GST_PLUGIN_* overrides so the system
# GStreamer — which we use, having removed the bundled core libs — resolves
# plugins via its own default path on any distro. Values that don't point into
# this AppImage are the user's own and are preserved.
here="$(dirname "$(readlink -f "$0")")"
appdir="$(readlink -f "$here/../..")"
for var in GST_PLUGIN_SYSTEM_PATH_1_0 GST_PLUGIN_SYSTEM_PATH \
GST_PLUGIN_PATH_1_0 GST_PLUGIN_PATH \
GST_PLUGIN_SCANNER GST_PLUGIN_SCANNER_1_0; do
val="${!var-}"
if [[ -n "$val" && "$val" == *"$appdir/"* ]]; then
unset "$var"
fi
done
exec -a "buzz-desktop" "$here/buzz-desktop.bin" "$@"
SHIM
chmod +x "$APP_BIN"
echo "==> Repacking AppImage"
# Pass a pinned type2 runtime when provided (CI sets APPIMAGETOOL_RUNTIME_FILE);

View File

@@ -855,7 +855,14 @@ pub(crate) fn collect_same_instance_orphans(
/// Binary names for the Buzz desktop/Tauri process. Used by dead-instance
/// detection to confirm the owning desktop is still alive.
const DESKTOP_BINARY_NAMES: &[&str] = &["Buzz", "buzz-desktop", "buzz_desktop"];
const DESKTOP_BINARY_NAMES: &[&str] = &[
"Buzz",
"buzz-desktop",
"buzz_desktop",
// Linux limits /proc/<pid>/comm to 15 visible bytes, truncating the
// AppImage shim's real executable name, `buzz-desktop.bin`.
"buzz-desktop.bi",
];
/// Check if a process name matches a known Buzz desktop binary.
fn is_desktop_binary(name: &str) -> bool {

View File

@@ -1,5 +1,12 @@
use crate::managed_agents::known_acp_runtime;
// ── desktop binary name tests ───────────────────────────────────────────
#[test]
fn appimage_binary_matches_truncated_linux_comm_name() {
assert!(super::is_desktop_binary("buzz-desktop.bi"));
}
// ── buffer_contains_identifier tests ────────────────────────────────────
#[test]