diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh index 6df42bf..70f22ee 100644 --- a/scripts/build-linux.sh +++ b/scripts/build-linux.sh @@ -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 diff --git a/scripts/fix-linux-appimage.sh b/scripts/fix-linux-appimage.sh new file mode 100755 index 0000000..e1dc5b8 --- /dev/null +++ b/scripts/fix-linux-appimage.sh @@ -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 .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."