Skip to content

fix(build): make AppImages installable via Gear Lever, fix bundling on Arch - #304

Open
YahyaZekry wants to merge 4 commits into
tonhowtf:mainfrom
YahyaZekry:fix/appimage-gearlever
Open

fix(build): make AppImages installable via Gear Lever, fix bundling on Arch#304
YahyaZekry wants to merge 4 commits into
tonhowtf:mainfrom
YahyaZekry:fix/appimage-gearlever

Conversation

@YahyaZekry

@YahyaZekry YahyaZekry commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fix(build): Make AppImages installable via Gear Lever, fix bundling on Arch

Problem

AppImages built from this repo fail to install in Gear Lever (and possibly other AppImage managers), and tauri build --bundles appimage no longer completes on Arch-based systems at all.

1. Absolute .desktop / .DirIcon symlinks → Gear Lever crash

tauri-bundler ≤ 2.10 created absolute symlinks in the AppDir:

omniget.desktop -> /home/user/.../bundle/appimage/omniget.AppDir/usr/share/applications/omniget.desktop
.DirIcon        -> /home/user/.../bundle/appimage/omniget.AppDir/omniget.png

Gear Lever extracts bundles with 7z, which rewrites absolute symlinks as paths inside its temp extraction folder — they become dangling, no desktop entry is found, and install crashes:

File ".../AppImageProvider.py", line 884, in _get_app_version
    version = desktop_entry.get('X-AppImage-Version', '')
AttributeError: 'NoneType' object has no attribute 'get'

Fixed upstream in tauri-apps/tauri#15596 — this PR bumps @tauri-apps/cli to ^2.11.4 so both symlinks are relative again.

2. linuxdeploy strip fails on modern ELF → bundling aborts

linuxdeploy's bundled strip predates .relr.dyn (DT_RELR) relocations used by current glibc/webkit2gtk packages. Every strip call fails (unknown type [0x13] section '.relr.dyn'), and since any strip failure sets success = false, bundling aborts. Refreshing cached linuxdeploy doesn't help — upstream still ships old binutils.

Fix: set NO_STRIP=1 (honored by linuxdeploy) in release.yml, gated on the ubuntu runners to match LDAI_UPDATE_INFORMATION directly above it.

3. GTK plugin fails on Arch → bundling aborts

Tauri's vendored linuxdeploy-plugin-gtk.sh runs under set -e and assumes /usr/lib/gdk-pixbuf-2.0/2.10.0 exists (per pkg-config). Arch ships gdk-pixbuf with loaders built in but keeps the stale pkg-config path, so copy_tree fails, then the loaders.cache write fails:

[gtk/stderr] cp: cannot stat '/usr/lib/gdk-pixbuf-2.0/2.10.0': No such file or directory
ERROR: Failed to run plugin: gtk (exit code: 1)

Fix: vendor a patched copy at scripts/linux/linuxdeploy-plugin-gtk.sh with three changes:

  1. copy_tree skips sources that don't exist (with a warning) instead of aborting.
  2. mkdir -p before writing loaders.cache.
  3. The sed -i on loaders.cache moved inside the -f guard. Upstream ran it unconditionally, so a missing cache tripped set -e immediately after warning that it was only a warning.

The file carries a header naming the source repo, the commit it was copied at (b5eb8d05, the exact URL Tauri fetches), the MIT notice, the three local hunks, and how to re-sync. It is otherwise byte-identical to upstream.

4. Missing updater signing key → build exits 1

createUpdaterArtifacts is true in the committed config, so after successfully bundling the AppImage the build tried to sign it and aborted:

A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.

The release key isn't available outside CI, so building an AppImage from source always ended in a non-zero exit.

How the local build is wired

pnpm tauri:appimage runs scripts/linux/appimage.sh, which sets NO_STRIP=1, turns off createUpdaterArtifacts, and hands the bundler the patched plugin.

