AppImages
A type-2 AppImage is a small ELF program with a SquashFS image appended to it.
FlatPark never runs it and needs no FUSE: apply_extra.sh finds where the
SquashFS starts and unpacks it, leaving the AppImage’s own directory tree
(the AppDir) in /app/extra. The wrapper then starts the app from that tree.
The runtime has no SquashFS tool, so every AppImage package downloads a second
small file, the appimage-tools stack from
flatpark/prebuilt. It holds two
programs: appimage-offset, which prints where the SquashFS starts, and
unsquashfs.
An AppImage brings its own libraries, so it is the format to reach for when
that is what the app needs, above all a Qt app that carries its own Qt (see
choosing a format). Otherwise a tarball or .deb
of the same release is simpler.
Start from one of these:
- Mark Shot: Qt 6 with its own Qt, freedesktop runtime, plus Debian-pinned clipboard tools.
- Compass: a
self-contained app started through the AppDir’s own
AppRun. - AlterSend: an Electron app with the Electron base app and zypak.
For agents
The manifest
Pin the tools outside the managed block, so the updater, which rewrites that block from the resolver’s output, leaves them alone:
sources:
- type: extra-data
filename: appimage-tools.tar.xz
only-arches: [x86_64]
url: https://github.com/flatpark/prebuilt/releases/download/appimage-tools-v2/appimage-tools-appimage-tools-v2-fd-26.08-x86_64.tar.xz
sha256: 01ff0b363031060c3d9877bc61b026a1a7422308fd9e5cfda53cc24971b12304
size: 57220
# BEGIN MANAGED EXTRA-DATA
- type: extra-data
filename: app.AppImage
# …
# END MANAGED EXTRA-DATA
Copy the current pin from an existing AppImage package; the one above may be
out of date. The tools are extra-data, not a module built into /app, because
a system-wide install runs apply_extra with only the runtime’s /usr and
/app/extra visible: a tool in /app/bin works on a --user install and
fails on a system one.
apply_extra.sh
#!/bin/sh
set -eu
extra_root="${EXTRA_ROOT:-/app/extra}"
cd "$extra_root"
for f in app.AppImage appimage-tools.tar.xz; do
[ -f "$f" ] || { echo "missing extra-data: $f" >&2; exit 1; }
done
rm -rf app squashfs-root appimage-tools
bsdtar --no-same-owner -xf appimage-tools.tar.xz
tools=appimage-tools/bin
# Find where the appended SquashFS starts, then unpack from there.
offset="$("$tools/appimage-offset" app.AppImage)"
case "$offset" in
''|*[!0-9]*) echo "appimage-offset did not return a number: '$offset'" >&2; exit 1 ;;
esac
"$tools/unsquashfs" -q -no-xattrs -d squashfs-root -o "$offset" app.AppImage >/dev/null
[ -x squashfs-root/usr/bin/example ] || { echo "no usr/bin/example in the AppImage" >&2; exit 1; }
mv squashfs-root app
rm -rf appimage-tools app.AppImage appimage-tools.tar.xz
-no-xattrs: the sandbox can’t set them, and the app doesn’t need them.- Keep the AppDir’s tree whole unless you know the app finds its files
another way. Bundled binaries usually locate their libraries through an
$ORIGIN/../libRUNPATH and their plugins through aqt.confnext to them, which only work whileusr/bin,usr/libandusr/pluginsstay siblings. - Missing pieces go next to the tree, not into it. Mark Shot’s app looks for
plugins at a path the bundle doesn’t use, so
apply_extra.shadds a symlink beside the vendor’s files rather than moving them.
Starting the app
Look at AppRun and the AppDir’s .desktop file first; they show how upstream
starts it.
AppRunis a symlink to the binary: run the binary directly (OpenCadStudio resolves the name through the link, so a renamed binary still works).AppRunis a script that only sets variables and starts the app:execit (Compass), or set the same variables in the wrapper and run the binary (Mark Shot).- A linuxdeploy AppDir with
apprun-hooks/: the GTK hook points GTK, GdkPixbuf, GSettings and GObject introspection at the bundled GTK, and forcesGDK_BACKEND=x11. If the app runs on the runtime’s GTK, put it on the GNOME runtime, skipAppRun, and set only the app’s own variables (Tavern, a Python and GTK 4 app). If it needs its bundled GTK,exec AppRunand grant--socket=x11; the bare binary runs with half its GTK missing. - Electron: as for tarballs: Electron
base app,
zypak-wrapper,unset ELECTRON_RUN_AS_NODE. Read the launcher name from the AppDir rather than hardcoding it (AlterSend).
AppRun is the format’s fixed entry point, so naming it is safe.
Don’t put the bundle’s usr/lib on LD_LIBRARY_PATH unless upstream’s own
launcher does. AppImages often carry their own copies of libraries the runtime
also has, and a global path makes every child process load them too.
Sandbox notes
- Qt platform plugins. A bundled Qt often has only
xcb, or the Wayland plugin withoutlibQt5WaylandClient. Then:--socket=x11,--unset-env=QT_QPA_PLATFORM, andQT_QPA_PLATFORM="${QT_QPA_PLATFORM:-xcb}"in the wrapper. More in choosing a format. - A bundled Qt may have no portal platform theme, so file dialogs
are Qt’s own and only see what the sandbox grants. Grant the directories the
app saves to by default (
xdg-pictures,xdg-documents), nothome. --persist=.diris ignored whenhomeis granted too: the broader mount hides it. For an app that writes a~/.dotdir, pick one:--persist=.dirwith onlyxdg-*grants, or--filesystem=~/.dir:create.- Testing a rebuild:
flatpak install --reinstallfrom a localfile://remote can keep the old commit. Uninstall, then install.