fix(linux): repair patchelf-corrupted Bun sidecar in AppImage

linuxdeploy injects a RUNPATH ($ORIGIN/../lib) via patchelf into every
usr/bin executable when building the AppDir. The Bun-compiled xpbridge
sidecar (self-contained, JS/assets appended past the ELF) does not
survive ELF rewriting — the patched copy core-dumps on start, so the app
launches but the bridge never listens.

Add scripts/fix-linux-appimage.sh: extract the built AppImage, restore the
pristine repo sidecar, repack with linuxdeploy-plugin-appimage (which does
not patchelf), verify the sidecar is byte-identical, and regenerate the
updater .sig. Wired into scripts/build-linux.sh after `tauri build`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-02 14:23:58 +02:00
parent 9aba24978b
commit 138498956e
2 changed files with 75 additions and 0 deletions
+6
View File
@@ -34,6 +34,12 @@ docker run --rm --platform linux/amd64 \
"$IMG" \
bash -c "export PATH=/usr/local/cargo/bin:\$PATH; tauri build --target x86_64-unknown-linux-gnu --bundles $BUNDLES $CFG"
# Repair the Bun sidecar that linuxdeploy's patchelf corrupts inside the AppImage
# (see scripts/fix-linux-appimage.sh). Runs on the host against the mounted
# artifacts; regenerates the updater .sig when a signing key is present.
echo "==> repairing AppImage sidecar"
bash "$ROOT/scripts/fix-linux-appimage.sh"
echo "==> artifacts:"
find target-linux/x86_64-unknown-linux-gnu/release/bundle -maxdepth 2 -type f \
\( -name '*.AppImage' -o -name '*.deb' -o -name '*.AppImage.sig' -o -name '*.tar.gz' -o -name '*.sig' \) 2>/dev/null
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Post-process a Tauri-built Linux AppImage to repair the Bun sidecar.
#
# WHY: Tauri's AppImage step runs linuxdeploy, which patchelf-injects a RUNPATH
# ($ORIGIN/../lib) into every executable in usr/bin. The Bun-compiled sidecar
# `xpbridge` is a self-contained binary (~91 MB, JS/assets appended past the ELF)
# and does NOT survive ELF rewriting — the patched copy core-dumps on start, so
# the app launches but the bridge never listens. The repo/standalone binary is
# fine; only the AppImage copy is corrupt.
#
# FIX: replace the patched sidecar in the AppImage with the pristine repo binary,
# repack (the appimage plugin does NOT patchelf), and regenerate the updater .sig.
#
# Run AFTER `tauri build ... --bundles appimage[,updater]`. Idempotent.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target-linux}"
BUNDLE_DIR="$TARGET_DIR/x86_64-unknown-linux-gnu/release/bundle/appimage"
ORIG_SIDECAR="$ROOT/desktop/src-tauri/binaries/xpbridge-x86_64-unknown-linux-gnu"
PLUGIN="$HOME/.cache/tauri/linuxdeploy-plugin-appimage.AppImage"
[[ -f "$ORIG_SIDECAR" ]] || { echo "!! pristine sidecar missing: $ORIG_SIDECAR (run prep-desktop.sh)"; exit 1; }
[[ -x "$PLUGIN" ]] || { echo "!! linuxdeploy-plugin-appimage missing: $PLUGIN (run a tauri appimage build once to fetch it)"; exit 1; }
APPIMAGE="$(find "$BUNDLE_DIR" -maxdepth 1 -name '*.AppImage' | head -1)"
[[ -n "$APPIMAGE" ]] || { echo "!! no AppImage found in $BUNDLE_DIR"; exit 1; }
echo "==> repairing sidecar in: $(basename "$APPIMAGE")"
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
( cd "$WORK" && APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGE" --appimage-extract >/dev/null )
APPDIR="$WORK/squashfs-root"
SC="$(find "$APPDIR" -name xpbridge -type f | head -1)"
[[ -n "$SC" ]] || { echo "!! xpbridge not found inside AppImage"; exit 1; }
cp -f "$ORIG_SIDECAR" "$SC"; chmod +x "$SC"
echo "==> restored pristine sidecar ($(sha256sum "$SC" | cut -c1-12)...)"
# Repack. The appimage plugin embeds the AppDir verbatim — no patchelf — so the
# sidecar stays byte-identical to the repo binary.
OUT="$WORK/out.AppImage"
( cd "$WORK" && APPIMAGE_EXTRACT_AND_RUN=1 NO_STRIP=1 ARCH=x86_64 LDAI_OUTPUT="$OUT" \
"$PLUGIN" --appdir "$APPDIR" >/dev/null )
[[ -f "$OUT" ]] || { echo "!! repack produced no AppImage"; exit 1; }
# sanity: the repacked sidecar must run, not core-dump
( cd "$WORK" && rm -rf v && mkdir v && cd v && APPIMAGE_EXTRACT_AND_RUN=1 "$OUT" --appimage-extract >/dev/null )
NEWSC="$(find "$WORK/v/squashfs-root" -name xpbridge -type f | head -1)"
if [[ "$(sha256sum "$NEWSC" | cut -d' ' -f1)" != "$(sha256sum "$ORIG_SIDECAR" | cut -d' ' -f1)" ]]; then
echo "!! repacked sidecar hash != pristine — repack still mangled it"; exit 1
fi
echo "==> verified: repacked sidecar is byte-identical to repo binary"
mv -f "$OUT" "$APPIMAGE"
echo "==> replaced original AppImage (same name, updater url unchanged)"
# Regenerate the updater signature for the new file, if signing is configured.
SIG="$APPIMAGE.sig"
if [[ -f "$ROOT/desktop/.tauri-signing.key" && -f "$ROOT/desktop/.tauri-signing.pw" ]]; then
# signer writes <FILE>.sig next to the input automatically
TAURI_SIGNING_PRIVATE_KEY="$(cat "$ROOT/desktop/.tauri-signing.key")" \
TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$ROOT/desktop/.tauri-signing.pw")" \
npx --prefix desktop tauri signer sign "$APPIMAGE" >/dev/null
echo "==> regenerated updater signature: $(basename "$SIG")"
else
[[ -f "$SIG" ]] && { rm -f "$SIG"; echo "==> no signing key — removed stale $(basename "$SIG")"; }
fi
echo "==> done. AppImage repaired."