The plugin goes into the project's src-tauri/target/.tauri/, not the shared ~/.cache/tauri/. This matters: the bundler only downloads a plugin when the file is missing and never checksums it, so dropping our patched copy in the user cache would pin it for every Tauri project on that machine, permanently, including after a Tauri release fixes this upstream. Setting bundle.useLocalToolsDir moves the whole tool cache into the cargo target directory, so the build touches nothing outside the repo and cargo clean undoes it. The script echoes where it installed the plugin.

README.md documents pnpm tauri:appimage in the same section as the existing createUpdaterArtifacts escape hatch from #298, rather than in a second place that would drift.

Desktop entry

src-tauri/desktop.template is wired via bundle.linux.deb/rpm.desktopTemplate; the AppImage bundler reuses the deb data generator, so deb/rpm/AppImage stay consistent.

The generated entry previously had an empty Categories=. It now matches flatpak/wtf.tonho.omniget.desktopCategories=Network;FileTransfer;Utility; plus GenericName and Keywords, which improve app-menu search. Those three lines are hardcoded rather than derived from bundle.category, because settings.app_category() maps through a fixed AppCategory table that can't express a multi-value list; the template says so, so nobody "fixes" it later.

Verification

On Arch (glibc 2.42, webkit2gtk 4.1), building the AppImage end to end:

  • pnpm tauri:appimage completes with exit code 0 ✅ (previously failed on strip, then the gtk plugin, then the missing signing key)
  • AppDir symlinks are relative ✅
    .DirIcon -> omniget.png
    omniget.desktop -> usr/share/applications/omniget.desktop
    
  • Replicated Gear Lever's 7z extraction of the new AppImage: the root desktop file resolves and is detected as application/x-desktop
  • Gear Lever installs the resulting AppImage successfully ✅
  • Generated entry has the categories above and keeps MimeType=x-scheme-handler/omniget; desktop-file-validate passes ✅
  • All five bundler tools land in src-tauri/target/.tauri/; the build writes nothing to ~/.cache/tauri/

Two notes on the verification, since an earlier revision of this PR got one of them wrong:

  • The desktop template was not actually taking effect until now. bundle already had a "linux" key for the deb libasound2 depends, and this branch added a second one. JSON keeps the last of a duplicate key, so desktopTemplate was dropped on the floor and the AppImage kept shipping the stock Tauri entry with an empty Categories=. Merging the two blocks fixes it, and the categories above were then read back out of the built AppDir rather than assumed.
  • The local build was run with --no-default-features. The linux-capture feature's pipewire bindgen needs libclang, which isn't installed on this machine. That's unrelated to this PR, but it means the default-feature build path isn't covered by the run above.

Local-only files (scripts/linux/, template) are safe for CI: tauri-action downloads its own plugin copy, and Ubuntu runners have real gdk-pixbuf loader dirs.

@tonhowtf

Copy link
Copy Markdown
Owner

Thanks for this — genuinely good work. I verified each claim against upstream rather than taking the description at face value, and they all hold up: linuxdeploy.rs:174-180 really does build absolute symlinks at 2.10.0 and relative ones at 2.11.4, and scripts/linux/linuxdeploy-plugin-gtk.sh is byte-identical to tauri-apps/linuxdeploy-plugin-gtk@master (the exact URL tauri fetches at linuxdeploy.rs:241) apart from your patches. The cache override works because linuxdeploy.rs:239 only downloads when the file is missing and never checksums it. I also spotted a third fix you didn't mention: moving the sed -i on loaders.cache inside the -f guard, where upstream ran it unconditionally and would trip set -e. That one's worth calling out in the description.

Two things I'd like fixed in this PR, then a few follow-ups.

