浏览文档

Tarballs and zips

A portable tarball is the easiest payload: one directory holding the app and the libraries it bundles, made to run from wherever it is unpacked. apply_extra.sh unpacks it, renames the directory to a fixed path, and the wrapper runs the binary inside. A zip works the same way; bsdtar reads both.

Start from one of these:

  • Grab: a GTK 4 app shipped as an install tree, on the GNOME runtime.
  • Electerm: an Electron app with a version-stamped top directory, on the freedesktop runtime with the Electron base app.
  • Impasto: a zip.
For agents

apply_extra.sh

#!/bin/sh
set -eu

# Runs offline at install time inside org.gnome.Platform. The tarball is the
# official portable build: one versioned top directory with the executable,
# its bundled libraries and plugins. Rename it to a stable path so the
# wrapper can exec it across updates.

extra_root="${EXTRA_ROOT:-/app/extra}"
cd "$extra_root"

[ -f app.tar.gz ] || { echo "missing extra-data: app.tar.gz" >&2; exit 1; }

rm -rf stage app
mkdir stage
# --no-same-owner: a system-wide install runs this as root with every
# capability dropped, and restoring the archive's uid/gid would abort it.
bsdtar --no-same-owner -xf app.tar.gz -C stage

top="$(find stage -mindepth 1 -maxdepth 1 -type d)"
[ "$(printf '%s\n' "$top" | wc -l)" -eq 1 ] || { echo "expected one top-level directory" >&2; exit 1; }
mv "$top" app
[ -x app/example ] || { echo "example binary not found in the tarball" >&2; exit 1; }

rm -rf stage app.tar.gz
  • Unpack into a staging directory, then move the one tree you want into place. Don’t trust the archive’s top-level name: it usually carries the version, and some archives have no top directory at all. Test for exactly what you expect and fail if it isn’t there.
  • Never hardcode a name upstream can change between releases (the version in the directory, sometimes the launcher’s name). Updates are merged without a human; read the name from the archive, or match it with a pattern and check there is exactly one (Electerm matches electerm-*-linux-*).
  • rm -rf the old copy first. The script runs again on every update in the same /app/extra.
  • Zips lose nothing with bsdtar, including Unix permissions, as long as upstream zipped on Unix. A zip made on Windows has no executable bits; then chmod +x the binaries you know about.
  • Other compressions (.tar.xz, .tar.zst, .tar.bz2) need no flag; bsdtar detects them.

The wrapper

#!/bin/sh
set -eu
# Bundled plugins and libraries stay inside the portable tree.
export LD_LIBRARY_PATH="/app/extra/app/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
cd /app/extra/app
exec /app/extra/app/example "$@"

Only add LD_LIBRARY_PATH or a cd when the app needs them: most portable builds find their own libraries through an $ORIGIN RUNPATH, and an LD_LIBRARY_PATH that points into the bundle also applies to every program the app starts.

Electron tarballs and zips

  • Use base: org.electronjs.Electron2.BaseApp (same version as the freedesktop runtime) and start the app with exec zypak-wrapper /app/extra/app/<launcher> --ozone-platform-hint=auto "$@". zypak puts Chromium’s sandbox on top of Flatpak’s, so the app keeps it; never use --no-sandbox for an Electron app.
  • unset ELECTRON_RUN_AS_NODE in the wrapper. If it leaks in from the user’s terminal, the binary starts as Node instead of the app.
  • chrome-sandbox is unused under zypak. Leave it, but if the archive records it setuid, clear the bit: chmod -R a-s app.
  • The window class an Electron app sets is its product name, not the binary or the app id. Measure it before writing StartupWMClass in the desktop entry.

Checks before the PR

./scripts/check-apply-extra.sh <app-id>     # unpacks as a system install would
./scripts/publish.sh --verify <app-id>      # builds and installs it

Then run the app and use its core feature; see submitting an app.

在 GitHub 上编辑此页