Files
lutris/utils/appimage/AppRun
Daniel Johnson 053933a34c Add AppImage packaging proof-of-concept
Flathub's recent ban on AI-using projects (see #6724) puts Lutris's
Flatpak channel at risk, and Flatpak has always been an awkward fit
anyway: Lutris is fundamentally an unsandboxed game manager that needs
to call host xrandr, 7z, fuser, wine, flatpak, mount drives, and reach
the user's whole $HOME. AppImage gives us a single-file distribution
that does none of that sandboxing.

This adds `make appimage`, which builds a self-contained AppImage in
a Docker/Podman container based on Ubuntu 22.04 (glibc 2.35, Python
3.10, GTK3, WebKit2GTK 4.1). The pipeline:

  utils/appimage/build.sh          — host wrapper (docker or podman)
  utils/appimage/Dockerfile        — build env with all GI typelibs
  utils/appimage/build-in-container.sh
                                   — assembles AppDir via linuxdeploy
                                     + linuxdeploy-plugin-gtk, runs
                                     appimagetool
  utils/appimage/AppRun            — launcher; sources plugin-gtk hook,
                                     sets PYTHONPATH, execs bundled
                                     python3 with bin/lutris

Design notes worth flagging for review:

* The bundled Python is the build image's /usr/bin/python3.10 plus its
  stdlib, with tkinter/test/idle stripped.

* PyGObject and dbus-python are taken from apt (python3-gi, python3-dbus)
  rather than pip — both have switched to meson-python build backends
  that drag in a sizeable toolchain, and the prebuilt packages link
  against exactly the libgirepository/libdbus we already bundle from
  the same distro.

* AppRun deliberately does NOT export LD_LIBRARY_PATH. linuxdeploy
  already sets $ORIGIN-relative RPATH on every binary it deploys, and
  exporting LD_LIBRARY_PATH would leak into every host subprocess —
  which surfaced immediately when flatpak crashed loading our bundled
  libssl 3.0 instead of the host's 3.4. The build script patchelf's
  the RPATH on the manually-copied _gi.so / _dbus*.so so they still
  find the bundled libgirepository without the env override.

* PATH is left intact apart from prepending $APPDIR/usr/bin, so host
  tools (xrandr, 7z, wine, flatpak) still win — that's the whole
  point of choosing AppImage over Flatpak here.

Output is dist/Lutris-<version>-x86_64.AppImage at ~83 MB. Smoke-tested
end-to-end on Fedora 43 Nobara: boots GUI, GPU detection, DB load,
service auth, GTK theme, and host subprocess invocations all work.

Refs #6724.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 17:15:30 -04:00

74 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# AppRun entry point for the Lutris AppImage.
#
# Two responsibilities:
# 1. Source linuxdeploy-plugin-gtk's hook so GI_TYPELIB_PATH, GDK_PIXBUF_*,
# XDG_DATA_DIRS, etc. point inside the AppDir.
# 2. Launch bin/lutris with the bundled Python interpreter, with PYTHONPATH
# pointing at the bundled site-packages.
#
# Lutris is intentionally NOT sandboxed: it must call host binaries (xrandr,
# 7z, fuser, wine, the user's installed runners), read the user's $HOME,
# and access mounted drives. We deliberately leave PATH intact so host
# tools win, and only prepend AppDir paths for libraries / typelibs.
set -e
HERE="$(dirname "$(readlink -f "${0}")")"
# linuxdeploy-plugin-gtk writes its environment setup into apprun-hooks/.
# Source every hook it dropped so we inherit the GTK/Pixbuf/typelib paths.
if [ -d "$HERE/apprun-hooks" ]; then
for hook in "$HERE"/apprun-hooks/*.sh; do
# shellcheck disable=SC1090
[ -r "$hook" ] && . "$hook"
done
fi
export APPDIR="$HERE"
export PATH="$HERE/usr/bin:$PATH"
# Intentionally do NOT export LD_LIBRARY_PATH. linuxdeploy already set
# RPATH=$ORIGIN/.. on every bundled binary, so they find their sibling libs
# without an env override. Exporting LD_LIBRARY_PATH here would leak into
# subprocesses — and Lutris launches host binaries constantly (xrandr, 7z,
# fuser, /usr/bin/flatpak, wine, etc.). Forcing them to load our older
# bundled libssl/libcrypto/libstdc++ breaks any host tool whose own deps
# were built against newer symbols. The cost of this discipline is that
# anything we copied into the AppDir manually (PyGObject's _gi.so,
# python3-dbus' bindings) must have its RPATH patched by the build script,
# since linuxdeploy only patches files it deploys itself.
unset LD_LIBRARY_PATH
# Python: find the bundled interpreter and site-packages. We accept any
# python3.X under usr/bin so this script doesn't need to be rev'd when the
# build image's Python minor version changes.
PYTHON_BIN=""
for cand in "$HERE"/usr/bin/python3.* "$HERE"/usr/bin/python3; do
if [ -x "$cand" ] && [ ! -L "$cand" ]; then
PYTHON_BIN="$cand"
break
fi
done
if [ -z "$PYTHON_BIN" ]; then
# Fallback: host python3. The AppImage is less portable this way, but
# at least it boots so the user sees a real error rather than nothing.
PYTHON_BIN="$(command -v python3 || true)"
fi
if [ -z "$PYTHON_BIN" ]; then
echo "Lutris AppImage: no python3 interpreter found in AppDir or host" >&2
exit 1
fi
PY_VER="$("$PYTHON_BIN" -c 'import sys;print("%d.%d"%sys.version_info[:2])')"
SITE="$HERE/usr/lib/python${PY_VER}/site-packages"
DIST="$HERE/usr/lib/python3/dist-packages"
export PYTHONPATH="$SITE:$DIST${PYTHONPATH:+:$PYTHONPATH}"
# bin/lutris strips /home paths from sys.path unless this is set, which
# would also drop our AppDir paths if they ever land under /home (e.g.
# AppImages extracted to ~/Applications). Setting this lets bin/lutris
# trust the PYTHONPATH we just built.
export LUTRIS_ALLOW_LOCAL_PYTHON_PACKAGES=1
exec "$PYTHON_BIN" "$HERE/usr/bin/lutris" "$@"