Skip to content

feat(bundler): Truly portable appimage (experimental) - #12491

Draft
FabianLars wants to merge 76 commits into
devfrom
feat/truly-portable-appimage
Draft

feat(bundler): Truly portable appimage (experimental)#12491
FabianLars wants to merge 76 commits into
devfrom
feat/truly-portable-appimage

Conversation

@FabianLars

@FabianLars FabianLars commented Jan 24, 2025

Copy link
Copy Markdown
Member

will add description later

Edit1: issue with bun's single exe output oven-sh/bun#28281 fixed with a hack: #12491 (comment)

fixes #10617 (xdg-open)
fixes #10078 (xdg-open)
fixes #6172 (xdg-open)
fixes #14208 (NO_STRIP)

@github-actions

github-actions Bot commented Jan 24, 2025

Copy link
Copy Markdown
Contributor

Package Changes Through 503bdbc

There are 14 changes which include tauri with minor, tauri-cli with minor, @tauri-apps/cli with minor, tauri-runtime with minor, tauri-runtime-wry with minor, tauri-utils with minor, tauri-bundler with minor, tauri-build with minor, tauri-macos-sign with minor, tauri-codegen with minor, tauri-macros with minor, tauri-plugin with minor, tauri-driver with minor, @tauri-apps/api with minor

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
@tauri-apps/api 2.11.1 2.12.0
tauri-utils 2.9.3 2.10.0
tauri-macos-sign 2.3.4 2.4.0
tauri-bundler 2.9.4 2.10.0
tauri-runtime 2.11.3 2.12.0
tauri-runtime-wry 2.11.4 2.12.0
tauri-codegen 2.6.3 2.7.0
tauri-macros 2.6.3 2.7.0
tauri-plugin 2.6.3 2.7.0
tauri-build 2.6.3 2.7.0
tauri 2.11.5 2.12.0
@tauri-apps/cli 2.11.4 2.12.0
tauri-cli 2.11.4 2.12.0
tauri-driver 2.0.6 2.1.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

Comment thread crates/tauri-bundler/src/bundle/linux/appimage/experimental.rs Outdated
Comment thread crates/tauri-bundler/src/bundle/linux/appimage/experimental.rs Outdated
Comment thread crates/tauri-bundler/src/bundle/linux/appimage/experimental.rs Outdated
Comment thread crates/tauri-bundler/src/bundle/linux/appimage/experimental.rs Outdated
@FabianLars

Copy link
Copy Markdown
Member Author

anyone else seeing this lately or just me?

Write lib.path: /home/fabianlars/_target/debug/bundle/appimage/ceftest.AppDir/lib/lib.path
ln: failed to create hard link '/home/fabianlars/_target/debug/bundle/appimage/ceftest.AppDir/lib/gstreamer-1.0/gst-completion-helper': File exists
       Error [tauri_cli] failed to bundle project: quick-sharun command failed to run.: `failed to run /bin/sh`

Pin the quick-sharun tooling to a reviewed revision and verify its
checksum. It was re-downloaded from the tip of a third-party branch on
every build, so an upstream push could silently change or break a
release build, and offline builds were impossible. The revision is part
of the cached file name, so a build only hits the network the first time
it sees a new pin. TAURI_BUNDLER_QUICK_SHARUN_SCRIPT points the bundler
at a local script for testing the tooling itself. The anylinux helper
library the script compiles into the bundle is pinned to the same
revision.

Deploy the libraries injected through appimage.files. The CLI adds
libappindicator there when the tray icon feature is enabled, but
copy_custom_files puts it under the AppDir's usr/lib, which sharun never
looks at, so the tray library was missing from the bundle.

Stream the tooling output instead of capturing it. On failure the user
saw "failed to run /bin/sh" with the actual diagnostics discarded, which
is how a missing patchelf presented itself. Also fail with a clear error
when the run succeeds but no AppImage was produced.

Return the .zsync file next to the AppImage. appimagetool writes one
whenever update information is embedded, which it also guesses from
GITHUB_REPOSITORY, and it is part of the release.

Honor bundleMediaFramework by forcing gstreamer deployment. The option
existed but did nothing on this code path.

Reject cross-architecture builds. The deployment collects libraries from
the build system, so building for a foreign target used to produce an
AppImage named for the target and filled with the host's libraries.

