Skip to content

Latest commit

 

History

History
250 lines (187 loc) · 25.1 KB

packaging.md

File metadata and controls

250 lines (187 loc) · 25.1 KB

Linux packaging

App id (used everywhere): io.github.memergamer.LinuxFiles. Binary/launcher: files. Assembly: Files.dll (apphost Files).

Layout

Path Purpose
packaging/linux/io.github.memergamer.LinuxFiles.desktop Desktop entry (inode/directory, new-window action).
packaging/linux/io.github.memergamer.LinuxFiles.metainfo.xml AppStream metadata.
packaging/linux/icons/hicolor/<N>x<N>/apps/ Icons 16, 24, 32, 48, 64, 128, 256, 512 (PNG).
packaging/linux/files Launcher: finds Files.dll (FILES_LIBDIR, next to itself, ../share/linuxfiles, /usr/lib/linuxfiles, ...) and execs the apphost if present, else dotnet Files.dll.
packaging/linux/flatpak/ Flatpak manifest (repackages the publish tarball).
packaging/linux/aur/linuxfiles-bin/PKGBUILD Guarded AUR binary and source recipe templates; generate checksum-pinned recipes before use.
packaging/linux/appimage/build-appimage.sh AppImage builder; downloads pinned appimagetool + type2 runtime into .cache/tools.
scripts/linux/build-flatpak.sh, gen-aur.sh Build the Flatpak bundle; render PKGBUILD + .SRCINFO with real checksums.
scripts/linux/publish.sh dotnet publish to artifacts/linux-<rid>/.
scripts/linux/install-local.sh, uninstall-local.sh User-local install into ~/.local.
scripts/linux/install-host-root-helper.py Explicit companion host-helper install from a matching native release; does not alter sandbox permissions.
scripts/linux/gen-icons.sh Regenerates the icons.
flake.nix, scripts/linux/update-nix-flake.sh Nix flake (repackages the release tarball); the script bumps version and hashes.
.github/workflows/package-linux.yml Manual / linux-v* tag: publish, AppImage, Flatpak, AUR recipe; tags also create a GitHub release.

Icons

The repo has no SVG logo, so there is no scalable/ icon. Sizes 16/24/32/48/256 are copied from Assets/AppTiles/Release/Square44x44Logo.targetsize-*.png; 64/128/512 are downscaled from the 600 px Square150x150Logo.scale-400.png (so 512 is a downscale). A proper SVG should replace these when design provides one.

Wayland host and package feed

Current source builds prefer native Wayland and retain X11 fallback. CI and scripts/linux/publish.sh fetch the pinned Uno fork release with build/fetch-uno-wayland.sh; contributors using dotnet build run that script first. The initial fork packages must be built, their hashes committed in LinuxFiles, and those exact bytes published before restore. Fetch/import fails until all eight reviewed pins exist. See the release asset contract, runtime overrides and build handoff. -p:UseUnoWayland=false or scripts/linux/publish.sh --x11 uses upstream X11 packages without fetching the fork. Alpha4 release assets and the current Nix application pin remain X11-only.

AUR binary and Nix packages use LinuxFiles release tarballs and require no Uno credentials/feed. Source AUR recipes declare the eight pinned Uno nupkgs, mark them noextract, and import them from $srcdir without downloads before publishing. AppImage uses host Wayland client/cursor/EGL and xkbcommon libraries; Flatpak uses its Freedesktop runtime and exposes both Wayland and X11 sockets. Validate both packaged hosts before publishing the next release and bumping Nix's application version/hashes. Previous package measurements and smoke checks below describe the older X11 builds.

Publishing

scripts/linux/publish.sh                        # self-contained linux-x64 -> artifacts/linux-x64/
scripts/linux/publish.sh --rid linux-arm64      # arm64 (cross-publish; not tested)
scripts/linux/publish.sh --framework-dependent  # needs a system dotnet 10 runtime
scripts/linux/publish.sh --tarball              # also writes artifacts/files-<rid>.tar.gz

