From 893f4500acdb7c2233c19c23bc7f296675d6835b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 18:36:27 +0000 Subject: [PATCH 1/8] Fail AppImage builds fast with clear errors instead of cryptic deep-compile failures Since Moonshine became a mandatory feature in every AppImage build, the compile now downloads a prebuilt ONNX Runtime from cdn.pyke.io at build time. When that download is unreachable (offline, firewall, proxy/egress policy, or a yanked CDN artifact) the entire build dies ~15 minutes into compilation with an opaque ort-sys error. A machine missing the GTK/WebKit dev libraries fails the same way with a cryptic "gdk-3.0 not found". Make the script fail fast and actionably instead: - Add a GTK/WebKit development-library preflight (pkg-config gtk+-3.0 and webkit2gtk-4.1) alongside the existing unsquashfs/npm/cargo/cmake checks, with per-distro install hints. - Probe cdn.pyke.io reachability before the long compile when Moonshine is enabled, and warn with the offline escape hatch if it is blocked. - Add a --no-moonshine flag (and MOONSHINE=0 env) to build the whisper-cpp -only AppImage without the build-time ONNX Runtime download, restoring the pre-Moonshine offline-capable build. Moonshine stays on by default. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 90 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index c46d4a2..7b5bb8e 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -8,12 +8,23 @@ set -euo pipefail # Parse command line options FORCE_CPU_FLAG=false +NO_MOONSHINE=false for arg in "$@"; do case "$arg" in --cpu) FORCE_CPU_FLAG=true ;; + # Build the whisper-cpp-only AppImage (the pre-Moonshine build). This + # drops the `moonshine` cargo feature so the compile no longer downloads + # a prebuilt ONNX Runtime from cdn.pyke.io — useful for offline builds or + # when that download host is unreachable/blocked. + --no-moonshine) NO_MOONSHINE=true ;; esac done +# Env override: MOONSHINE=0 is equivalent to --no-moonshine. +if [ "${MOONSHINE:-1}" = "0" ]; then + NO_MOONSHINE=true +fi + # ── Colors ─────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' @@ -127,6 +138,39 @@ if ! command -v cmake &>/dev/null; then exit 1 fi +# Verify the GTK/WebKit development libraries Tauri links against are present. +# Without these, the Rust compile fails deep in the build with a cryptic +# pkg-config error (e.g. "Package gdk-3.0 was not found") rather than a clear +# up-front message. We check via pkg-config: gtk+-3.0 covers the overlay's gdk +# dependency, webkit2gtk-4.1 covers the Tauri webview. +if ! command -v pkg-config &>/dev/null; then + fail "'pkg-config' is not installed on your system!" + info "Building the Tauri application requires pkg-config to locate GTK/WebKit." + info "👉 Please install it via your package manager:" + info " - Arch: sudo pacman -S pkgconf" + info " - Ubuntu: sudo apt install pkg-config" + info " - Fedora: sudo dnf install pkgconf-pkg-config" + echo "" + exit 1 +fi + +MISSING_DEV_LIBS=() +for pc in gtk+-3.0 webkit2gtk-4.1; do + if ! pkg-config --exists "$pc" 2>/dev/null; then + MISSING_DEV_LIBS+=("$pc") + fi +done +if [ ${#MISSING_DEV_LIBS[@]} -gt 0 ]; then + fail "Missing GTK/WebKit development libraries: ${MISSING_DEV_LIBS[*]}" + info "Tauri links against these at build time; the Rust compile will fail without them." + info "👉 Please install the development packages for your distribution:" + info " - Arch: sudo pacman -S gtk3 webkit2gtk-4.1" + info " - Ubuntu: sudo apt install libgtk-3-dev libwebkit2gtk-4.1-dev" + info " - Fedora: sudo dnf install gtk3-devel webkit2gtk4.1-devel" + echo "" + exit 1 +fi + ok "AppImage toolchain wrapper is verified and ready." # ══════════════════════════════════════════════════════════════════════════════ @@ -206,16 +250,46 @@ if [ "$CUDA_FOUND" = false ] && [ "$HAS_NVIDIA_GPU" = true ]; then fi fi -info "Running Tauri release compiler with headless PATH and CUDA injection..." -# The Moonshine ONNX backend is always compiled in so both speech engines -# (whisper-cpp and Moonshine) are selectable in every AppImage. It links ONNX -# Runtime, fetched at build time, so this step needs network access. +# Assemble the cargo feature list for the release build. +# - cuda : GPU acceleration (only when a CUDA toolkit was found). +# - moonshine : the Moonshine ONNX speech engine (default on; opt out with +# --no-moonshine / MOONSHINE=0). This feature makes the compile +# download a prebuilt ONNX Runtime from cdn.pyke.io, so it is the +# one part of the build that requires network access. +BUILD_FEATURES="" if [ "$CUDA_FOUND" = true ]; then - info "CUDA detected. Compiling with GPU support (whisper-cpp + Moonshine)..." - npx tauri build -- --features cuda,moonshine + BUILD_FEATURES="cuda" +fi +if [ "$NO_MOONSHINE" = false ]; then + BUILD_FEATURES="${BUILD_FEATURES:+$BUILD_FEATURES,}moonshine" +fi + +# The Moonshine backend fetches a prebuilt ONNX Runtime from cdn.pyke.io at +# compile time. If that host is unreachable the build only fails after a long +# compile, so probe it up front and point users at the offline escape hatch. +if [ "$NO_MOONSHINE" = false ] && command -v curl &>/dev/null; then + info "Checking reachability of the ONNX Runtime download host (cdn.pyke.io)..." + # Without -f, curl succeeds as long as it *connected* (even on an HTTP 4xx), + # and only fails on connection-level problems: DNS, connect timeout, or a + # proxy/egress policy rejecting the CONNECT (403/407). That is exactly the + # condition that dooms the ONNX Runtime download, so key off the exit status. + if ! curl -s -o /dev/null --connect-timeout 10 --max-time 20 https://cdn.pyke.io/ 2>/dev/null; then + warn "Could not reach cdn.pyke.io." + warn "The Moonshine build downloads a prebuilt ONNX Runtime from there at compile" + warn "time; if it stays unreachable the build will fail after a long compile." + info "To build the whisper-cpp-only AppImage without this download, re-run with:" + info " ./build_appimage.sh --no-moonshine (or MOONSHINE=0 ./build_appimage.sh )" + echo "" + fi +fi + +info "Running Tauri release compiler with headless PATH and CUDA injection..." +if [ -n "$BUILD_FEATURES" ]; then + info "Compiling with features: ${BUILD_FEATURES}" + npx tauri build -- --features "$BUILD_FEATURES" else - info "CUDA not detected. Compiling for CPU only (whisper-cpp + Moonshine)..." - npx tauri build -- --features moonshine + info "Compiling with default features (whisper-cpp only)..." + npx tauri build fi ok "Compilation finished successfully." From 3a2963e6b8ff5624605c0a80a2d771bb46a18c08 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 03:50:26 +0000 Subject: [PATCH 2/8] Check for patchelf/file so AppImage bundling fails fast, not at linuxdeploy The final packaging step runs Tauri's AppImage bundler, which downloads and executes `linuxdeploy`. linuxdeploy shells out to `patchelf` (to rewrite library rpaths) and `file` (to classify binaries). When either is missing the Rust compile succeeds but bundling dies late with a generic "failed to run linuxdeploy" and no obvious cause. Neither tool was installed or checked anywhere, even though cmake/npm/etc. were. - build_appimage.sh: add a patchelf/file preflight to the toolchain checks with per-distro install hints. - install.sh: add patchelf and file to every build-dependency install line, and treat their absence as a missing build tool so the installer pulls them. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 23 +++++++++++++++++++++++ install.sh | 19 +++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index 7b5bb8e..0d587e6 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -171,6 +171,29 @@ if [ ${#MISSING_DEV_LIBS[@]} -gt 0 ]; then exit 1 fi +# Verify the tools Tauri's AppImage bundler shells out to. Tauri downloads and +# runs `linuxdeploy` for the final packaging step; linuxdeploy in turn requires +# `patchelf` (to rewrite library rpaths) and `file` (to classify binaries). When +# either is missing the Rust compile succeeds but bundling dies late with a +# generic "failed to run linuxdeploy" and no obvious cause. Check them up front. +MISSING_BUNDLE_TOOLS=() +for tool in patchelf file; do + if ! command -v "$tool" &>/dev/null; then + MISSING_BUNDLE_TOOLS+=("$tool") + fi +done +if [ ${#MISSING_BUNDLE_TOOLS[@]} -gt 0 ]; then + fail "Missing AppImage bundling tools: ${MISSING_BUNDLE_TOOLS[*]}" + info "Tauri's AppImage packager runs 'linuxdeploy', which needs these to succeed." + info "Without them the build fails late with 'failed to run linuxdeploy'." + info "👉 Please install them via your package manager:" + info " - Arch: sudo pacman -S patchelf file" + info " - Ubuntu: sudo apt install patchelf file" + info " - Fedora: sudo dnf install patchelf file" + echo "" + exit 1 +fi + ok "AppImage toolchain wrapper is verified and ready." # ══════════════════════════════════════════════════════════════════════════════ diff --git a/install.sh b/install.sh index 9a05e3b..6b5182b 100755 --- a/install.sh +++ b/install.sh @@ -151,6 +151,9 @@ else if ! command -v cargo &>/dev/null || ! cargo --version &>/dev/null; then MISSING_BUILD_TOOLS=1; fi if ! command -v npm &>/dev/null; then MISSING_BUILD_TOOLS=1; fi if ! command -v cmake &>/dev/null; then MISSING_BUILD_TOOLS=1; fi + # patchelf/file are needed by linuxdeploy for the AppImage packaging step. + if ! command -v patchelf &>/dev/null; then MISSING_BUILD_TOOLS=1; fi + if ! command -v file &>/dev/null; then MISSING_BUILD_TOOLS=1; fi # Check if an NVIDIA GPU is present HAS_NVIDIA_GPU=false @@ -173,35 +176,35 @@ else pacman) if [ "$INSTALL_CUDA" = true ]; then info "NVIDIA GPU detected. Adding 'cuda' toolkit to installation..." - sudo pacman -S --noconfirm --needed base-devel rustup nodejs npm pkgconf cuda cmake + sudo pacman -S --noconfirm --needed base-devel rustup nodejs npm pkgconf cuda cmake patchelf file else - sudo pacman -S --noconfirm --needed base-devel rustup nodejs npm pkgconf cmake + sudo pacman -S --noconfirm --needed base-devel rustup nodejs npm pkgconf cmake patchelf file fi ;; apt) if [ "$INSTALL_CUDA" = true ]; then info "NVIDIA GPU detected. Adding 'nvidia-cuda-toolkit' to installation..." - sudo apt-get install -y build-essential curl nodejs npm pkg-config nvidia-cuda-toolkit cmake + sudo apt-get install -y build-essential curl nodejs npm pkg-config nvidia-cuda-toolkit cmake patchelf file else - sudo apt-get install -y build-essential curl nodejs npm pkg-config cmake + sudo apt-get install -y build-essential curl nodejs npm pkg-config cmake patchelf file fi ;; dnf) sudo dnf groupinstall -y "Development Tools" if [ "$INSTALL_CUDA" = true ]; then info "NVIDIA GPU detected. Adding 'cuda-toolkit' to installation..." - sudo dnf install -y curl nodejs npm pkgconf-pkg-config cuda-toolkit cmake + sudo dnf install -y curl nodejs npm pkgconf-pkg-config cuda-toolkit cmake patchelf file else - sudo dnf install -y curl nodejs npm pkgconf-pkg-config cmake + sudo dnf install -y curl nodejs npm pkgconf-pkg-config cmake patchelf file fi ;; zypper) sudo zypper install -t pattern -y devel_basis if [ "$INSTALL_CUDA" = true ]; then info "NVIDIA GPU detected. Adding 'cuda' toolkit to installation..." - sudo zypper install -y curl nodejs npm pkg-config cuda cmake + sudo zypper install -y curl nodejs npm pkg-config cuda cmake patchelf file else - sudo zypper install -y curl nodejs npm pkg-config cmake + sudo zypper install -y curl nodejs npm pkg-config cmake patchelf file fi ;; esac From db7ea6f1a212eeeb46c81791e649958761a2bf47 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 04:13:31 +0000 Subject: [PATCH 3/8] Add --verbose to build_appimage.sh to surface linuxdeploy's real error Tauri collapses any AppImage packaging failure into a bare "failed to run linuxdeploy", hiding linuxdeploy's own diagnostics. Add a --verbose/-v flag that passes --verbose to `tauri build` and sets VERBOSE=2 so linuxdeploy logs at debug level, making the underlying cause (FUSE, a GTK plugin, an unresolved library, etc.) visible. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index 0d587e6..3a26546 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -9,6 +9,7 @@ set -euo pipefail # Parse command line options FORCE_CPU_FLAG=false NO_MOONSHINE=false +VERBOSE_FLAG=false for arg in "$@"; do case "$arg" in --cpu) FORCE_CPU_FLAG=true ;; @@ -17,6 +18,10 @@ for arg in "$@"; do # a prebuilt ONNX Runtime from cdn.pyke.io — useful for offline builds or # when that download host is unreachable/blocked. --no-moonshine) NO_MOONSHINE=true ;; + # Pass --verbose to `tauri build` and crank up linuxdeploy's own + # logging. Tauri otherwise collapses a bundling failure into a bare + # "failed to run linuxdeploy"; this surfaces linuxdeploy's real error. + --verbose|-v) VERBOSE_FLAG=true ;; esac done @@ -306,13 +311,23 @@ if [ "$NO_MOONSHINE" = false ] && command -v curl &>/dev/null; then fi fi +# In verbose mode, ask Tauri and linuxdeploy to print what they are doing so a +# bundling failure shows linuxdeploy's real error instead of just Tauri's +# generic "failed to run linuxdeploy" wrapper. +TAURI_VERBOSE=() +if [ "$VERBOSE_FLAG" = true ]; then + TAURI_VERBOSE=(--verbose) + export VERBOSE=2 # linuxdeploy log verbosity (0=error … 2=debug) + info "Verbose mode enabled: streaming Tauri + linuxdeploy output." +fi + info "Running Tauri release compiler with headless PATH and CUDA injection..." if [ -n "$BUILD_FEATURES" ]; then info "Compiling with features: ${BUILD_FEATURES}" - npx tauri build -- --features "$BUILD_FEATURES" + npx tauri build "${TAURI_VERBOSE[@]}" -- --features "$BUILD_FEATURES" else info "Compiling with default features (whisper-cpp only)..." - npx tauri build + npx tauri build "${TAURI_VERBOSE[@]}" fi ok "Compilation finished successfully." From 48e244fd65e285840d256ee689e5f66bfd3518ed Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 13:13:25 +0000 Subject: [PATCH 4/8] Detect third-party GLib on the linker path that breaks linuxdeploy The AppImage target fails at the linuxdeploy step ("failed to run linuxdeploy") when a third-party app registers its own copy of core GLib libraries on the system linker path via /etc/ld.so.conf.d. linuxdeploy then resolves the app's libgio/libglib to those shadow copies and fails to bundle them because they pull in dependencies the host lacks. The common culprit is Insync: /usr/lib/insync/libgio-2.0.so.0 needs libselinux.so.1, absent on Arch/CachyOS, so the GTK plugin aborts. deb/rpm don't deploy libraries, which is why only the AppImage build breaks. Scan /etc/ld.so.conf.d entries during the toolchain preflight and, when a non-standard directory ships libgio-2.0/libglib-2.0, warn up front with the exact remediation (temporarily disable the .conf, run ldconfig, build, restore) instead of letting the build fail cryptically 20 minutes in. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/build_appimage.sh b/build_appimage.sh index 3a26546..591d714 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -199,6 +199,39 @@ if [ ${#MISSING_BUNDLE_TOOLS[@]} -gt 0 ]; then exit 1 fi +# Warn about third-party library directories (registered via /etc/ld.so.conf.d) +# that ship their own copies of core GLib/GTK libraries. linuxdeploy resolves the +# app's libgio/libglib to those shadow copies and then fails to bundle them +# because they drag in exotic dependencies the host lacks — the classic case is +# Insync, whose /usr/lib/insync/libgio-2.0.so.0 needs libselinux.so.1. That +# surfaces 20 minutes into the build as a cryptic "failed to run linuxdeploy", so +# flag it up front with the remediation. +if [ -d /etc/ld.so.conf.d ]; then + shopt -s nullglob + for _conf in /etc/ld.so.conf.d/*.conf; do + while IFS= read -r _dir; do + case "$_dir" in + ""|\#*|/usr/lib|/usr/lib64|/lib|/lib64|/usr/local/lib|/usr/local/lib64|/usr/lib/*-linux-gnu) + continue ;; + esac + [ -d "$_dir" ] || continue + if compgen -G "$_dir/libgio-2.0.so*" >/dev/null 2>&1 \ + || compgen -G "$_dir/libglib-2.0.so*" >/dev/null 2>&1; then + warn "'$_dir' (added by $(basename "$_conf")) ships its own GLib libraries." + warn "linuxdeploy will try to bundle those instead of the system copies and" + warn "fail — e.g. Insync's libgio-2.0.so.0 needs libselinux.so.1 — showing up" + warn "as a late 'failed to run linuxdeploy'." + info "Take that directory off the linker path for the build, then restore it:" + info " sudo mv \"$_conf\" \"$_conf.disabled\" && sudo ldconfig" + info " ./build_appimage.sh" + info " sudo mv \"$_conf.disabled\" \"$_conf\" && sudo ldconfig # restore after" + echo "" + fi + done < "$_conf" + done + shopt -u nullglob +fi + ok "AppImage toolchain wrapper is verified and ready." # ══════════════════════════════════════════════════════════════════════════════ From f8b7ceb0d36c904dcbbfeacc9f6bde99737a2ca2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 20:18:54 +0000 Subject: [PATCH 5/8] Detect shadow GLib/GTK libs under /usr/lib that break linuxdeploy's gtk plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous check looked at /etc/ld.so.conf.d, but that is the wrong mechanism: linuxdeploy's GTK plugin deploys the GLib/GTK stack by globbing library names across /usr/lib and its subdirectories — not via the loader, ld.so cache, or environment. It therefore sweeps in any third-party copy it finds (e.g. Insync's /usr/lib/insync/libgio-2.0.so.0) and fails to bundle it when it needs a library the host lacks (libselinux.so.1 on Arch/CachyOS), surfacing as a late, cryptic "failed to run linuxdeploy". Confirmed by linuxdeploy deploying a stray "libgdk_pixbuf-2.0.so.0.bak" file — only a filesystem glob would match that. Replace the ld.so.conf.d scan with one that mirrors the plugin: look for libgio/libgobject/libglib/libgdk_pixbuf/libgtk-3 copies in non-standard subdirectories of the library prefixes (skipping the multiarch dir), and, when found, warn up front with the correct fix — move the directory OUT of /usr/lib for the build (renaming it in place or renaming the .so files does not work, since the glob still matches anything under /usr/lib). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 77 +++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index 591d714..91ece6c 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -199,37 +199,54 @@ if [ ${#MISSING_BUNDLE_TOOLS[@]} -gt 0 ]; then exit 1 fi -# Warn about third-party library directories (registered via /etc/ld.so.conf.d) -# that ship their own copies of core GLib/GTK libraries. linuxdeploy resolves the -# app's libgio/libglib to those shadow copies and then fails to bundle them -# because they drag in exotic dependencies the host lacks — the classic case is -# Insync, whose /usr/lib/insync/libgio-2.0.so.0 needs libselinux.so.1. That -# surfaces 20 minutes into the build as a cryptic "failed to run linuxdeploy", so -# flag it up front with the remediation. -if [ -d /etc/ld.so.conf.d ]; then - shopt -s nullglob - for _conf in /etc/ld.so.conf.d/*.conf; do - while IFS= read -r _dir; do - case "$_dir" in - ""|\#*|/usr/lib|/usr/lib64|/lib|/lib64|/usr/local/lib|/usr/local/lib64|/usr/lib/*-linux-gnu) - continue ;; - esac - [ -d "$_dir" ] || continue - if compgen -G "$_dir/libgio-2.0.so*" >/dev/null 2>&1 \ - || compgen -G "$_dir/libglib-2.0.so*" >/dev/null 2>&1; then - warn "'$_dir' (added by $(basename "$_conf")) ships its own GLib libraries." - warn "linuxdeploy will try to bundle those instead of the system copies and" - warn "fail — e.g. Insync's libgio-2.0.so.0 needs libselinux.so.1 — showing up" - warn "as a late 'failed to run linuxdeploy'." - info "Take that directory off the linker path for the build, then restore it:" - info " sudo mv \"$_conf\" \"$_conf.disabled\" && sudo ldconfig" - info " ./build_appimage.sh" - info " sudo mv \"$_conf.disabled\" \"$_conf\" && sudo ldconfig # restore after" - echo "" - fi - done < "$_conf" +# Warn about third-party directories under /usr/lib (or /lib) that ship their own +# copies of the core GLib/GTK libraries. linuxdeploy's GTK plugin deploys that +# stack by globbing library names across these prefixes and *their subdirectories* +# — not via the loader, the ld.so cache, or the environment — so it sweeps in any +# stray copy it finds and then fails to bundle it when it needs a library the host +# lacks. The classic case is Insync: /usr/lib/insync/libgio-2.0.so.0 needs +# libselinux.so.1, absent on Arch/CachyOS. That surfaces late as a cryptic +# "failed to run linuxdeploy", so flag it up front with the correct remediation. +# +# Note: the shadow directory must be moved *out* of the scanned prefix entirely — +# renaming it in place (e.g. to insync.disabled) or renaming the files (…so.0.bak) +# does NOT help, because the glob still matches anything under /usr/lib. +SHADOW_LIB_DIRS=() +shopt -s nullglob +for _prefix in /usr/lib /usr/lib64 /lib /lib64; do + [ -d "$_prefix" ] || continue + for _sub in "$_prefix"/*/; do + _sub="${_sub%/}" + # Skip the standard multiarch dir (e.g. /usr/lib/x86_64-linux-gnu). + case "$_sub" in */*-linux-gnu) continue ;; esac + if compgen -G "$_sub/libgio-2.0.so*" >/dev/null 2>&1 \ + || compgen -G "$_sub/libgobject-2.0.so*" >/dev/null 2>&1 \ + || compgen -G "$_sub/libglib-2.0.so*" >/dev/null 2>&1 \ + || compgen -G "$_sub/libgdk_pixbuf-2.0.so*" >/dev/null 2>&1 \ + || compgen -G "$_sub/libgtk-3.so*" >/dev/null 2>&1; then + SHADOW_LIB_DIRS+=("$_sub") + fi + done +done +shopt -u nullglob +if [ ${#SHADOW_LIB_DIRS[@]} -gt 0 ]; then + warn "Found third-party copies of the GLib/GTK libraries under /usr/lib:" + for _d in "${SHADOW_LIB_DIRS[@]}"; do warn " $_d"; done + warn "linuxdeploy's GTK plugin globs those library names across /usr/lib and will" + warn "try to bundle these instead of the system copies, then fail on their extra" + warn "dependencies (e.g. Insync's libgio-2.0.so.0 needs libselinux.so.1) — the" + warn "late, cryptic 'failed to run linuxdeploy'." + info "Move each directory OUT of /usr/lib for the build, then restore it after." + info "(Renaming it in place or renaming the .so files does not work — it is still" + info " matched by the glob.) For example:" + for _d in "${SHADOW_LIB_DIRS[@]}"; do + info " sudo mv \"$_d\" \"/opt/$(basename "$_d").buildhide\"" + done + info " ./build_appimage.sh" + for _d in "${SHADOW_LIB_DIRS[@]}"; do + info " sudo mv \"/opt/$(basename "$_d").buildhide\" \"$_d\" # restore after" done - shopt -u nullglob + echo "" fi ok "AppImage toolchain wrapper is verified and ready." From 7cbd3564564189c700ff89786c8c69e09bde3251 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 21:04:22 +0000 Subject: [PATCH 6/8] Add --hide-shadow-libs to auto-move third-party GLib/GTK dirs during the build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shadow-library problem (Insync's /usr/lib/insync breaking linuxdeploy's gtk plugin) previously required moving the directory out of /usr/lib by hand before every build and restoring it after — fiddly and easy to get wrong. Automate it. With --hide-shadow-libs (needs sudo), each detected shadow directory is moved to a sibling path outside the scanned prefix (e.g. /usr/lib/insync -> /usr/insync.buildhide) just before `tauri build`, and an EXIT/INT/TERM trap restores every moved directory when the build finishes or fails, so the third-party app is never left displaced. Without the flag the behavior is unchanged: the script only warns and now also points at the flag. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 77 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index 91ece6c..2ec2d50 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -10,6 +10,7 @@ set -euo pipefail FORCE_CPU_FLAG=false NO_MOONSHINE=false VERBOSE_FLAG=false +HIDE_SHADOW_LIBS=false for arg in "$@"; do case "$arg" in --cpu) FORCE_CPU_FLAG=true ;; @@ -22,6 +23,11 @@ for arg in "$@"; do # logging. Tauri otherwise collapses a bundling failure into a bare # "failed to run linuxdeploy"; this surfaces linuxdeploy's real error. --verbose|-v) VERBOSE_FLAG=true ;; + # Automatically move any third-party GLib/GTK shadow libraries (e.g. + # Insync's /usr/lib/insync) out of linuxdeploy's search path for the + # duration of the build and restore them afterwards. Needs sudo. Without + # this the script only *warns* about them (see the detection in step 1). + --hide-shadow-libs|--fix-shadow-libs) HIDE_SHADOW_LIBS=true ;; esac done @@ -40,6 +46,27 @@ info() { echo -e " ${BLUE}[*]${NC} $*"; } fail() { echo -e " ${RED}[FAIL]${NC} $*"; } step() { echo -e "\n${BOLD}── $* ──────────────────────────────────────────${NC}"; } +# Shadow-library hiding (see --hide-shadow-libs). SHADOW_HIDE_MAP holds +# "original|hidden" entries for directories we moved out of linuxdeploy's search +# path; restore_shadow_libs() puts them back and is wired to run on any exit +# (normal, error, or Ctrl-C) so a third-party app like Insync is never left +# displaced if the build fails midway. +SHADOW_HIDE_MAP=() +restore_shadow_libs() { + local entry orig hidden + for entry in "${SHADOW_HIDE_MAP[@]}"; do + orig="${entry%%|*}"; hidden="${entry#*|}" + if [ -e "$hidden" ] && [ ! -e "$orig" ]; then + if sudo mv "$hidden" "$orig"; then + info "Restored $orig" + else + warn "Could not restore $orig — run manually: sudo mv \"$hidden\" \"$orig\"" + fi + fi + done +} +trap restore_shadow_libs EXIT INT TERM + # ══════════════════════════════════════════════════════════════════════════════ # 1. Verification of appimagetool Wrapper # ══════════════════════════════════════════════════════════════════════════════ @@ -236,16 +263,20 @@ if [ ${#SHADOW_LIB_DIRS[@]} -gt 0 ]; then warn "try to bundle these instead of the system copies, then fail on their extra" warn "dependencies (e.g. Insync's libgio-2.0.so.0 needs libselinux.so.1) — the" warn "late, cryptic 'failed to run linuxdeploy'." - info "Move each directory OUT of /usr/lib for the build, then restore it after." - info "(Renaming it in place or renaming the .so files does not work — it is still" - info " matched by the glob.) For example:" - for _d in "${SHADOW_LIB_DIRS[@]}"; do - info " sudo mv \"$_d\" \"/opt/$(basename "$_d").buildhide\"" - done - info " ./build_appimage.sh" - for _d in "${SHADOW_LIB_DIRS[@]}"; do - info " sudo mv \"/opt/$(basename "$_d").buildhide\" \"$_d\" # restore after" - done + if [ "$HIDE_SHADOW_LIBS" = true ]; then + info "--hide-shadow-libs is set: these will be moved aside for the build and" + info "restored automatically when it finishes." + else + info "Re-run with --hide-shadow-libs to move them aside automatically (needs sudo):" + info " ./build_appimage.sh --hide-shadow-libs" + info "…or do it by hand — move each directory OUT of /usr/lib (a sibling path, not" + info "a rename in place; the glob still matches anything under /usr/lib), then" + info "restore it afterwards. For example:" + for _d in "${SHADOW_LIB_DIRS[@]}"; do + _hide="$(dirname "$(dirname "$_d")")/$(basename "$_d").buildhide" + info " sudo mv \"$_d\" \"$_hide\" # …build… then: sudo mv \"$_hide\" \"$_d\"" + done + fi echo "" fi @@ -371,6 +402,32 @@ if [ "$VERBOSE_FLAG" = true ]; then info "Verbose mode enabled: streaming Tauri + linuxdeploy output." fi +# With --hide-shadow-libs, move any detected third-party GLib/GTK directories out +# of linuxdeploy's search path now, recording each move so the EXIT trap restores +# them once the build finishes (or fails). They are staged in a sibling of the +# scanned prefix (e.g. /usr/lib/insync -> /usr/insync.buildhide) — outside +# /usr/lib so the plugin's glob can't reach them, and on the same filesystem so +# the move is an instant rename. +if [ "$HIDE_SHADOW_LIBS" = true ] && [ ${#SHADOW_LIB_DIRS[@]} -gt 0 ]; then + step "Hiding third-party shadow libraries from linuxdeploy" + for _d in "${SHADOW_LIB_DIRS[@]}"; do + _hide="$(dirname "$(dirname "$_d")")/$(basename "$_d").buildhide" + if [ -e "$_hide" ]; then + fail "Staging path $_hide already exists; refusing to overwrite it." + info "Move it out of the way and re-run, or restore it manually." + exit 1 + fi + info "Moving $_d -> $_hide (restored automatically when the build finishes)..." + if sudo mv "$_d" "$_hide"; then + SHADOW_HIDE_MAP+=("$_d|$_hide") + else + fail "Could not move $_d out of the way (sudo required)." + exit 1 + fi + done + ok "Shadow libraries hidden; they will be restored automatically on exit." +fi + info "Running Tauri release compiler with headless PATH and CUDA injection..." if [ -n "$BUILD_FEATURES" ]; then info "Compiling with features: ${BUILD_FEATURES}" From 96f67f4eafb47f965f0559c6933b6a9759980ab9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 21:30:47 +0000 Subject: [PATCH 7/8] Dedupe shadow-lib dirs by canonical path (fix /usr/lib64 symlink collision) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Arch/CachyOS /usr/lib64 (and often /lib) is a symlink to /usr/lib, so the scan found the same physical directory twice — e.g. /usr/lib/insync and /usr/lib64/insync. With --hide-shadow-libs the first was moved to /usr/insync.buildhide and the second collided with that path, aborting the build (the trap then restored the first). Canonicalize each candidate with readlink -f and dedupe so each physical directory is processed once. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build_appimage.sh b/build_appimage.sh index 2ec2d50..05cdcb5 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -238,7 +238,12 @@ fi # Note: the shadow directory must be moved *out* of the scanned prefix entirely — # renaming it in place (e.g. to insync.disabled) or renaming the files (…so.0.bak) # does NOT help, because the glob still matches anything under /usr/lib. +# Note: /usr/lib64 and /lib are commonly symlinks to /usr/lib (Arch/CachyOS), so +# the same physical directory can be reached under several prefixes. Canonicalize +# each candidate and dedupe, otherwise the same shadow dir is processed twice and +# the second hide collides with the first. SHADOW_LIB_DIRS=() +declare -A _shadow_seen=() shopt -s nullglob for _prefix in /usr/lib /usr/lib64 /lib /lib64; do [ -d "$_prefix" ] || continue @@ -251,7 +256,11 @@ for _prefix in /usr/lib /usr/lib64 /lib /lib64; do || compgen -G "$_sub/libglib-2.0.so*" >/dev/null 2>&1 \ || compgen -G "$_sub/libgdk_pixbuf-2.0.so*" >/dev/null 2>&1 \ || compgen -G "$_sub/libgtk-3.so*" >/dev/null 2>&1; then - SHADOW_LIB_DIRS+=("$_sub") + _canon="$(readlink -f "$_sub" 2>/dev/null || echo "$_sub")" + if [ -z "${_shadow_seen[$_canon]:-}" ]; then + _shadow_seen[$_canon]=1 + SHADOW_LIB_DIRS+=("$_canon") + fi fi done done From 64f5c042afc549454b0ebfe30e16a61342ee202f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 23:36:32 +0000 Subject: [PATCH 8/8] Hide third-party shadow libs by default so the bare build command works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the shadow-library workaround (moving Insync's /usr/lib/insync out of linuxdeploy's search path) required the --hide-shadow-libs flag; a plain `./build_appimage.sh` only warned and then failed at linuxdeploy. Make hiding the default whenever a shadow directory is actually detected — it is a safe, self-restoring operation (EXIT trap puts everything back) that only runs when needed. Add --keep-shadow-libs / KEEP_SHADOW_LIBS=1 to opt out for anyone who does not want the script to touch system directories. Users without such libs are unaffected: nothing is detected, so nothing is moved and no sudo is used. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Tm6DgUP1zwuY9powkT1EHU --- build_appimage.sh | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/build_appimage.sh b/build_appimage.sh index 05cdcb5..5652262 100755 --- a/build_appimage.sh +++ b/build_appimage.sh @@ -10,7 +10,12 @@ set -euo pipefail FORCE_CPU_FLAG=false NO_MOONSHINE=false VERBOSE_FLAG=false -HIDE_SHADOW_LIBS=false +# Shadow-library handling is ON by default: if a third-party app (e.g. Insync) +# has copies of the GLib/GTK libraries under /usr/lib, linuxdeploy sweeps them in +# and the AppImage build fails. By default we move them aside for the build and +# restore them afterwards (needs sudo). Pass --keep-shadow-libs to disable that +# and only get a warning instead. +HIDE_SHADOW_LIBS=true for arg in "$@"; do case "$arg" in --cpu) FORCE_CPU_FLAG=true ;; @@ -23,18 +28,21 @@ for arg in "$@"; do # logging. Tauri otherwise collapses a bundling failure into a bare # "failed to run linuxdeploy"; this surfaces linuxdeploy's real error. --verbose|-v) VERBOSE_FLAG=true ;; - # Automatically move any third-party GLib/GTK shadow libraries (e.g. - # Insync's /usr/lib/insync) out of linuxdeploy's search path for the - # duration of the build and restore them afterwards. Needs sudo. Without - # this the script only *warns* about them (see the detection in step 1). + # Explicit form of the default behaviour (kept for compatibility). --hide-shadow-libs|--fix-shadow-libs) HIDE_SHADOW_LIBS=true ;; + # Opt out: do NOT touch third-party shadow libraries; only warn. The + # build will likely fail at linuxdeploy if any are present. + --keep-shadow-libs|--no-hide-shadow-libs) HIDE_SHADOW_LIBS=false ;; esac done -# Env override: MOONSHINE=0 is equivalent to --no-moonshine. +# Env overrides. if [ "${MOONSHINE:-1}" = "0" ]; then NO_MOONSHINE=true fi +if [ "${KEEP_SHADOW_LIBS:-0}" = "1" ]; then + HIDE_SHADOW_LIBS=false +fi # ── Colors ─────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' @@ -273,14 +281,13 @@ if [ ${#SHADOW_LIB_DIRS[@]} -gt 0 ]; then warn "dependencies (e.g. Insync's libgio-2.0.so.0 needs libselinux.so.1) — the" warn "late, cryptic 'failed to run linuxdeploy'." if [ "$HIDE_SHADOW_LIBS" = true ]; then - info "--hide-shadow-libs is set: these will be moved aside for the build and" - info "restored automatically when it finishes." + info "These will be moved aside for the build and restored automatically when it" + info "finishes (needs sudo). Pass --keep-shadow-libs to disable and build as-is." else - info "Re-run with --hide-shadow-libs to move them aside automatically (needs sudo):" - info " ./build_appimage.sh --hide-shadow-libs" - info "…or do it by hand — move each directory OUT of /usr/lib (a sibling path, not" - info "a rename in place; the glob still matches anything under /usr/lib), then" - info "restore it afterwards. For example:" + info "--keep-shadow-libs is set, so they are left in place and the build will most" + info "likely fail at linuxdeploy. To let the script handle them, drop that flag." + info "To do it by hand, move each directory OUT of /usr/lib (a sibling path, not a" + info "rename in place; the glob still matches anything under /usr/lib), e.g.:" for _d in "${SHADOW_LIB_DIRS[@]}"; do _hide="$(dirname "$(dirname "$_d")")/$(basename "$_d").buildhide" info " sudo mv \"$_d\" \"$_hide\" # …build… then: sudo mv \"$_hide\" \"$_d\""