Pass the script to sh as an argument rather than building a shell
command string, so paths containing spaces survive, and return an error
instead of panicking when no square icon is available.
Remove the squashfs option. The appimagetool the tooling uses only
produces DwarFS images and exposes no squashfs switch, so the option was
read from the config but never had any effect.

Correct the useNewFormat documentation. It claimed the format can only
be built on Debian/Ubuntu 24.04 and above, while Arch is the recommended
build host and Ubuntu 24.04 is known to lose hardware acceleration on
Wayland with the proprietary NVIDIA driver. It now also states that the
bundler runs the application once during bundling to discover the
libraries it loads at runtime, and that it cannot cross-compile.

Drop the zsyncmake requirement from the updateInformation docs, since
appimagetool generates the zsync file itself, and document that the
UPINFO environment variable takes precedence over the config value.

Point the deprecation warning at the config option instead of a docs
anchor that does not exist yet.
…nging

quick-sharun runs each binary it deploys for a few seconds to discover
which libraries get dlopened, then kills it with a process-group signal.
That signal only reaches the process if the shell put it in its own
process group, which is what `set -m` does.

dash does not create the group when there is no controlling terminal.
On Debian and Ubuntu, where /bin/sh is dash, every terminal-less build,
which is every CI run, therefore hangs forever on the first traced
process that does not exit by itself. On Ubuntu that is webkit2gtk's
MiniBrowser, which the tooling deploys and traces. bash creates the
group with or without a terminal, which is why the same build works on
Arch and Fedora, where /bin/sh is bash.

Reproduced in a container: an ubuntu:24.04 build stopped with
"can't access tty; job control turned off" and sat on MiniBrowser until
it was killed. With bash, the same build traces MiniBrowser and finishes.
Verified on ubuntu:22.04, ubuntu:24.04 and ubuntu:26.04.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quick-sharun silently skips wrapping the application binary when the
AppDir path contains whitespace. It still exits successfully, so the
build looks fine and the AppImage dies on the user's machine with
"error while loading shared libraries: libgdk-3.so.0".

Isolated with two otherwise identical runs: with a space in the path the
binary stayed unwrapped in bin/ and shared/bin/ was never created, and
without one it was hardlinked to sharun as expected. A space in any
parent directory triggers it too.

Product names with spaces are common, tauri's own api example is called
"Tauri API", so derive the AppDir, desktop and icon file names by
replacing whitespace. The product name still reaches users through the
desktop entry's Name and the AppImage file name, and the resource
directory keeps the product name because the runtime resolves resources
by it.

The project path is not ours to rename, so a remaining space in the path
is now an error that says what to do rather than a bundle that fails to
start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mage

Both the AppImageKit runtime and uruntime extract to
$TMPDIR/appimage_extracted_* with --appimage-extract-and-run, which the
".mount_" only check flagged as a possible security issue on every start.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The sharun bundler returns the .zsync next to the AppImage so it can be
published; it is not an update package and must not get a .sig.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ment the tooling switches

The desktop entry refers to the icon by the main binary name and the
AppImage spec expects AppDir/<Icon>.png, which is what desktop integration
tools look for. Also state that sidecars are launched during bundling and
that quick-sharun's environment variables are forwarded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@lucasfernog

lucasfernog commented Sep 6, 2026

Copy link
Copy Markdown
Member

I tried to run some LLM-based tests on VMs and pushed a few fixes, let me know if you spot any issue
here's the report too: https://claude.ai/code/artifact/bd23c67a-8133-415a-be32-e5b600b66558