Trimming and AOT are explicitly disabled (PublishTrimmed=false, PublishAot=false). Measured on the linux/l-pkg branch: self-contained linux-x64 is about 155 MB unpacked (276 files), 63 MB as .tar.gz. The publish emits a few ILxxxx analyzer warnings (IL2026 for Microsoft.Xaml.Interactivity behaviours and Assembly.GetType, IL2072 for Activator.CreateInstance/PageStackEntry, IL2122) plus many Uno0001 not-implemented warnings; these matter only if trimming/AOT is enabled later.

Local install

scripts/linux/install-local.sh [--from DIR] [--prefix ~/.local] copies the build to <prefix>/share/linuxfiles, the launcher to <prefix>/bin/files, and installs the desktop entry (with an absolute Exec), metainfo and icons, then runs update-desktop-database and gtk-update-icon-cache when present. uninstall-local.sh reverses it.

WM_CLASS / StartupWMClass

Uno's X11 host calls XSetClassHint but the class value was not verified at runtime here. The desktop entry assumes StartupWMClass=Files (assembly/process name). Verify on a real X11 session with xprop WM_CLASS (click the window) and adjust the .desktop if it differs; otherwise taskbar grouping and icon matching will fail.

Building the packages

All three consume the output of scripts/linux/publish.sh --tarball (artifacts/files-linux-x64.tar.gz), and CI runs the same scripts (job package-linux-x64; on a linux-v* tag the release job attaches files-linux-x64.tar.gz, files-packaging.tar.gz, Files-x86_64.AppImage and Files-x86_64.flatpak to a GitHub release). Verified locally on Arch: all three build.

AppImage

packaging/linux/appimage/build-appimage.sh [--arch x86_64|aarch64] [publish-dir] assembles an AppDir (AppRun sets FILES_LIBDIR; the runtime is self-contained, host libs such as fontconfig, Wayland, xkbcommon and X11/GL/EGL come from the system) and runs appimagetool. The tool (1.9.1) and the type2 runtime (20251108) are downloaded into the git-ignored .cache/tools and verified against SHA-256 sums pinned in the script (they match the digests GitHub publishes for those release assets). Nothing is installed system-wide; without FUSE the tool runs via APPIMAGE_EXTRACT_AND_RUN. Output: artifacts/Files-<arch>.AppImage (about 61 MB). arm64 is wired (pinned sums) but only x86_64 was built.

Smoke run on the private Xvfb display (the Home page renders):

FILES_EXEC=artifacts/Files-x86_64.AppImage scripts/linux/headless-run.sh -s 25 -o /tmp/files-appimage

headless-run.sh honours FILES_EXEC (any launcher, including packaging/linux/files) instead of dotnet Files.dll; for *.AppImage it sets APPIMAGE_EXTRACT_AND_RUN=1 and APPIMAGELAUNCHER_DISABLE=1 so a host AppImageLauncher cannot pop up an integration dialog.

Flatpak

packaging/linux/flatpak/io.github.memergamer.LinuxFiles.yml, runtime org.freedesktop.Platform//25.08. The module unpacks the self-contained publish tarball, so there is no .NET SDK, NuGet restore or network access in the build (Flathub-friendly: for a release, point the file source at the release URL with its sha256). scripts/linux/build-flatpak.sh [--bundle] runs flatpak-builder (needs the 25.08 Platform and Sdk installed) into artifacts/flatpak-repo and optionally writes artifacts/Files-x86_64.flatpak; without flatpak-builder it only validates the manifest structure. Built locally, installed from a throwaway FLATPAK_USER_DIR and run inside the sandbox on a private Xvfb.

XDG user folders (Quick access / Pinned): user-dirs.dirs is visible in the sandbox (flatpak bind-mounts the host file at $XDG_CONFIG_HOME, which is remapped to ~/.var/app/<id>/config), $HOME is the real home, and --filesystem=host exposes the folders, so no extra finish-arg is needed (xdg-* permissions would be redundant). With a seeded real-path HOME the Desktop/Downloads/Documents/Pictures/Music/Videos pins appear, also when user-dirs.dirs is missing (fallback to $HOME/<default names>). What does hide them is a HOME under /tmp: flatpak gives the app a private /tmp, so a HOME seeded there looks empty (only the Recycle Bin is pinned). Seed test homes outside /tmp. Defensive changes: LinuxUserDirectories honours XDG_<NAME>_DIR environment variables before the file, and the first-run seed of pinned_folders.json is persisted only when at least one folder was found, so a launch that could not see the folders retries instead of freezing an empty list.

