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:
@@ -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
|
||||
|
||||
Executable
+69
@@ -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."
|
||||
Reference in New Issue
Block a user