(i also have a folder with a bunch of logs and screenshots if needed, but appimage ran on a bunch of different systems

chrox added a commit to readest/readest that referenced this pull request Sep 6, 2026
The CEF AppImage carries the build host's glibc, so every library it does
not bundle is resolved from the host at run time and links against a
glibc the bundled one cannot satisfy on any distro newer than the
builder. Chromium dlopens libsoftokn3.so, gets the host copy and aborts
on first use (FATAL crypto/nss_util.cc, nss_error=-5925), so the nightly
AppImage dies ~1.5s after launch on anything past the builder's
glibc 2.35 — Ubuntu 24.04+, Fedora 42, Arch. ANGLE loses libGL.so.1 the
same way, which is why the GPU process exits during startup.

quick-sharun's DEPLOY_CHROMIUM rules already list these modules, but the
script cli-cef 3.0.0-alpha.26 downloads is a fork ~1100 lines behind
pkgforge upstream that expands its deployment array without `eval`: every
path keeps its literal quotes, so all DEPLOY_* extras are silently
skipped and only ldd-derived libraries reach the bundle. Hand the modules
over through appimage.files instead — the /usr/lib channel the bundler
passes to quick-sharun as plain ELF arguments — and flatten Debian's
<triple>/nss/ first so they land next to the libnss3.so that loads them.
libsqlite3 follows as libsoftokn3's own dependency. A post-build check
fails the leg when one is missing, because the tooling skips what it
cannot find without erroring.

tauri-apps/tauri#12491 replaces that fork with a pinned, checksummed
upstream revision and fixes the hang the STRACE_MODE pin works around;
the comments spell out what to drop once cli-cef carries it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Samueru-sama

Copy link
Copy Markdown

anyone else seeing this lately or just me?

Write lib.path: /home/fabianlars/_target/debug/bundle/appimage/ceftest.AppDir/lib/lib.path
ln: failed to create hard link '/home/fabianlars/_target/debug/bundle/appimage/ceftest.AppDir/lib/gstreamer-1.0/gst-completion-helper': File exists
       Error [tauri_cli] failed to bundle project: quick-sharun command failed to run.: `failed to run /bin/sh`

Ubuntu 25 by chance?

likely this bug

Comment on lines +224 to +238
// quick-sharun runs each binary it deploys for a few seconds to see which
// libraries get dlopened, then kills it with a process-group signal. That
// only reaches the process if the shell put it in its own group, which is
// what `set -m` is for. dash does not create the group when there is no
// controlling terminal, so on Debian and Ubuntu, where /bin/sh is dash,
// every terminal-less build - which is every CI run - hangs forever on the
// first traced process that does not exit by itself. bash creates the group
// either way, so prefer it and fall back to sh where it is missing.
let shell = which::which("bash")
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| "/bin/sh".into());

// Passing the script to the shell as an argument rather than building a
// `-c` string keeps paths containing spaces intact.
let mut cmd = Command::new(shell);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasfernog This shouldn't be needed anymore: pkgforge-dev/Anylinux-AppImages#853

@FabianLars

FabianLars commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

Ubuntu 25 by chance?
likely this bug

Nope that happened on my arch build machine

Edit: happened when building tauri's webkitgtk version, cef version worked fine. Just mentioned because the project in the error is named ceftest

chrox added a commit to readest/readest that referenced this pull request Sep 7, 2026
* fix(ci): bundle the audio libraries the CEF AppImage dlopens

TTS runs to completion in the AppImage but nothing is audible: Chromium
reaches its audio backend through dlopen, and the bundle carries none of
it. libpulse.so.0 resolves to the host copy and fails against the
bundled glibc 2.35 (host libpulsecommon needs GLIBC_2.38), and the ALSA
fallback dies the same way — its `default` PCM is a config hook that
loads the host's libasound_module_conf_pulse.so, so snd_pcm_open()
returns "Unknown PCM default" and only a raw hw: device opens, which
Chromium will not use. libspeechd.so.2 fails identically, leaving the
Web Speech engine with no system voices.