finish-args rationale (each line is the minimum for a feature that exists in the code):

  • --share=ipc, --socket=wayland, --socket=x11, --device=dri: native Wayland with X11 fallback and graphics access. Keep unconditional X11 access so UNO_SKIA_HOST=x11 works even when Wayland is available.
  • --filesystem=host: a file manager needs the whole filesystem. --filesystem=xdg-run/gvfs, xdg-run/gvfs-fuse and --talk-name=org.gtk.vfs.*: mounted network/MTP locations through GVfs.
  • --own-name=org.freedesktop.FileManager1: the ShowFolders/ShowItems/ShowItemProperties service (Files.Platform.Linux/DBus/FileManagerService.cs).
  • --system-talk-name=org.freedesktop.UDisks2: drive listing, mount, unmount, eject.
  • --talk-name=org.freedesktop.secrets: Secret Service credential store.
  • --talk-name=org.freedesktop.Notifications: toasts.
  • Not granted: org.freedesktop.Flatpak (flatpak-spawn --host would void the sandbox). The portal bus names (org.freedesktop.portal.*, used for the appearance setting) are always reachable. Opening files with other apps inside the sandbox therefore depends on the OpenURI/OpenFile portals; verify "Open with" behaviour when running the Flatpak for real.

AUR

packaging/linux/aur/linuxfiles-bin/PKGBUILD repackages the release tarballs (files-linux-x64.tar.gz plus files-packaging.tar.gz, which carries the desktop entry, metainfo, icons, launcher and LICENSE-MIT) into /usr/lib/linuxfiles + /usr/bin/files; x86_64 only. Both checked-in recipes are guarded templates: they fail when sourced until scripts/linux/gen-aur.sh <version> <dir> [out] [binary|source] generates a recipe with SHA-256 checksums and .SRCINFO. Binary mode (the default) uses the two release assets above; source mode requires the exact GitHub tag archive saved as linuxfiles-<version>.tar.gz. Obtain the input artifacts from a trusted release before generating checksums. CI generates the binary recipe on tags and uploads it; the AUR remote is updated separately. AUR_BASE_URL=file:///dir can point generated recipes at local inputs for offline testing. Native package builds and namcap validation remain separate release checks.

Publishing a release (exact procedure)

First used for linux-v0.1.0-alpha1 (prerelease; install instructions are in .github/README.md, the release body is kept in docs/linux-port/release-notes/<version>.md). Create a draft release linux-vX.Y.Z[-pre] with the notes, optionally with hand-uploaded assets; CI replaces them with its own build.

  1. Undraft the release (GitHub UI, or gh release edit linux-vX.Y.Z-pre -R MemerGamer/LinuxFiles --draft=false). Publishing creates the tag, which triggers package-linux.yml. The release job finds the existing release (drafts included) and runs gh release upload --clobber for files-linux-x64.tar.gz, files-packaging.tar.gz, Files-x86_64.AppImage, Files-x86_64.flatpak, a regenerated SHA256SUMS, and the AUR PKGBUILD and SRCINFO (the .SRCINFO; release assets cannot start with a dot). Notes and prerelease state are untouched. If no release exists for the tag, it creates one (prerelease when the version after linux-v contains -). gen-aur.sh validates the version strictly and keeps the real tag for the download URLs (_tag), while pkgver drops hyphens. Wait for the run to finish: the CI tarballs differ from the hand-built ones, so the checksums in the PKGBUILD only match the CI assets.
  2. Download the CI's recipe and commit it in the AUR clone:
    cd ~/Documents/aur/linuxfiles-bin
    gh release download linux-vX.Y.Z-pre -R MemerGamer/LinuxFiles -p PKGBUILD -p SRCINFO --clobber
    mv -f SRCINFO .SRCINFO
    git add PKGBUILD .SRCINFO && git commit -m "Update to X.Y.Z-pre"
    
  3. git push aur (from the same directory; the remote is the AUR linuxfiles-bin repo).

Nix flake

flake.nix (with flake.lock, pinned to nixos-unstable) exposes packages.x86_64-linux.{linuxfiles,default}, apps.x86_64-linux.default, overlays.default (adds pkgs.linuxfiles), and nixosModules.default. The package repackages the pinned release tarballs, patches all ELF files with autoPatchelfHook (including the retained Native AOT helper), and installs the app under $out/lib/linuxfiles. Runtime dlopen libraries are added to ELF runpaths rather than the launch environment, so child programs do not inherit Nix libraries. The unused CoreCLR LTTng tracing provider remains exempt from dependency checking.

Plain nix run and nix profile install retain .root-actions-disabled: all root actions remain off. Installing a profile cannot activate host polkit. NixOS administrators may opt in through their configuration flake with the compatible alpha4 runtime:

# inputs.linuxfiles.url = "github:MemerGamer/LinuxFiles";
# Add inputs.linuxfiles.nixosModules.default to nixosSystem.modules, then:
programs.linuxfiles = {
  enable = true;
  rootActions = true; # default: false
};

The module selects the package's enableRootActions variant, removes its disable marker, enables security.polkit, and installs a generated action in share/polkit-1/actions. Its exec.path names exactly the invoked store helper, never a mutable profile alias. /etc/linuxfiles/root-actions is a root-controlled two-line deployment manifest (helper path, policy path, final newline). Discovery checks every manifest symlink traversal, immutable root-owned store entries, executable permissions, and the action's path and fresh auth_admin defaults. No user environment variable selects a helper or manifest. A changed helper path between preview and execution rejects the old confirmation; a malformed deployment never falls back to a native helper or a generic prompt.

pkexec uses /run/wrappers/bin before the system profile and /usr/bin//bin. The module enables security.polkit.enablePkexecWrapper when present; older nixpkgs uses security.wrappers.pkexec. Only pkexec is setuid, never the LinuxFiles helper. A session authentication agent and an administrator identity are still required. The narrow store exception accepts a root-owned sticky /nix/store with group write (1775), but rejects other write there, non-root ownership, and any writable entries beneath it. Native /usr helper/policy checks retain their existing restrictions.

Release integration: the current pin is 0.1.0-alpha8; like alpha4 to alpha7, it includes deployment-manifest and Nix elevation-wrapper discovery. Its passthru.supportsNixRootActions marker is true, so the module accepts rootActions = true. Overrides with a false or missing marker are still rejected by the runtime-capability assertion. For future releases, verify that both runtime changes are present before retaining the compatible-runtime marker.

Per release (after CI has attached SHA256SUMS; the flake's tag is linux-v<version>):

gh release download linux-vX.Y.Z[-pre] -R MemerGamer/LinuxFiles -p SHA256SUMS --clobber
scripts/linux/update-nix-flake.sh X.Y.Z[-pre] SHA256SUMS   # rewrites version + both hashes in flake.nix

Commit flake.nix in a PR. It is deliberately not a CI step: the hashes only exist after the release job has uploaded the assets, and main takes changes through PRs. Run nix flake update separately to bump nixpkgs.

The earlier plain package passed nix flake check --no-build and nix build with nix-portable. The checks.x86_64-linux.root-actions-module check covers rejection of packages with a false or missing capability marker, opt-out defaults, and compatible-runtime module wiring using the pinned alpha4 package. When built, it also checks helper presence, marker state, and the generated policy path. Re-run nix flake check against the locked revision after the release bump; sandboxed DNS failures cannot establish NixOS evaluation or packaged loader startup. See the headless checklist below.

Nightly builds

.github/workflows/nightly-linux.yml builds the tarball and AppImage on every push to main and replaces the single rolling pre-release at https://github.com/MemerGamer/LinuxFiles/releases/tag/nightly (stable asset names LinuxFiles-nightly-linux-x64.tar.gz and LinuxFiles-nightly-x86_64.AppImage; the Flatpak is only built for linux-v* tags). Pull requests targeting main get the same files as workflow artifacts with a read-only token. Try it: curl -LO https://github.com/MemerGamer/LinuxFiles/releases/download/nightly/LinuxFiles-nightly-x86_64.AppImage && chmod +x LinuxFiles-nightly-x86_64.AppImage && ./LinuxFiles-nightly-x86_64.AppImage

Becoming the default file manager (Phase 6, not done yet)

  1. inode/directory: already in the desktop entry's MimeType=. Users (or the installer's post-install hook) then run xdg-mime default io.github.memergamer.LinuxFiles.desktop inode/directory. Do not do this automatically from the package scripts.
  2. org.freedesktop.FileManager1: implement ShowFolders, ShowItems, ShowItemProperties on the session bus (Phase 6 task, in Files.Platform.Linux). To make it activatable when the app is not running, install /usr/share/dbus-1/services/org.freedesktop.FileManager1.service with Name=org.freedesktop.FileManager1, Exec=/usr/bin/files --dbus-activate (flag to be defined). The Flatpak equivalent is installed under /app/share/dbus-1/services/ and covered by the --own-name finish-arg already present.
  3. Each package recipe above needs the .service file added to its install step at that point.

Open questions

  • Real WM_CLASS value on X11 (see above).
  • Whether an SVG logo can be supplied for scalable/.
  • Runtime behaviour of the Flatpak (sandboxed run, portals) and a namcap lint of the AUR package.
  • arm64 publishing has not been tested.

Root actions

Native AUR packages install a root-owned Native AOT helper at /usr/lib/linuxfiles/files-elevation-helper and packaging/linux/io.github.memergamer.LinuxFiles.root-actions.policy under /usr/share/polkit-1/actions/. Install polkit 0.105 or newer with distributor security fixes to enable helper actions in root mode. The helper is never setuid; the action uses auth_admin without retaining authorization. AOT publishing requires clang and zlib development files.

For local installation, publish normally and opt in with scripts/linux/install-local.sh --install-root-helper. This separately runs sudo python3 scripts/linux/install-root-helper.py --from artifacts/linux-x64/elevation-helper --sha256 <published-helper-sha256>; the app stays in the user prefix. This is equivalent to sudo make install: trust the installer checkout and build output. The policy is embedded; a private root-owned helper snapshot is hashed and checked before installation. Before writing system files, the installer queries available /usr/bin/pacman -Qo, /usr/bin/dpkg-query -S, and /usr/bin/rpm -qf tools for ownership of /usr/lib/linuxfiles, the helper, and the policy. It refuses to overwrite package-owned paths: omit --install-root-helper and use the distro package's helper instead. Tarball users without package-owned paths keep the same /usr/lib/linuxfiles/files-elevation-helper and /usr/share/polkit-1/actions/ installation; the launcher and app's fixed helper/policy trust checks remain compatible. To inspect the package layout without privileges, run python3 scripts/linux/install-root-helper.py --from <published-helper-dir> --sha256 <published-helper-sha256> --destdir <temporary-staging-dir>. Remove the system helper and policy as administrator when uninstalling; the user-local uninstaller intentionally cannot remove system files.

Companion host-helper opt-in (AppImage / Flatpak)

AppImage and Flatpak omit the helper and Open in Root Mode desktop action, and retain their full elevation disable gates. A separately installed host helper is an administrator opt-in for native host integration; installing it does not enable root actions in either current format. No sandbox permission or runtime gate is changed here. Flatpak still has no org.freedesktop.Flatpak permission and no flatpak-spawn --host bridge. AppImage still refuses its user-controlled extraction directory for privileged helpers.

To provision only the host helper, verify and unpack the matching native tarball from the same release (the helper is not in the AppImage or Flatpak). Verify the tarball against the release SHA256SUMS, inspect the trusted installer checkout, and record the approved helper digest:

sha256sum -- /path/to/linux-x64/elevation-helper/files-elevation-helper
python3 scripts/linux/install-host-root-helper.py --from /path/to/linux-x64 --sha256 <approved-helper-sha256>

The companion script invokes the existing hardened installer through literal argv and sudo. It installs only /usr/lib/linuxfiles/files-elevation-helper (0755, never setuid) and the fixed-path policy (0644), verifies a private snapshot and digest, and refuses package-owned destinations. NixOS users must use the module instead of this /usr layout. For inspection without privileges, add --destdir /tmp/linuxfiles-helper-stage. Re-run explicitly for each matching release update; remove those two system files as administrator when retiring a manually installed helper. No installer grants persistent polkit authorization or changes the user's desktop settings.

For root file operations today, run the matching native tarball/local install or enabled NixOS package. A future AppImage host discovery path or Flatpak transport needs separate implementation and review; a host helper alone is insufficient. See the elevation threat model for protocol, failure semantics and limitations.

Normal launches show only Root actions → Open in terminal as root, independently of helper installation; authentication happens through the terminal elevation tool. Launch files --root or choose Open in Root Mode from the desktop entry to additionally show Delete/Rename/Paste as root when the trusted helper is installed. Each action requests polkit authentication; the GUI remains unprivileged. Tabs and the title show (Root mode) only when helper actions are allowed by the package/disable gates. Each --root invocation opens its own process/window and bypasses normal single-instance forwarding, so an existing ordinary window never acquires root mode. New Window preserves --root. These separate processes never claim FileManager1.

For an actual uid-0 launch (sudo files), tabs/title show (Running as root), ordinary operations already run as root, and all helper/root-terminal actions are hidden. The normal terminal command remains available. Before app initialization, HOME and XDG config/data/cache/state are reset to root's account home/defaults; XDG_DATA_DIRS and XDG_CONFIG_DIRS are reset to /usr/local/share:/usr/share and /etc/xdg, and PATH to /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin. Inherited TERMINAL, EDITOR, VISUAL, BROWSER, SHELL, XDG_CURRENT_DESKTOP, user runtime/session-bus, GVfs and TMPDIR overrides are cleared. Unsafe root-owned directory configuration aborts startup before settings writes. This also protects the invoking user's settings when sudo preserves HOME or XDG variables. AppImage/Flatpak retain their full elevation disable gate, including the root-terminal item, even with --root, and show no root-mode indicator when helper actions are disabled.

Pending headless verification for Nix root actions

Run app checks only with FILES_SANDBOX_DRIVES=scripts/linux/showcase-drives.txt scripts/linux/headless-run.sh (private Xvfb and throwaway HOME); never use the real display. The harness blocks the system bus, so authentication and privileged operations additionally require a disposable NixOS VM with private Xvfb, a controlled polkit agent, and disposable test files.

  • Plain Nix launch and --root: marker keeps all root actions and the root-mode indicator hidden. Enabled NixOS files --root: root-mode title/tabs, Delete/Rename/Paste menus and independent root-terminal action appear while GUI uid remains unprivileged; ordinary launch stays outside helper mode.
  • Packaged helper starts with the runner's cleared environment: its patched loader and dependencies work, and polkit selects the action whose exec.path exactly matches the displayed/invoked store helper. Root-terminal elevation selects the trusted Nix wrapper.
  • Confirmation shows the exact helper, sanitized paths/operation, stdin JSON summary and request SHA-256. Cancel/dismiss leaves files unchanged; approved delete, rename, copy and move on disposable protected files report complete/partial results correctly.
  • Switch NixOS generations while a dialog is open: the old helper confirmation refuses execution; a fresh plan shows and uses the new helper/action. Disabling the module or breaking helper/policy/manifest trust keeps helper actions unavailable without fallback.
  • AppImage and Flatpak with the companion installed still hide all root actions and root-mode indicators, including --root; ordinary browsing and existing portals continue working with unchanged sandbox permissions.