On the aarch64 Gitea runner, tauri-bundler's prepare_tools step runs
'dd ... seek=8 conv=notrunc' on the cached linuxdeploy-x86_64 binary to
hide it from AppImage integration tools. That zeroed bytes 8-10 of the
file; our cached binary there was a bash wrapper script, so the dd wrote
into the middle of its shebang/interpreter line, corrupting it, and the
bundler's subsequent spawn failed with ENOENT ('failed to bundle
project: No such file or directory').
Replace the shell wrapper with a small native ELF launcher compiled with
cc: bytes 8-10 of an ELF header are EI_PAD padding, unused by the
loader, so the same dd leaves it executable. The launcher sets the same
environment the old wrapper did and execs the real aarch64 linuxdeploy
binary. Also cache AppRun-x86_64 explicitly so the bundler does not need
to fetch it itself mid-build.
Verified locally: compiled a minimal ELF binary, ran the same dd
command against it, confirmed it still executes (the shebang-script
equivalent does not).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
186 lines
8.6 KiB
Bash
Executable File
186 lines
8.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Builds the desktop app (Tauri) and places the resulting bundles in dist-gui/.
|
|
#
|
|
# scripts/build-gui.sh linux # AppImage and .deb (native architecture)
|
|
# scripts/build-gui.sh linux-aarch64 # AppImage and .deb for aarch64
|
|
# scripts/build-gui.sh linux-x86_64 # AppImage and .deb for x86_64 (native or cross-built from aarch64)
|
|
# scripts/build-gui.sh windows # NSIS installer, cross-built from Linux via cargo-xwin
|
|
# scripts/build-gui.sh windows-docker # Same as above inside an isolated container
|
|
#
|
|
# The Tauri updater signs each bundle (createUpdaterArtifacts: true), so
|
|
# TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD must be set
|
|
# in the environment. If unset locally, this script loads ~/.tauri/guara-updater.key
|
|
# when available.
|
|
#
|
|
# Windows builds use cargo-xwin and NSIS (requires: nsis, lld, llvm, clang, and
|
|
# `cargo install cargo-xwin`).
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
platform=${1:?usage: build-gui.sh <linux|linux-x86_64|linux-aarch64|windows|windows-docker>}
|
|
tauri() { npx --yes @tauri-apps/cli@2 "$@"; }
|
|
|
|
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ] && [ -f "$HOME/.tauri/guara-updater.key" ]; then
|
|
export TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/guara-updater.key")"
|
|
[ -f "$HOME/.tauri/guara-updater.pass" ] && export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat "$HOME/.tauri/guara-updater.pass")"
|
|
fi
|
|
: "${TAURI_SIGNING_PRIVATE_KEY:?set TAURI_SIGNING_PRIVATE_KEY (or generate a local key with 'tauri signer generate')}"
|
|
|
|
if [ "$platform" = windows-docker ]; then
|
|
docker volume create guara-cargo >/dev/null
|
|
docker volume create guara-rustup >/dev/null
|
|
mkdir -p dist-gui
|
|
exec docker run --rm -v "$PWD":/src -v guara-cargo:/root/.cargo -v guara-rustup:/root/.rustup \
|
|
-w /src -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
|
|
-e TAURI_SIGNING_PRIVATE_KEY -e TAURI_SIGNING_PRIVATE_KEY_PASSWORD \
|
|
ubuntu:24.04 bash -c '
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update -qq
|
|
apt-get install -y -qq curl ca-certificates build-essential pkg-config nsis lld llvm clang nodejs npm unzip >/dev/null
|
|
[ -x /root/.cargo/bin/rustup ] || curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
|
|
export PATH=/root/.cargo/bin:$PATH
|
|
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
|
|
scripts/build-gui.sh windows
|
|
chown -R "$HOST_UID:$HOST_GID" dist-gui apps/gui/src-tauri/target 2>/dev/null || true'
|
|
fi
|
|
|
|
rm -rf dist-gui
|
|
mkdir -p dist-gui
|
|
cd apps/gui/src-tauri
|
|
|
|
[ -f icons/icon.ico ] || tauri icon icons/source.png -o icons
|
|
|
|
tauri_blob_cache="$HOME/.cache/tauri"
|
|
if [ -d /opt/hostedtoolcache ] && [ -w /opt/hostedtoolcache ]; then
|
|
tauri_blob_cache="/opt/hostedtoolcache/tauri-tools"
|
|
fi
|
|
mkdir -p "$tauri_blob_cache" "$HOME/.cache/tauri"
|
|
|
|
fetch_cached_tool() {
|
|
local name=$1 url=$2 patch_elf=${3:-0}
|
|
local cached="$tauri_blob_cache/$name"
|
|
if [ ! -s "$cached" ]; then
|
|
local tmp="$cached.tmp.$$"
|
|
curl -fsSL "$url" -o "$tmp"
|
|
if [ "$patch_elf" = 1 ]; then
|
|
dd if=/dev/zero of="$tmp" bs=1 count=3 seek=8 conv=notrunc 2>/dev/null
|
|
fi
|
|
chmod +x "$tmp"
|
|
mv -f "$tmp" "$cached"
|
|
fi
|
|
if [ "$cached" != "$HOME/.cache/tauri/$name" ]; then
|
|
cp -f "$cached" "$HOME/.cache/tauri/$name"
|
|
chmod +x "$HOME/.cache/tauri/$name"
|
|
fi
|
|
}
|
|
|
|
case "$platform" in
|
|
linux|linux-aarch64)
|
|
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
|
|
rm -rf "$bundle/appimage" "$bundle/deb"
|
|
tauri build --bundles appimage,deb
|
|
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
|
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
|
|
;;
|
|
linux-x86_64)
|
|
if [ "$(uname -m)" = x86_64 ]; then
|
|
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
|
|
rm -rf "$bundle/appimage" "$bundle/deb"
|
|
tauri build --bundles appimage,deb
|
|
else
|
|
rustup target add x86_64-unknown-linux-gnu >/dev/null
|
|
mkdir -p /tmp/x86_64-cross-tools "$HOME/.cache/tauri"
|
|
cat >/tmp/x86_64-cross-tools/ldd <<'EOF'
|
|
#!/usr/bin/env bash
|
|
exec qemu-x86_64 /lib64/ld-linux-x86-64.so.2 --list "$@"
|
|
EOF
|
|
chmod +x /tmp/x86_64-cross-tools/ldd
|
|
ln -sf /usr/bin/x86_64-linux-gnu-strip /tmp/x86_64-cross-tools/strip
|
|
for tool_rel in glib-2.0/gio-querymodules gdk-pixbuf-2.0/gdk-pixbuf-query-loaders libgtk-3-0t64/gtk-query-immodules-3.0 libgtk-3-0/gtk-query-immodules-3.0; do
|
|
if [ -x "/usr/lib/x86_64-linux-gnu/$tool_rel" ]; then
|
|
tname=$(basename "$tool_rel")
|
|
printf '#!/usr/bin/env bash\nexec qemu-x86_64 "/usr/lib/x86_64-linux-gnu/%s" "$@"\n' "$tool_rel" >"/tmp/x86_64-cross-tools/$tname"
|
|
chmod +x "/tmp/x86_64-cross-tools/$tname"
|
|
fi
|
|
done
|
|
fetch_cached_tool "linuxdeploy-07333c6-aarch64.AppImage" \
|
|
"https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy-07333c6/linuxdeploy-aarch64.AppImage" 1
|
|
# tauri-bundler looks for this exact cache name (no arch suffix); the
|
|
# architecture only appears in the download URL.
|
|
fetch_cached_tool "linuxdeploy-plugin-appimage.AppImage" \
|
|
"https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-aarch64.AppImage" 1
|
|
fetch_cached_tool "AppRun-x86_64" \
|
|
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-x86_64" 0
|
|
fetch_cached_tool "runtime-x86_64" \
|
|
"https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64" 0
|
|
# tauri-bundler runs `dd ... seek=8` on this path to hide linuxdeploy from
|
|
# AppImage integration tools. A shell-script wrapper loses its shebang and
|
|
# then fails with ENOENT, so use a native ELF launcher: the dd writes into
|
|
# ELF padding and the launcher stays executable.
|
|
launcher_src=/tmp/x86_64-cross-tools/linuxdeploy-x86_64.c
|
|
launcher_bin="$HOME/.cache/tauri/linuxdeploy-07333c6-x86_64.AppImage"
|
|
cat >"$launcher_src" <<'EOF'
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *home = getenv("HOME");
|
|
if (home == NULL) { fputs("HOME is not set\n", stderr); return 127; }
|
|
char target[4096];
|
|
char runtime[4096];
|
|
char path[8192];
|
|
const char *old_path = getenv("PATH");
|
|
snprintf(target, sizeof target, "%s/.cache/tauri/linuxdeploy-07333c6-aarch64.AppImage", home);
|
|
snprintf(runtime, sizeof runtime, "%s/.cache/tauri/runtime-x86_64", home);
|
|
snprintf(path, sizeof path, "/tmp/x86_64-cross-tools:%s",
|
|
old_path ? old_path : "/usr/local/bin:/usr/bin:/bin");
|
|
if (setenv("ARCH", "x86_64", 1) ||
|
|
setenv("NO_STRIP", "true", 1) ||
|
|
setenv("APPIMAGE_EXTRACT_AND_RUN", "1", 1) ||
|
|
setenv("LDAI_RUNTIME_FILE", runtime, 1) ||
|
|
setenv("LD_GTK_LIBRARY_PATH", "/usr/lib/x86_64-linux-gnu", 1) ||
|
|
setenv("DEB_HOST_MULTIARCH", "x86_64-linux-gnu", 1) ||
|
|
setenv("PKG_CONFIG_ALLOW_CROSS", "1", 1) ||
|
|
setenv("PKG_CONFIG_LIBDIR", "/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig", 1) ||
|
|
setenv("PATH", path, 1)) {
|
|
perror("setenv");
|
|
return 127;
|
|
}
|
|
char **args = calloc((size_t)argc + 1, sizeof *args);
|
|
if (args == NULL) { perror("calloc"); return 127; }
|
|
args[0] = target;
|
|
for (int i = 1; i < argc; i++) args[i] = argv[i];
|
|
execv(target, args);
|
|
perror(target);
|
|
return 127;
|
|
}
|
|
EOF
|
|
cc -O2 -Wall -Wextra -o "$launcher_bin" "$launcher_src"
|
|
export PKG_CONFIG_ALLOW_CROSS=1
|
|
export PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig
|
|
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
|
|
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
|
|
export CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++
|
|
bundle="${CARGO_TARGET_DIR:-target}/x86_64-unknown-linux-gnu/release/bundle"
|
|
rm -rf "$bundle/appimage" "$bundle/deb"
|
|
tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb
|
|
fi
|
|
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
|
cp "$bundle"/appimage/*.AppImage.sig ../../../dist-gui/ 2>/dev/null || true
|
|
;;
|
|
windows)
|
|
rustup target add x86_64-pc-windows-msvc >/dev/null
|
|
bundle="${CARGO_TARGET_DIR:-target}/x86_64-pc-windows-msvc/release/bundle/nsis"
|
|
rm -rf "$bundle"
|
|
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
|
cp "$bundle"/*-setup.exe ../../../dist-gui/
|
|
cp "$bundle"/*-setup.exe.sig ../../../dist-gui/ 2>/dev/null || true
|
|
;;
|
|
*) echo "unknown platform: $platform" >&2; exit 2 ;;
|
|
esac
|
|
|
|
ls -1 ../../../dist-gui
|