1. License attribution on the vendored script (scripts/linux/linuxdeploy-plugin-gtk.sh:1). The file is a derivative of linuxdeploy/linuxdeploy-plugin-gtk via the tauri-apps fork, which is MIT. Vendoring MIT into a GPL-3 repo is fine, but the file currently carries no provenance at all. Please add a header with the upstream URL, the commit or date you copied from, the MIT notice, and a short list of the three local modifications — otherwise the next person to touch it can't tell what's ours and can't rebase it onto a newer upstream.

2. ~/.cache/tauri/ is a global, shared cache (package.json:14). Because tauri only downloads the plugin when the file is absent, a single pnpm tauri:appimage run permanently pins our patched copy for every Tauri project on that machine — including after a Tauri release fixes this upstream. That's a surprising thing for a project-local build script to do to a contributor's system. Options, in order of preference: keep the copy in a project-local dir if we can point Tauri at it, or at minimum have the script echo what it just installed and document the undo (rm ~/.cache/tauri/linuxdeploy-plugin-gtk.sh).

3. Docs (README.md:381). That section already documents pnpm tauri build --config '{"bundle":{"createUpdaterArtifacts":false}}' as the from-source escape hatch (added in #298). Your item 4 is the same problem, so pnpm tauri:appimage should be mentioned right there as the Linux/AppImage path — otherwise we have two answers to one question and they'll drift.

Follow-ups, no need to block on these:

  • Categories diverge from the Flatpak entry. flatpak/wtf.tonho.omniget.desktop:9 has Categories=Network;FileTransfer;Utility; plus GenericName=Download Manager and a Keywords= line; src-tauri/desktop.template:2 emits Network;FileTransfer; and neither of the other two. Both files are hand-maintained and now disagree. Aligning Categories, and adding GenericName/Keywords (they improve app-menu search), would be a nice touch. For what it's worth I think hardcoding rather than setting bundle.category is the right call here — settings.app_category() maps through a fixed AppCategory table that can't express Network;FileTransfer; — but that reasoning deserves a comment in the template so nobody "fixes" it later.
  • Nothing in CI exercises any of this. Every job in ci.yml uses cargo build, including smoke; no job runs tauri build --bundles. Since tauri-action invokes our project-local pnpm tauri, the CLI bump also changes the bundler for the Windows NSIS/MSI and macOS DMG artifacts, and none of that is validated until the next v*.*.* tag.
  • NO_STRIP: '1' is set for the whole matrix, including macOS and Windows where nothing reads it. Harmless, but LDAI_UPDATE_INFORMATION directly above it is already gated on ubuntu — matching that would make it self-documenting.
  • AppStream metadata. Your scope is correct: Gear Lever's crash was the dangling desktop symlink, and it doesn't require AppStream, so this PR is sufficient. A future improvement is shipping flatpak/wtf.tonho.omniget.metainfo.xml inside the AppImage at usr/share/metainfo/ via bundle.linux.appimage.files. Note it can't be copied verbatim: that file's <launchable type="desktop-id"> is wtf.tonho.omniget.desktop, while the bundled entry is named omniget.desktop (from productName), so the launchable would dangle and appstreamcli validate would object.

On timing: I'm cutting a release right now that carries a large new feature, and this PR changes the bundler for every platform (2.10.0 → 2.11.4) with nothing in CI validating bundle output. I don't want to move both at once, so I'd rather land this immediately after that release goes out, with the two items above fixed. Nothing about the change itself is blocking it.

Nice bit of debugging on the .relr.dyn strip failure in particular — that one is not obvious from the error message.

YahyaZekry and others added 4 commits August 30, 2026 08:27
…n Arch

- bump @tauri-apps/cli to ^2.11.4: tauri-bundler <=2.10 created absolute
  .desktop/.DirIcon symlinks in the AppDir; Gear Lever extracts with 7z,
  which turns absolute symlinks into dangling ones, finds no desktop
  entry and crashes on install (upstream fix: tauri#15596)
- add src-tauri/desktop.template (Categories=Network;FileTransfer;) and
  wire it for deb/rpm so the AppImage desktop entry is no longer missing
  Categories (AppImage bundling reuses the deb data generator)
- set NO_STRIP=1 when running linuxdeploy: its bundled strip predates
  .relr.dyn relocations and any strip failure aborts bundling on modern
  toolchains; also set it in release.yml for CI
- vendor a patched linuxdeploy-plugin-gtk.sh: skip copy_tree sources
  that don't exist and mkdir -p before writing loaders.cache — Arch's
  gdk-pixbuf pkg-config paths are stale (loaders built in), which made
  the GTK plugin exit 1 under set -e. Synced into ~/.cache/tauri/ by
  the new pnpm tauri:appimage script
…g key

`createUpdaterArtifacts` is true in the committed config, so `pnpm
tauri:appimage` bundled the AppImage and then exited 1 signing it —
the release signing key isn't available outside CI. Disable updater
artifacts for the local AppImage script only; CI is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4caqH8dy3gKLKSoJgpv2W
Review feedback on tonhowtf#304.

The vendored linuxdeploy-plugin-gtk.sh carried no provenance, so nobody could
tell what was ours or rebase it onto a newer upstream. It now names the source
repo, the commit it was copied at, the MIT notice, and the three local hunks.

Copying it into ~/.cache/tauri also pinned our patched copy for every Tauri
project on the machine, forever, because the bundler only downloads the plugin
when the file is missing. Turning on bundle.useLocalToolsDir moves the tool
cache into src-tauri/target/.tauri instead, so the build touches nothing
outside the repo and `cargo clean` undoes it. The command grew past what
belongs in a package.json string, so it lives in scripts/linux/appimage.sh now
and says which plugin it installed.

README documents `pnpm tauri:appimage` next to the existing from-source build
flag rather than in a second place that would drift from it.

Also gates NO_STRIP on ubuntu, matching LDAI_UPDATE_INFORMATION right above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bundle` already had a "linux" key for the deb `libasound2` depends, and this
branch added a second one. JSON keeps the last of a duplicate key, so
`desktopTemplate` was dropped on the floor: the AppImage shipped the stock
Tauri entry with an empty `Categories=`. Merging the two blocks makes the
template take effect. Verified by building the AppImage and reading the entry
out of the AppDir.

While in there, the template now matches flatpak/wtf.tonho.omniget.desktop --
same Categories, plus the GenericName and Keywords that make the app findable
in a menu search -- and says why those three lines are hardcoded instead of
coming from `bundle.category`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@YahyaZekry
YahyaZekry force-pushed the fix/appimage-gearlever branch from 4934b7a to 47d5cb3 Compare August 30, 2026 10:59

@tonhowtf tonhowtf left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Verified end to end: the Gear Lever crash is the absolute omniget.desktop symlink that tauri-bundler ≤ 2.10 wrote (7z mangles it, _get_app_version gets None), and tauri-cli 2.11.4 fixes it in linuxdeploy.rs by symlinking relatively — so the @tauri-apps/cli bump is the whole Gear Lever fix and no AppStream metadata is needed. The vendored linuxdeploy-plugin-gtk.sh diffs against upstream b5eb8d05 (exactly what the bundler downloads) in only the three documented hunks, and each one matches a real Arch failure (.relr.dyn vs bundled binutils, stale gdk_pixbuf_binarydir, unconditional sed -i on a missing loaders.cache). useLocalToolsDir is honoured by the bundler, the duplicate linux key is gone, the desktop template matches the 2.11.4 default plus the Flatpak categories, merge-tree against main is clean, and all 8 checks pass.

One optional nit: the NO_STRIP env in release.yml does nothing on the release runners today (v0.9.0 shipped both AppImages without it, and [profile.release] strip = true already strips the binary); scripts/linux/appimage.sh already sets it for the local Arch case. It's harmless, so keep it if you want it as future-proofing — I just wouldn't call it part of the fix.

Since 0.9.0 is already out, this is safe to land now; the bundler bump only gets exercised at the next tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants