Skip to content

feat: portable AppImage build (bundled Qt6/KF6 runtime)#30

Closed
hikaps wants to merge 2 commits into
developfrom
feature/appimage
Closed

feat: portable AppImage build (bundled Qt6/KF6 runtime)#30
hikaps wants to merge 2 commits into
developfrom
feature/appimage

Conversation

@hikaps

@hikaps hikaps commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a portable AppImage distribution: the GUI bundled with its Qt6/KF6 runtime libraries, so it runs on current x86_64 desktops without installing Qt6/KF6 system-wide. Came out of the #28 install discussion as a "runs anywhere" option alongside Flatpak.

How it's structured

  • GUI (couchplay): bundled via linuxdeploy + linuxdeploy-plugin-qt (Qt plugins, QML imports, libs). Runs from the AppImage against the bundled runtime.
  • Privileged helper: cannot live in the read-only/ephemeral AppImage mount (it's a root Type=dbus daemon). So it's carried inside and installed on demand via a custom AppRun subcommand:
    ./CouchPlay-x86_64.AppImage install-helper
    This extracts the helper + installer to a temp dir and runs the existing install-helper.sh install with sudo — landing the helper in system paths exactly like the tarball. The helper then uses system Qt6/Polkit (the AppImage's bundled libs are for the GUI only); missing ones are reported by the dep-detection in install-helper.sh/install.sh.

Files

  • scripts/AppRun — custom AppRun: launches GUI by default; install-helper subcommand extracts + installs the privileged helper.
  • scripts/build-appimage.sh — builds, fetches linuxdeploy/Qt-plugin/appimagetool, bundles the GUI, packages the AppImage.
  • .github/workflows/appimage.ymlfedora:40 container (glibc 2.39 baseline), triggers on v* tags + workflow_dispatch, uploads the AppImage to the release.
  • README.md — AppImage install section.

Portability

glibc forward-compat means the AppImage runs on any distro with glibc ≥ the build baseline. fedora:40 (glibc 2.39) covers Arch/CachyOS, Fedora, Ubuntu 24.04 LTS, Debian 13, openSUSE Tumbleweed. It will not run on Debian 12 / Ubuntu 22.04 LTS (older glibc); the workflow file documents how to lower the baseline (requires KF6 available on the older base — not in their default repos, so it's a follow-up). Flatpak remains the zero-glibc-constraint method.

⚠️ Verification status — needs a CI run

I could not build this locally (full KDE/Qt6 toolchain in a Fedora container). Verified so far:

  • bash -n clean on AppRun and build-appimage.sh; YAML valid.
  • AppRun env wiring (LD_LIBRARY_PATH/QT_PLUGIN_PATH/QML2_IMPORT_PATH) and the install-helper extraction layout match install-helper.sh's release-tarball structure detection.

To validate, trigger the workflow on this branch:

gh workflow run appimage.yml --repo hikaps/couchplay --ref feature/appimage

Things to confirm on that run: (a) fedora:40 resolves the kf6-*/qt6-* dnf deps, (b) linuxdeploy + Qt plugin bundle the Wayland platform plugin correctly, (c) the resulting AppImage boots the GUI on a clean system, (d) install-helper extracts and installs the helper.

Out of scope (follow-ups)

  • Bundle libs into the helper's system install (patchelf RPATH + install-helper.sh lib-dir install) so the helper also runs without system Qt6 — that's the change that would make how to install on cachyos? #28's helper fully portable.
  • Older-LTS support (lower glibc baseline + KF6 from source).

Refs #28

Add a portable AppImage distribution: the GUI is bundled with its Qt6/KF6
runtime libraries via linuxdeploy + the Qt plugin, so it runs on current
x86_64 desktops (glibc >= 2.39 baseline) without system Qt6/KF6.

The privileged helper cannot run from the AppImage mount, so it is carried
inside and installed on demand via a custom AppRun subcommand:

  ./CouchPlay-x86_64.AppImage install-helper

which extracts the helper + installer and runs install-helper.sh install
with sudo, landing the helper in system paths like the tarball. The helper
then uses system Qt6/Polkit (missing libs reported by existing dep checks).

- scripts/AppRun: GUI launch + install-helper subcommand
- scripts/build-appimage.sh: build + linuxdeploy + appimagetool
- .github/workflows/appimage.yml: fedora:40, tag + workflow_dispatch
- README.md: AppImage install section

Built on fedora:40 (glibc 2.39) for broad compat. Needs a workflow_dispatch
run to validate the actual bundle (could not build a full KDE app locally).

Refs #28
hikaps added a commit that referenced this pull request Jun 29, 2026
The release tarball's couchplay-helper is dynamically linked against system
Qt6/Polkit/PipeWire but bundles none, so on minimal distros (e.g. Arch) it fails
to start — the opaque exit / ERRNO 2 from #28. Bundle the helper's non-glibc
runtime libs into lib/couchplay with a $ORIGIN-relative RPATH via patchelf:

  - scripts/bundle-libs.sh: ldd-gather deps (excluding glibc/ld-linux), copy,
    patchelf --set-rpath $ORIGIN/../lib/couchplay
  - release.yml: add patchelf, run bundle-libs.sh in the Package step
  - install-helper.sh: install/uninstall lib/couchplay to $PREFIX/lib/couchplay

The helper now starts with zero system Qt6. glibc/ld-linux stay system-provided,
so the binary still has the build container's glibc floor. The GUI still needs
system Qt6 — a portable GUI is the AppImage's job (#30).

Refs #28
The install documentation — including the AppImage section — is consolidated
into PR #29 so the two PRs cannot conflict on README. This PR is now build-only
(scripts + workflow).
hikaps added a commit that referenced this pull request Jun 29, 2026
The release tarball's couchplay-helper is dynamically linked against system
Qt6/Polkit/PipeWire but bundles none, so on minimal distros (e.g. Arch) it fails
to start — the opaque exit / ERRNO 2 from #28. Bundle the helper's non-glibc
runtime libs into lib/couchplay with a $ORIGIN-relative RPATH via patchelf:

  - scripts/bundle-libs.sh: ldd-gather deps (excluding glibc/ld-linux), copy,
    patchelf --set-rpath $ORIGIN/../lib/couchplay
  - release.yml: add patchelf, run bundle-libs.sh in the Package step
  - install-helper.sh: install/uninstall lib/couchplay to $PREFIX/lib/couchplay

The helper now starts with zero system Qt6. glibc/ld-linux stay system-provided,
so the binary still has the build container's glibc floor. The GUI still needs
system Qt6 — a portable GUI is the AppImage's job (#30).

Refs #28
hikaps added a commit that referenced this pull request Jun 29, 2026
#28) (#29)

* fix(install): robust D-Bus reload + correct Flatpak helper export path

Resolve two install failures reported in #28:

1. couchplay-helper.service failed to start (Type=dbus unit exited 1).
   install-helper.sh masked D-Bus config-reload failures with || true, so
   the helper's ownership policy was never loaded and it could not register
   its service name. reload_dbus() now retries via SIGHUP on the pid file,
   failures are surfaced (not masked), and a failed start dumps the last 30
   journalctl lines.

2. Flatpak export wrote to the sandbox home (~/.var/app/...) while the
   README/host command looked in ~/.local/share/couchplay, so the host-side
   install found nothing. The manifest now grants --filesystem=home and
   export resolves the real host home from /etc/passwd. README adds an
   older-build fallback path.

Refs #28

* fix(install): correct Flatpak export path + detect missing runtime libs

Follow-up to the #28 install fixes based on reporter feedback:

- Flatpak export was still wrong: writing to $HOME/.local/share inside the
  sandbox vanishes (the sandbox home is not persisted to the host). Stage to
  $XDG_DATA_HOME instead, which Flatpak persists to ~/.var/app/<id>/data
  (host-visible, no extra permission needed). Reverts the unnecessary
  --filesystem=home (and the profiles behavior change it would introduce).
  README now points at ~/.var/app/.../data.

- The helper service crash on minimal Arch is an exec/load failure: the
  tarball bundles no runtime libraries, so the Qt6/KF6/Polkit/PipeWire-
  linked helper can't start where those are missing. install.sh now ldd-
  checks the helper and aborts with the missing sonames + per-distro install
  commands instead of an opaque service failure.

Refs #28

* fix(install): bundle helper runtime libs so it runs without system Qt6

The release tarball's couchplay-helper is dynamically linked against system
Qt6/Polkit/PipeWire but bundles none, so on minimal distros (e.g. Arch) it fails
to start — the opaque exit / ERRNO 2 from #28. Bundle the helper's non-glibc
runtime libs into lib/couchplay with a $ORIGIN-relative RPATH via patchelf:

  - scripts/bundle-libs.sh: ldd-gather deps (excluding glibc/ld-linux), copy,
    patchelf --set-rpath $ORIGIN/../lib/couchplay
  - release.yml: add patchelf, run bundle-libs.sh in the Package step
  - install-helper.sh: install/uninstall lib/couchplay to $PREFIX/lib/couchplay

The helper now starts with zero system Qt6. glibc/ld-linux stay system-provided,
so the binary still has the build container's glibc floor. The GUI still needs
system Qt6 — a portable GUI is the AppImage's job (#30).

Refs #28

* docs(install): restructure README — chooser + Flatpak/tarball/AppImage priority

Lead the install section with a 'which method should I use?' table and order
the methods by broad suitability: Flatpak (universal) -> tarball (curl one-liner
+ manual/Bazzite) -> AppImage (portable GUI). Consolidate the curl quick-install
and manual tarball steps under one Tarball section, and fold the AppImage docs in
here so this PR owns all install documentation.

Refs #28

* docs: drop AppImage install method from README

CouchPlay's entire function depends on the privileged root helper, which
cannot run from an AppImage mount and always needs a system install. That
nullifies AppImage's core 'just download and run' promise for this app, and
Flatpak already covers the universal/portable case (without AppImage's glibc
floor). Keep Flatpak + tarball/curl only.

* fix(review): force-rpath transitivity, guard empty lib glob, fix usage() path

Address code-review findings on PR #29:

- bundle-libs.sh: add --force-rpath so the helper gets DT_RPATH (transitive),
  not DT_RUNPATH. Without it the bundled transitive libs (Qt6Core's
  libpcre2/libdouble-conversion/libzstd) are unreachable on minimal systems and
  the helper still fails to load — defeating the #28 fix.
- install-helper.sh: guard the bundled-lib install with compgen -G so an empty
  lib dir doesn't abort the root install mid-way under set -e (partial install).
- install-helper.sh: usage() now prints the resolved $EXPORT_DIR instead of the
  stale hardcoded ~/.local/share/couchplay (which doesn't exist on host in Flatpak).

Finding C (install derefs symlinks) is N/A: bundle-libs.sh uses cp -L upstream,
so lib/couchplay holds flattened real files, no symlinks reach the install step.

---------

Co-authored-by: hikaps <hikaps@users.noreply.github.com>
@hikaps hikaps closed this Jun 29, 2026
@hikaps hikaps deleted the feature/appimage branch June 29, 2026 18:50
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.

1 participant