Stage all three through the same appimage.files channel as the NSS
modules (#6101). quick-sharun then pulls the rest of the closure
(libsndfile, FLAC, vorbis, opus, ogg, asyncns...) from the runner by ldd.
libpulse keeps its private half in <triple>/pulseaudio/ and finds it by
RUNPATH, which points at the host directory: verified that the private
lib is deployed to lib/pulseaudio/ and that lib.path gains `+/pulseaudio`
so sharun's library path wins over the RUNPATH, and the guard now checks
both.

Verified against the shipped 0.12.6-2026090706 bundle on glibc 2.41: in
its runtime environment dlopen of libpulse/libpipewire/libspeechd fails
and ALSA has no usable PCM, while staging these libraries makes dlopen
succeed and a tone play through PipeWire's PulseAudio server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(linux): stop the AppImage asking for a root password (#6110)

Every new AppImage pops a modal on first launch and asks for the root
password through pkexec. That is the bundler's fix-namespaces.hook: on
distros that restrict unprivileged user namespaces (Ubuntu 24.04+) it
offers to install an AppArmor profile granting `userns`, and since the
profile it writes is keyed to the AppImage's exact path, every nightly
asks again.

Readest never needs that capability. tauri-runtime-cef sets
CefSettings.no_sandbox unless its `sandbox` feature is enabled, and this
build compiles it without: every Chromium child of the shipped AppImage
runs `--type=zygote --no-sandbox`, and the app starts fine on a machine
where `unshare -Urm` is denied and no profile is installed. The hook is
boilerplate for Chromium apps that do sandbox; here it buys a root
prompt and nothing else.

Ship an AppRun hook that sorts ahead of it and stubs the two helpers it
prompts through — both come from AppRun.lib and have no other callers in
the bundle, so the rest of that hook still runs, it just cannot reach
the user. Verified against the shipped 0.12.6-2026090706 bundle: stock
hooks block startup on the dialog (three lines of log, app never comes
up), with this hook the app starts normally and no zenity or pkexec
process appears. Also verified in a jammy container that a file shipped
through appimage.files survives quick-sharun and lands in bin/ ahead of
the bundler's own hooks.

The entry lives in tauri.conf.json rather than the workflows so local
Linux builds get it too; the CI staging step now merges into that map
instead of replacing it.

This leaves the AppImage exactly as sandboxed as it is today, which is
also what the WebKitGTK builds before it were — Tauri never enabled
WebKit's sandbox either. Sandboxing the renderer on Linux is a separate
change: it needs the CEF `sandbox` feature plus /etc/apparmor.d/readest
shipped in deb/rpm, and this hook would then have to go.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: pin down the quick-sharun regression behind the AppImage staging

The staging comment blamed drift — a fork "~1100 lines behind upstream"
that "expands its deployment array without eval". The gap is real but
not the cause. The fork's HEAD, FabianLars/Anylinux-AppImages@3e280d1b
"fix handling of spaces in input paths", is a deliberate one-line change:

    eval set -- "$TO_DEPLOY_ARRAY" "$@"   ->   set -- $TO_DEPLOY_ARRAY "$@"

Upstream's line really does split positional paths on spaces, and tauri
names the AppDir after the product, so the change fixed something. But
the array entries come single-quoted from _save_array, so without the
eval each keeps its literal quotes, fails `[ -f ]`, and every DEPLOY_*
extra is silently skipped. Measured in a dash one-liner: upstream splits
"My App/libfoo.so" into two arguments, the fork keeps the array entry as
'/path/libextra.so' quotes and all, and quoting the positional args
through the same helper — eval set -- "$TO_DEPLOY_ARRAY"
"$(_save_array "$@")" — keeps both.

Record that, and the two things upstream review pointed out: pulse is
already implied by DEPLOY_CHROMIUM through DEPLOY_PIPEWIRE, so the
libpulse entry turns redundant the moment the fork's main is fixed (the
CLI re-fetches it on every build), and libpipewire is not needed for
audio at all, only for the screen-share portal, so it stays out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci(linux): build the CEF tauri CLI from readest/tauri with pkgforge pinned

The published @tauri-apps/cli-cef (3.0.0-alpha.26) bundles the AppImage
with a fork of quick-sharun whose deployment array is broken, and it
re-downloads that fork on every build with no way to point it anywhere
else - no env var, no mirror (TAURI_BUNDLER_TOOLS_GITHUB_MIRROR only
rewrites github.com URLs), and no cache seeding, since the guard that
made the pre-#6074 seed work is commented out.

Build the CLI on the Linux legs from readest/tauri@c62e9f90 instead, the
way the workflow did until #6074: there sharun_cef.rs fetches
pkgforge-dev/Anylinux-AppImages at facb95e8 verified by SHA-256 (the
revision tauri-apps/tauri#12491 pins), runs the tooling with bash and
streamed output, passes the deploy list as arguments, keeps whitespace
out of the AppDir name, and adds no fix-namespaces hook. Pinned to a
rev so a release cannot change behaviour under us; rust-cache keeps
~/.cargo/bin and a marker file skips the rebuild while the pin holds.
scripts/tauri.mjs runs `cargo tauri` when TAURI_CEF_CARGO=1, with the
same command line as the npm CLI.

STRACE_MODE stays off in this step; the hang it works around is fixed
by the bash/streamed-output change, but lifting it is its own change.
The hand-staged NSS and audio entries stay too, until a nightly built
this way confirms the tooling deploys them itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci: trim the AppImage comments to the essentials

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet