mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
## Summary - Bundles the glibc dynamic linker that `sharun` needs inside the Linux AppImage when the post-build AppImage cleanup runs. - Repackages and re-signs AppImage artifacts when the only mutation is adding the missing loader, not just when graphics libraries were stripped. - Keeps the existing Mesa/libdrm/libva stripping behavior intact and adds a release smoke item for the Ubuntu 24.04 `Interpreter not found!` regression. ## Problem - `OpenHuman_0.54.0_amd64.AppImage` can exit immediately on Ubuntu 24.04 with `Interpreter not found!` because the AppImage contains a `sharun` launcher but no `lib/ld-linux-x86-64.so.2`. - The existing post-processing script only repacked when graphics libraries were removed, so it had no guard for a missing `sharun` interpreter. ## Solution - Detect `sharun`-style launchers by checking the extracted AppDir launcher binaries for the `Interpreter not found!` marker. - Resolve the expected loader from the build target architecture and copy it into `squashfs-root/lib/` when missing. - Fail the post-processing step if the AppImage uses `sharun` but the build host cannot provide the required loader. - Track all modified AppImages, whether modified by stripping graphics libs or by adding the loader, so updater tarballs and signatures stay in sync. ## Submission Checklist - [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — release smoke checklist updated; local function smoke covers loader injection path. - [x] **Diff coverage >= 80%** — N/A: shell release packaging script and docs are not covered by Vitest/cargo diff coverage. - [x] Coverage matrix updated — N/A: release packaging behavior, not a product feature row. - [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — N/A: no feature matrix row. - [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy)) - [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) - [x] Linked issue closed via `Closes #NNN` in the `## Related` section ## Impact - Linux AppImage release artifacts should launch on clean Ubuntu 24.04 hosts without requiring users to extract the AppImage and manually copy `ld-linux-x86-64.so.2`. - Packaging fails earlier if a future build cannot locate the required loader, preventing a known-broken AppImage from shipping. ## Related - Closes #2297 - Follow-up PR(s)/TODOs: N/A --- ## AI Authored PR Metadata (required for Codex/Linear PRs) ### Linear Issue - Key: N/A - URL: N/A ### Commit & Branch - Branch: `codex/2297-appimage-sharun-loader` - Commit SHA: `4260df24f5355a63df02e748e276586ebed0c53a` ### Validation Run - [x] `pnpm --filter openhuman-app format:check` — N/A: no frontend files changed. - [x] `pnpm typecheck` — N/A: no TypeScript files changed. - [x] Focused tests: Git Bash smoke test for `ensure_sharun_interpreter` copies `ld-linux-x86-64.so.2` into a synthetic sharun AppDir. - [x] Rust fmt/check (if changed): N/A: no Rust files changed. - [x] Tauri fmt/check (if changed): N/A: no Tauri Rust files changed. - [x] Shell syntax/check: `C:\Program Files\Git\bin\bash.exe -n scripts/release/strip-appimage-graphics-libs.sh`; `git diff --check`. ### Validation Blocked - `command:` Full AppImage rebuild and launch on Ubuntu 24.04. - `error:` No produced Linux AppImage artifact is available in this local Windows workspace. - `impact:` CI release packaging will exercise the changed script; manual release smoke checklist now covers the Ubuntu 24.04 launch regression. ### Behavior Changes - Intended behavior change: AppImage post-processing now bundles the missing `sharun` dynamic linker when needed. - User-visible effect: Linux users should no longer hit `Interpreter not found!` on affected AppImages. ### Parity Contract - Legacy behavior preserved: existing graphics-library stripping, re-signing, and updater tarball rebuild behavior remain intact. - Guard/fallback/dispatch parity checks: AppImages unchanged by stripping or loader injection are still left untouched; missing host loader now fails packaging instead of shipping a broken artifact. ### Duplicate / Superseded PR Handling - Duplicate PR(s): N/A - Canonical PR: this PR - Resolution (closed/superseded/updated): N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * AppImages now remove incompatible host graphics libraries and auto-include a missing bundled dynamic loader when needed, preventing "Interpreter not found" failures on clean Ubuntu 24.04 hosts. * **Chores** * Added a smoke-test checklist item to validate AppImage launches on Ubuntu 24.04. * Release tooling now only repacks and re-signs AppImages that were actually modified. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2307?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: aqilaziz <gonzes7@gmail.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
363 lines
10 KiB
Bash
Executable File
363 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Strip host graphics libraries from AppImage bundles so they load the user's
|
|
# system Mesa/libdrm/libva at launch instead of the older versions baked in by
|
|
# lib4bin's ldd-walk on the ubuntu-22.04 build runner.
|
|
#
|
|
# Without this, AppImages built on Mesa 22.x fail to initialize on systems
|
|
# with newer GPUs (RDNA3, Intel Arc, Lovelace) because the bundled drivers
|
|
# can't talk to the host kernel/driver stack. AppImage convention is to never
|
|
# ship graphics drivers — they must come from the host. See:
|
|
# https://github.com/AppImageCommunity/pkg2appimage/blob/master/excludelist
|
|
#
|
|
# Only top-level lib directories are swept. CEF's own subdirs (swiftshader/,
|
|
# locales/, libcef.so neighbors) are left alone — CEF ships its own
|
|
# GLES/EGL implementation that must stay bundled.
|
|
#
|
|
# Usage: strip-appimage-graphics-libs.sh <bundle-root> [bundle-root...]
|
|
# where <bundle-root> contains an `appimage/` subdir with *.AppImage files.
|
|
#
|
|
# Env:
|
|
# TAURI_SIGNING_PRIVATE_KEY — re-sign modified artifacts when set
|
|
# TAURI_SIGNING_PRIVATE_KEY_PASSWORD — passphrase for the key (may be empty)
|
|
# APPIMAGETOOL_URL — override appimagetool download URL
|
|
# APPIMAGETOOL_SHA256 — expected SHA256 of the download
|
|
# (verified before use when set; rotate
|
|
# alongside APPIMAGETOOL_URL)
|
|
|
|
set -euo pipefail
|
|
|
|
EXCLUDE_PATTERNS=(
|
|
'libGL.so.*'
|
|
'libGLX.so.*'
|
|
'libGLdispatch.so.*'
|
|
'libGLESv1_CM.so.*'
|
|
'libGLESv2.so.*'
|
|
'libEGL.so.*'
|
|
'libgbm.so.*'
|
|
'libdrm.so.*'
|
|
'libdrm_*.so.*'
|
|
'libva.so.*'
|
|
'libva-drm.so.*'
|
|
'libva-glx.so.*'
|
|
'libva-x11.so.*'
|
|
'libvdpau.so.*'
|
|
'libxcb-dri2.so.*'
|
|
'libxcb-dri3.so.*'
|
|
'libxcb-glx.so.*'
|
|
'libxcb-present.so.*'
|
|
)
|
|
|
|
# Default to a pinned release tag rather than the mutable `continuous` asset so
|
|
# CI builds are reproducible and resistant to upstream replacement. Override via
|
|
# APPIMAGETOOL_URL (and bump APPIMAGETOOL_SHA256 alongside it).
|
|
APPIMAGETOOL_URL="${APPIMAGETOOL_URL:-https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage}"
|
|
APPIMAGETOOL_SHA256="${APPIMAGETOOL_SHA256:-}"
|
|
|
|
ensure_appimagetool() {
|
|
if command -v appimagetool >/dev/null 2>&1; then
|
|
APPIMAGETOOL_BIN="$(command -v appimagetool)"
|
|
return
|
|
fi
|
|
local tool=/tmp/appimagetool.AppImage
|
|
if [ ! -x "$tool" ]; then
|
|
echo "[strip-libs] Downloading appimagetool from $APPIMAGETOOL_URL"
|
|
curl -fsSL "$APPIMAGETOOL_URL" -o "$tool"
|
|
if [ -n "$APPIMAGETOOL_SHA256" ]; then
|
|
echo "[strip-libs] Verifying appimagetool sha256"
|
|
if ! echo "${APPIMAGETOOL_SHA256} ${tool}" | sha256sum -c -; then
|
|
echo "[strip-libs] ERROR: appimagetool sha256 mismatch — refusing to run" >&2
|
|
rm -f "$tool"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "[strip-libs] WARNING: APPIMAGETOOL_SHA256 not set — skipping integrity check" >&2
|
|
fi
|
|
chmod +x "$tool"
|
|
fi
|
|
APPIMAGETOOL_BIN="$tool"
|
|
}
|
|
|
|
appimage_loader_name() {
|
|
local target_arch="${APPIMAGE_TARGET_ARCH:-${MATRIX_TARGET:-$(uname -m)}}"
|
|
case "$target_arch" in
|
|
x86_64*|amd64*)
|
|
echo "ld-linux-x86-64.so.2"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
host_dynamic_loader() {
|
|
local loader_name="$1"
|
|
local candidates=()
|
|
case "$loader_name" in
|
|
ld-linux-x86-64.so.2)
|
|
candidates=(
|
|
"/lib64/$loader_name"
|
|
"/lib/x86_64-linux-gnu/$loader_name"
|
|
"/usr/lib64/$loader_name"
|
|
"/usr/lib/$loader_name"
|
|
)
|
|
;;
|
|
ld-linux-aarch64.so.1)
|
|
candidates=(
|
|
"/lib/$loader_name"
|
|
"/lib/aarch64-linux-gnu/$loader_name"
|
|
"/usr/lib/aarch64-linux-gnu/$loader_name"
|
|
)
|
|
;;
|
|
esac
|
|
|
|
local candidate
|
|
for candidate in "${candidates[@]}"; do
|
|
if [ -f "$candidate" ]; then
|
|
echo "$candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
is_executable_elf() {
|
|
local candidate
|
|
candidate="$1"
|
|
[ -f "$candidate" ] || return 1
|
|
[ -x "$candidate" ] || return 1
|
|
[ "$(LC_ALL=C head -c 4 "$candidate" 2>/dev/null || true)" = $'\177ELF' ]
|
|
}
|
|
|
|
emit_entry_if_elf() {
|
|
local candidate="$1"
|
|
if is_executable_elf "$candidate"; then
|
|
printf '%s\0' "$candidate"
|
|
fi
|
|
}
|
|
|
|
emit_desktop_exec_candidate() {
|
|
local appdir="$1"
|
|
local command="$2"
|
|
local candidate
|
|
|
|
[ -n "$command" ] || return 0
|
|
case "$command" in
|
|
/*)
|
|
emit_entry_if_elf "$appdir$command"
|
|
;;
|
|
*/*)
|
|
emit_entry_if_elf "$appdir/$command"
|
|
;;
|
|
*)
|
|
for candidate in "$appdir/$command" "$appdir/bin/$command" "$appdir/usr/bin/$command"; do
|
|
emit_entry_if_elf "$candidate"
|
|
done
|
|
;;
|
|
esac
|
|
}
|
|
|
|
discover_appimage_entry_binaries() {
|
|
local appdir="$1"
|
|
local desktop line exec_line command root candidate
|
|
|
|
emit_entry_if_elf "$appdir/AppRun"
|
|
emit_entry_if_elf "$appdir/sharun"
|
|
|
|
while IFS= read -r -d '' desktop; do
|
|
while IFS= read -r line || [ -n "$line" ]; do
|
|
case "$line" in
|
|
Exec=*)
|
|
exec_line="${line#Exec=}"
|
|
case "$exec_line" in
|
|
\"*\")
|
|
command="${exec_line#\"}"
|
|
command="${command%%\"*}"
|
|
;;
|
|
\'*\')
|
|
command="${exec_line#\'}"
|
|
command="${command%%\'*}"
|
|
;;
|
|
*)
|
|
command="${exec_line%%[[:space:]]*}"
|
|
;;
|
|
esac
|
|
emit_desktop_exec_candidate "$appdir" "$command"
|
|
;;
|
|
esac
|
|
done < "$desktop"
|
|
done < <(find "$appdir" -maxdepth 1 -type f -name '*.desktop' -print0)
|
|
|
|
for root in "$appdir" "$appdir/bin" "$appdir/usr/bin"; do
|
|
[ -d "$root" ] || continue
|
|
while IFS= read -r -d '' candidate; do
|
|
emit_entry_if_elf "$candidate"
|
|
done < <(find "$root" -maxdepth 1 -type f -perm /111 -print0)
|
|
done
|
|
}
|
|
|
|
uses_sharun_launcher() {
|
|
local appdir="$1"
|
|
local candidate
|
|
while IFS= read -r -d '' candidate; do
|
|
if grep -a -q "Interpreter not found!" "$candidate" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
done < <(discover_appimage_entry_binaries "$appdir")
|
|
return 1
|
|
}
|
|
|
|
ensure_sharun_interpreter() {
|
|
local appdir="$1"
|
|
if ! uses_sharun_launcher "$appdir"; then
|
|
return 1
|
|
fi
|
|
|
|
local loader_name
|
|
if ! loader_name="$(appimage_loader_name)"; then
|
|
echo "[strip-libs] ERROR: AppImage uses sharun but architecture is unsupported; cannot determine required loader" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local target="$appdir/lib/$loader_name"
|
|
if [ -e "$target" ]; then
|
|
return 1
|
|
fi
|
|
|
|
local source
|
|
if ! source="$(host_dynamic_loader "$loader_name")"; then
|
|
echo "[strip-libs] ERROR: AppImage uses sharun but host loader $loader_name was not found; refusing to ship an AppImage that exits with 'Interpreter not found!'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$appdir/lib"
|
|
cp -L "$source" "$target"
|
|
chmod 755 "$target"
|
|
echo "[strip-libs] bundling sharun interpreter ${target#"$appdir"/} from $source"
|
|
return 0
|
|
}
|
|
|
|
strip_one_appimage() {
|
|
local img="$1"
|
|
local original
|
|
original="$(realpath "$img")"
|
|
local name
|
|
name="$(basename "$original")"
|
|
local workdir
|
|
workdir="$(mktemp -d)"
|
|
|
|
echo "[strip-libs] Processing $original"
|
|
(
|
|
cd "$workdir"
|
|
chmod +x "$original"
|
|
if ! "$original" --appimage-extract >/dev/null; then
|
|
echo "[strip-libs] ERROR: --appimage-extract failed for $original" >&2
|
|
exit 1
|
|
fi
|
|
)
|
|
|
|
local appdir="$workdir/squashfs-root"
|
|
local removed=0
|
|
local added_loader=0
|
|
local lib_roots=()
|
|
for candidate in \
|
|
"$appdir/usr/lib" \
|
|
"$appdir/usr/lib/x86_64-linux-gnu" \
|
|
"$appdir/shared/lib" \
|
|
"$appdir/shared/lib/x86_64-linux-gnu" \
|
|
"$appdir/lib" \
|
|
"$appdir/lib/x86_64-linux-gnu"; do
|
|
[ -d "$candidate" ] && lib_roots+=("$candidate")
|
|
done
|
|
|
|
if [ "${#lib_roots[@]}" -eq 0 ]; then
|
|
echo "[strip-libs] WARNING: no known lib roots inside $original — layout changed?" >&2
|
|
else
|
|
for root in "${lib_roots[@]}"; do
|
|
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
|
|
while IFS= read -r -d '' f; do
|
|
echo "[strip-libs] removing ${f#"$appdir"/}"
|
|
rm -f "$f"
|
|
removed=$((removed + 1))
|
|
done < <(find "$root" -maxdepth 1 -name "$pattern" -print0)
|
|
done
|
|
done
|
|
fi
|
|
|
|
if ensure_sharun_interpreter "$appdir"; then
|
|
added_loader=1
|
|
fi
|
|
|
|
if [ "$removed" -eq 0 ] && [ "$added_loader" -eq 0 ]; then
|
|
echo "[strip-libs] No graphics libs or missing sharun interpreter found in $original; leaving unchanged."
|
|
rm -rf "$workdir"
|
|
return
|
|
fi
|
|
echo "[strip-libs] Removed $removed file(s), added $added_loader loader file(s); repacking AppImage."
|
|
|
|
local rebuilt="$workdir/$name"
|
|
(
|
|
cd "$workdir"
|
|
ARCH=x86_64 "$APPIMAGETOOL_BIN" --appimage-extract-and-run \
|
|
--no-appstream squashfs-root "$rebuilt" >/dev/null
|
|
)
|
|
mv "$rebuilt" "$original"
|
|
rm -rf "$workdir"
|
|
MODIFIED_PATHS+=("$original")
|
|
}
|
|
|
|
resign_artifact() {
|
|
local file="$1"
|
|
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
|
return
|
|
fi
|
|
if ! command -v cargo-tauri >/dev/null 2>&1; then
|
|
echo "[strip-libs] WARNING: cargo-tauri not on PATH; cannot re-sign $file" >&2
|
|
return
|
|
fi
|
|
echo "[strip-libs] Re-signing $file"
|
|
rm -f "$file.sig"
|
|
cargo tauri signer sign \
|
|
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
|
|
--password "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" \
|
|
"$file" >/dev/null
|
|
}
|
|
|
|
main() {
|
|
if [ $# -lt 1 ]; then
|
|
echo "Usage: $0 <bundle-root> [bundle-root...]" >&2
|
|
exit 2
|
|
fi
|
|
ensure_appimagetool
|
|
shopt -s nullglob
|
|
MODIFIED_PATHS=()
|
|
local found_any=0
|
|
for root in "$@"; do
|
|
[ -d "$root/appimage" ] || continue
|
|
for img in "$root/appimage"/*.AppImage; do
|
|
found_any=1
|
|
strip_one_appimage "$img"
|
|
done
|
|
done
|
|
if [ "$found_any" -eq 0 ]; then
|
|
echo "[strip-libs] No AppImages found under any provided bundle root." >&2
|
|
return
|
|
fi
|
|
|
|
# Re-sign each modified .AppImage and rebuild its updater tarball + sig.
|
|
# The updater tarball is just a gzipped tar of the .AppImage (Tauri convention),
|
|
# so its contents are stale the moment we mutate the AppImage.
|
|
for original in "${MODIFIED_PATHS[@]:-}"; do
|
|
[ -n "$original" ] || continue
|
|
resign_artifact "$original"
|
|
|
|
local tar="$original.tar.gz"
|
|
if [ -e "$tar" ]; then
|
|
echo "[strip-libs] Rebuilding $(basename "$tar")"
|
|
tar -C "$(dirname "$original")" -czf "$tar" "$(basename "$original")"
|
|
resign_artifact "$tar"
|
|
fi
|
|
done
|
|
}
|
|
|
|
main "$@"
|