packaging: AppImage/Flatpak/macOS/Windows + first-run launcher prompt - #510
Merged
Merged
Conversation
Adds reproducible packaging recipes for the four end-user distribution
formats and a GitHub Actions workflow_dispatch that builds + uploads
artifacts on demand.
Tree:
packaging/
linux/
build.sh dispatcher: appimage | flatpak
appimage/ AppRun, Dockerfile, build.sh
flatpak/ org.amule.aMule.yaml.in (template,
rendered via versions.env), build.sh
versions.env pinned tarball URLs + SHA256s
macos/ cmake -> bundle .app -> dylibbundler -> .dmg
windows/ cmake -G Ninja -> install --prefix -> zip
.github/workflows/packaging.yml workflow_dispatch matrix per platform
Linux jobs run native on ubuntu-latest (x86_64) and ubuntu-24.04-arm
(aarch64). AppImages are validated cross-distro inside a Docker matrix
(Ubuntu 22/24/25, Fedora 41, Debian 12, Arch latest) so glibc/libstdc++
skew is caught before users hit it.
Flatpak manifest sets --persist=.aMule so $HOME/.aMule writes inside
the sandbox (server.met, amule.conf, known.met, partfile metadata,
client credits) are redirected to ~/.var/app/org.amule.aMule/.aMule on
the host. Without it, Flatpak's sandbox overlay swallowed those writes
and the server list reset on every reboot. A long-term follow-up could
teach aMule's config-path resolution to prefer XDG_DATA_HOME/aMule
inside a sandbox (detected via FLATPAK_ID / /.flatpak-info), making
the --persist hint redundant — out of scope for this PR.
When aMule starts inside an AppImage and no user-level launcher entry
exists yet, show a one-shot dialog ("Install / Not now" + "Don't ask
again" checkbox) offering to copy the bundled .desktop and hicolor
icons into ~/.local/share. The .desktop's Exec= is rewritten to point
at $APPIMAGE so the menu entry invokes the image at its real on-disk
path. update-desktop-database and gtk-update-icon-cache are run
best-effort.
The whole module is gated on __WXGTK__ — Windows / macOS skip the
include and call. Detection is via the APPIMAGE / APPDIR env vars
exported by AppImage's AppRun, so distro-installed and dev builds
never trigger the prompt.
A new persistent flag /GUI/AppImageIntegrationDeclined records the
"Don't ask again" choice so a user who declines once is never asked
again on subsequent AppImage launches.
4 tasks
Reviewers landing in packaging/ get hit with five subdirectories and no entry point that explains how they fit together. Add packaging/ README.md as a single orientation doc: * Tree layout with one-line descriptions of each subdir. * Per-platform pointer table linking to the existing platform-specific README.md files (AppImage / Flatpak / macOS / Windows). * 'Building one platform locally' — the four shell-script entrypoints in one place, so contributors don't have to read four docs to find the right invocation. * 'Building all four via GitHub Actions' — runner matrix, trigger policy (push-to-master + manual dispatch only, deliberately no pull_request trigger), and the macOS Rosetta migration note. * Versions and dependency pins — what versions.env files exist and how the Flatpak template gets rendered. * 'What this repo does NOT ship' — explicit scope: distro packaging, signing secrets, and the future release.yml are intentionally not in scope. Reduces 'why doesn't this also do X' questions in review. The per-platform README.md files under each subdir continue to own their platform-specific details — this top-level doc references them rather than duplicating their content.
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
May 2, 2026
…3nks mrjimenez flagged on PR amule-project#514 review that these three contributors were missing from the roster: * Werner Mahr / Vollstrecker — has merge access on amule-project/amule and merges incoming PRs (e.g. amule-project#430, amule-project#385, amule-project#319, amule-project#330, amule-project#286). Goes under Maintainers based on that role. * Pablo Barciela / Sc0w — recent contributor, e.g. wxWidgets 3.2.6 build fixes, GTK version-check cleanup, dropping gtk1/gtk2 support, Chinese translation work. Goes under Developers. * got3nks — Wayland app_id binding + SNI tray (amule-project#508), macOS UX (amule-project#508), libmaxminddb CI fix (amule-project#507), ip2country/upnp/nls/boost hard-fail series (amule-project#509/amule-project#511/amule-project#512/amule-project#513), packaging (amule-project#510), docs modernize (this PR). Goes under Developers. Other recent folks may be missing too — those can be added in follow-up commits as they're identified. mrjimenez explicitly noted this is the priority subset to not block the rename PR.
mrjimenez
pushed a commit
that referenced
this pull request
May 2, 2026
…3nks mrjimenez flagged on PR #514 review that these three contributors were missing from the roster: * Werner Mahr / Vollstrecker — has merge access on amule-project/amule and merges incoming PRs (e.g. #430, #385, #319, #330, #286). Goes under Maintainers based on that role. * Pablo Barciela / Sc0w — recent contributor, e.g. wxWidgets 3.2.6 build fixes, GTK version-check cleanup, dropping gtk1/gtk2 support, Chinese translation work. Goes under Developers. * got3nks — Wayland app_id binding + SNI tray (#508), macOS UX (#508), libmaxminddb CI fix (#507), ip2country/upnp/nls/boost hard-fail series (#509/#511/#512/#513), packaging (#510), docs modernize (this PR). Goes under Developers. Other recent folks may be missing too — those can be added in follow-up commits as they're identified. mrjimenez explicitly noted this is the priority subset to not block the rename PR.
2 tasks
mrjimenez
pushed a commit
that referenced
this pull request
May 2, 2026
…lity The libupnp 1.18.4 cmake build hard-codes lib64 as the install directory on both x86_64 and aarch64 — its CMakeLists doesn't go through GNUInstallDirs the usual biarch-aware way. As a result libupnp.pc + libupnp.so end up at /app/lib64/, but Flatpak's downstream PKG_CONFIG_PATH only searches /app/lib/pkgconfig/. aMule's cmake/upnp.cmake then can't find libupnp via pkg_check_modules and (since #511 made ENABLE_UPNP=YES a hard requirement) errors out. Before #511 this silently produced a Flatpak with no UPnP support — symptom went unnoticed because the existing test plan didn't cover UPnP port forwarding from inside the sandbox. Fix: pass -DCMAKE_INSTALL_LIBDIR=lib to libupnp's config-opts. Same pattern already applied to the ayatana-* libs further down in the manifest for the identical reason. libupnp.pc now lands at /app/lib/pkgconfig/libupnp.pc, pkg-config finds it, ENABLE_UPNP resolves true, the build completes. Affected runs: the first amule-project/amule:master Packaging run after #510-#514 merged (https://github.com/amule-project/amule/actions/runs/25253858400) where Flatpak (aarch64) failed at the amule cmake step with the new #511 FATAL_ERROR. Verified locally that the same regression existed on x86_64 too.
This was referenced May 2, 2026
mrjimenez
pushed a commit
that referenced
this pull request
May 3, 2026
PR #510 (AppImage first-run launcher prompt) added 14 user-facing strings in src/AppImageIntegration.cpp wrapped with _() / wxTRANSLATE for translation. The file itself was never added to po/POTFILES.in, so xgettext skips it during the periodic 'Automated translation update' run — the strings are extractable in source but invisible to the translation pipeline. Result: the new dialog stays English-only in every locale, even after translators sync their .po files. Add src/AppImageIntegration.cpp to po/POTFILES.in (alphabetised under src/A*). The next automated translation update will then regenerate po/aMule.pot with the new entries, propagating them to translators through the existing workflow.
5 tasks
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
May 3, 2026
Re-run scripts/update-po.sh to refresh po/amule.pot and merge into all po/*.po files. The previous update-po run did not pick up the new strings introduced in src/AppImageIntegration.cpp (PR amule-project#510), because src/AppImageIntegration.cpp had not yet been added to po/POTFILES.in when xgettext ran. This commit propagates the missing 14 user-facing strings into amule.pot and into every locale's .po file (as untranslated or fuzzy-matched entries, per msgmerge). Also fix scripts/update-po.sh to pass package metadata flags to xgettext (--copyright-holder, --package-name, --package-version, --msgid-bugs-address). Without these, each regen reset the pot header back to the xgettext placeholder values (PACKAGE VERSION, THE PACKAGE'S COPYRIGHT HOLDER), losing the aMule-specific package references that translators rely on.
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
May 3, 2026
Re-run scripts/update-po.sh to refresh po/amule.pot and merge into all po/*.po files. The previous update-po run did not pick up the new strings introduced in src/AppImageIntegration.cpp (PR amule-project#510), because src/AppImageIntegration.cpp had not yet been added to po/POTFILES.in when xgettext ran. This commit propagates the missing 14 user-facing strings into amule.pot and into every locale's .po file (as untranslated or fuzzy-matched entries, per msgmerge). Also fix scripts/update-po.sh to preserve aMule package metadata in the regenerated pot: - Pass --copyright-holder, --package-name, --package-version, and --msgid-bugs-address to xgettext so the pot header retains 'Project-Id-Version: aMule SVN' and the upstream bug address. - Substitute the current year for xgettext's 'Copyright (C) YEAR' placeholder using sed. Without these, every regen reset the pot header to xgettext placeholders (PACKAGE VERSION, THE PACKAGE'S COPYRIGHT HOLDER, YEAR), erasing the aMule-specific package references.
mrjimenez
pushed a commit
that referenced
this pull request
May 3, 2026
Re-run scripts/update-po.sh to refresh po/amule.pot and merge into all po/*.po files. The previous update-po run did not pick up the new strings introduced in src/AppImageIntegration.cpp (PR #510), because src/AppImageIntegration.cpp had not yet been added to po/POTFILES.in when xgettext ran. This commit propagates the missing 14 user-facing strings into amule.pot and into every locale's .po file (as untranslated or fuzzy-matched entries, per msgmerge). Also fix scripts/update-po.sh to preserve aMule package metadata in the regenerated pot: - Pass --copyright-holder, --package-name, --package-version, and --msgid-bugs-address to xgettext so the pot header retains 'Project-Id-Version: aMule SVN' and the upstream bug address. - Substitute the current year for xgettext's 'Copyright (C) YEAR' placeholder using sed. Without these, every regen reset the pot header to xgettext placeholders (PACKAGE VERSION, THE PACKAGE'S COPYRIGHT HOLDER, YEAR), erasing the aMule-specific package references.
5 tasks
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
Jul 17, 2026
…ty (amule-project#510) PR amule-project#508 added an "aMuleGUI Log" tab to the remote GUI's log notebook by inserting it at index 1. CamuleDlg tracked the network-conditional log tabs (Server Info / ED2K Info / Kad Info) by position in a fixed m_logpages[4] array, so the extra tab shifted everything: the GetPageCount() == 4 assert fired in debug builds, the capture loop wrote past the array end (corrupting the adjacent m_networkpages), and DoNetworkRearrange re-added the wrong pages -- dropping the Kad Info tab, as reported on amule-project#508. Track the network-conditional tabs by identity instead of index: capture each page via the control it hosts (ID_SERVERINFO / ID_ED2KINFO / ID_KADINFO) and add/remove those specific pages with FindPage(). This is index- and build-independent -- the always-on tabs (aMule Log, and aMuleGUI Log in the remote GUI) are simply left in place -- so the monolithic and amulegui layouts share one code path and it won't break again if a tab is added or reordered.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
aMule today ships as source. Distros build their own packages on their own schedule (some with patches, some pinned to
2.3.3from years ago); macOS users either use MacPorts or build from source with whatever Homebrew layout breaks today; Windows users are on the auld eMule-style installers from third-party sites. There's no upstream-blessed "click here, get aMule" path on any platform.This PR lays that path. Single
workflow_dispatchproduces:amule,amuled,amulegui,amulecmd,amuleweb,ed2k); a custom AppRun dispatches based onargv[0], soln -s aMule.AppImage amuled && ./amuledruns the daemon,… amulecmdruns the EC client, etc. Same idiom Krita / GIMP use to ship multiple tools in one image.flatpak run org.amule.aMulelaunches the GUI by default,flatpak run --command=amuled org.amule.aMule(or--command=amulecmd,--command=amuleweb,--command=ed2k) invokes the other binaries..dmg(arm64 + x86_64 lipo-merged) — drag-to-Applications layout, dylibs bundled. CLI binaries (amuled,amulecmd,amuleweb,ed2k) ship insideaMule.app/Contents/MacOS/next to the GUI entry point, runnable from terminal asaMule.app/Contents/MacOS/amuled …..zip(x64 MINGW64, arm64 CLANGARM64) — self-contained MSYS2 runtime, CA bundle for libcurl HTTPS.bin/contains every executable (amule.exe,amuled.exe,amulegui.exe,amulecmd.exe,ed2k.exe); run any directly.A second small commit then teaches the AppImage to integrate itself with the user's desktop on first launch (one-shot prompt; declinable for life via a prefs flag).
Commit 1 — packaging recipes
Tree layout
Workflow design
Two triggers, both path-filtered so doc-only commits don't burn CI:
master(or anypackaging**branch) — fires whensrc/**,cmake/**,CMakeLists.txt,packaging/**, or.github/workflows/packaging.ymlchange. Keeps master always-shippable; in practice this fires on every PR merge (since merge is a push to master). Open PRs do not trigger the workflow — that avoids the wasteful "fires on PR open AND on merge" double-cost.workflow_dispatch— with anonly=appimage,flatpak,macos,windowsinput for iterating on a single track without paying for the green ones. Reviewers who want to inspect a PR's artifacts before merging can dispatch this against the PR branch from the Actions UI.Each platform runs on its native GHA runner (no QEMU emulation), or its closest equivalent:
ubuntu-22.04ubuntu-22.04-armubuntu-22.04ubuntu-22.04-armmacos-15macos-15(Rosetta)/usr/localHomebrewmacos-15lipomerge of both arm64 + x86_64 .app treeswindows-latest(MSYS2 MINGW64)windows-latest(MSYS2 CLANGARM64)AppImage cross-distro validation
Every AppImage build runs the produced binary inside a Docker matrix — Ubuntu 22/24/25, Fedora 41, Debian 12, Arch latest — and asserts
--versionworks. This catches glibc / libstdc++ / openssl skew before users do.Flatpak details
The
org.amule.aMule.yaml.inmanifest is a template;packaging/linux/versions.envprovides pinned tarball URLs + SHA256s, rendered into the manifest bybuild.sh. Notable choices:--filesystem=home: aMule is a P2P client where users mark arbitrary folders as shared and pick arbitrary destinations for Incoming/Temp dirs. Restricting toxdg-downloadmakes the most common configs ("share my Music folder, save to ~/Movies") silently fail. This matches qBittorrent and Transmission on Flathub for the same reason. Side benefit: aMule's config dir lives at the standard~/.aMule/on the host, so state survives without--persist, and a Flatpak install shares config with any non-Flatpak install side by side.AyatanaIndicators stack from source: GNOME Platform 49 doesn't ship
libayatana-appindicator(or its dependencies —intltool,libdbusmenu,ayatana-ido,libayatana-indicator). Building them in-manifest is the price of admission for the SNI tray-icon backend on Flatpak. Each module has tightcleanup:rules so build-only deps don't end up in the runtime layer.libdbusmenu source: only on
launchpad.net. Debian/Ubuntu ship the.1point release with a different sha256, snapshot.debian.org doesn't carry the source package at this version, and old-releases / ports paths 404. No working mirror exists at the pinned upstream sha — relying on launchpad-only with a documented fallback hint (switch toAyatanaIndicators/libayatana-dbusmenu, the GitHub-hosted ABI-compatible fork) for if launchpad reliability becomes ongoing trouble.macOS x86_64 via Rosetta
GitHub deprecated
macos-13(Intel) — it's been flaky for months and is being retired. Migrating: x86_64 builds onmacos-15(Apple Silicon) with Rosetta. A parallel Homebrew install at/usr/localsupplies x86_64 dep bottles, and every command that produces x86_64 output is wrapped inarch -x86_64. CMake honoursMACOS_ARCHITECTURES=x86_64from the env (handled inpackaging/macos/build.sh's existing arch logic) so clang emits the right slice. End result is identical to a native Intel build, and the runner is now reliable.Universal2 .dmg merge
Both arm64 and x86_64 builds upload their
.apptrees as artifacts. A third job downloads both, walks the arm64 layout, lipo-merges every Mach-O against its x86_64 sibling, and re-wraps the merged.appinto a single.dmg.After lipo-merge, two post-processing steps are mandatory:
chmod +xevery merged Mach-O.lipo -create -output <file>writes the result with the runner's process umask (default 022 →rw-r--r--), discarding the source's executable bit. Without restoring it, macOS launchd refuses to spawn the bundle with errno 13 (EACCES onaccess(X_OK)) — the .app appears intact but won't open.codesign --force --deep --sign -ad-hoc the whole bundle. clang on Apple Silicon emits ad-hoc-signed Mach-O by default;lipoinvalidates per-slice signatures on merge, and Apple Silicon's amfid then refuses to load the resulting fat binary. The re-sign produces valid ad-hoc sigs across every nested executable so the binaries pass amfid even before any notarization step.macOS first-launch UX (unsigned + non-notarized)
Without an Apple Developer ID + notarization stamp, the produced .dmg ships with only ad-hoc signatures. End users on macOS 11–14 used to bypass Gatekeeper via right-click → Open → "Open Anyway". macOS 15 (Sequoia) removed that path entirely — the only way for end users to launch an unsigned .app now is via System Settings:
Or via terminal one-liner (faster for power users):
Until the project has Apple Developer credentials wired up (see follow-ups), this is the user experience. The macOS install hint should be documented in the .dmg's README and on the project download page so first-time users aren't surprised. Notarization is the highest-impact post-merge follow-up — the only thing that makes the .dmg "just work" without these steps.
Windows .zip
cmake --installlays out a portable tree under~/amule-portable/:bin/*.exeplus all bundled MSYS2 runtime DLLs (viafile(GET_RUNTIME_DEPENDENCIES)) and abin/ca-bundle.crtfor libcurl HTTPS. Self-contained — copies cleanly to a non-MSYS2 host.Commit 2 — AppImage first-run launcher prompt
Why this is in the same PR as packaging
Without a launcher entry, the AppImage produced by Commit 1 is just a file in
~/Downloads. Wayland's app_id binding (PR #508) only kicks in when there's a.desktopto bind against. So the AppImage launches, GNOME shows a generic icon, and the user has no way to "find aMule in the menu" short of runningchmod +xfrom a terminal every time.Splitting these into two PRs would mean shipping a half-finished UX from day one. Bundling them keeps the artifact testable end-to-end.
Behaviour
When aMule starts inside an AppImage (
getenv("APPIMAGE")set by AppRun) AND no user-level~/.local/share/applications/org.amule.aMule.desktopexists yet AND the user hasn't opted out:wxRichMessageDialogafter the main frame is realized:org.amule.aMule.desktopfrom$APPDIR/usr/share/applications/into$XDG_DATA_HOME/applications/(default~/.local/share/applications/), rewritingExec=to point at the current$APPIMAGEpath so the launcher invokes the AppImage at its real on-disk location.org.amule.aMule.pngper size) into$XDG_DATA_HOME/icons/hicolor/<size>/apps/.update-desktop-database+gtk-update-icon-cache.shthat polls the current PID andexecs$APPIMAGEonce aMule has fully exited (gives the normal shutdown path time to save partfiles, release the EC port, etc.).[General] AppImageIntegrationDeclined=trueto aMule prefs and callSave()immediately so a crash before normal shutdown still remembers the user's choice.Source-code touch points
src/AppImageIntegration.{h,cpp}— new module (~270 LOC): env detection, file copies, cache-refresh, the prompt + restart flow.src/Preferences.{h,cpp}— addss_appimageIntegrationDeclinedstatic +IsAppImageIntegrationDeclined() / SetAppImageIntegrationDeclined()accessors. Persisted via the existings_MiscListfor cfg items not tied to a UI dialog control.src/amule-gui.cpp::CamuleGuiApp::OnInit— call site, gated on__WXGTK__. UsesCallAfter()to defer the dialog until the event loop is fully running so it doesn't blockOnInit's return path.cmake/source-vars.cmake— adds the new.cpptoGUI_SOURCES.The whole module is gated on
__WXGTK__— Windows / macOS builds skip both the include and the call entirely (verified by CI on those platforms).Why only AppImage (not also Flatpak / distro / cmake-install)
apt install amule)/usr/share/applications/*.desktopalready therecmake --installto/usr/local/usr/local/share/applications/*.desktopalready there.desktop$APPIMAGEenv var is the affirmative signal that the user is in a self-contained, no-install-script context. Every other case is either already handled by a package manager or a dev workflow.Verification
CI green on all 9 jobs across 4 platforms (linked to the passing workflow run at PR-open time).
Manual end-to-end tests:
gnome-shell-extension-appindicator): AppImage launcher prompt fires on first run,Installpopulates~/.local/share/applications/and~/.local/share/icons/hicolor/<sizes>/apps/,Restart nowcleanly cycles the binary, post-restart aMule appears in the application menu with the correct mule icon, dock icon binds via Wayland app_id (PR desktop integration: Wayland app_id, SNI tray, macOS dock-restore + tray opt-in (closes #410) #508).flatpak install --user *.flatpak,flatpak run org.amule.aMulelaunches, server.met persists acrossflatpak kill+ relaunch (--filesystem=homeresolves the sandbox-overlay write loss), tray icon renders, Incoming dir set to~/Movies/works (no sandbox write block).HideOnCloserestores window (PR desktop integration: Wayland app_id, SNI tray, macOS dock-restore + tray opt-in (closes #410) #508).bin/amule.exelaunches, banner correct.Future follow-ups (out of scope here)
release.yml: tag-triggered workflow that runs the same builds and uploads artifacts to GitHub Releases (permanent public URLs, anonymous download). The packaging.yml here is a "PR sanity gate"; the release workflow is the user-facing distribution channel.flatpak install flathub org.amule.aMulediscoverability + auto-updates.packaging/macos/sign.shis stubbed; wire up Apple Developer credentials when the project has an account, otherwise users get the "unidentified developer" Gatekeeper warning.Test plan
~/.aMulestate across reboots (home access)$APPIMAGEunset)