Skip to content

Linux packaging & deployment: zero-toolchain install (deb / rpm / AppImage) + release artifacts #168

Description

@jonocodes

Problem

deckd runs on Linux today only from a source checkout: just setup-linux (uv + Python + Node), just build-client, then just install-service, which sed-substitutes @PROJECT_DIR@ into packaging/systemd/deckd.service and points the user unit at .venv/bin/deckd. On top of that the user must hand-install the udev rule + input group membership (packaging/udev/70-deckd-uinput.rules) and the desktop-specific focus watcher (GNOME Shell extension / KWin script). That needs git, Python, Node, uv, and a checkout.

Nix users are covered by the flake (#17: packages.deckd + NixOS/home-manager modules own the service, udev rule, and group), but that's a different audience and a different install path.

#165 gives macOS a zero-toolchain path (self-contained .app in a DMG, built and published by a release workflow). Linux has no equivalent.

Goal

Any Linux user can install deckd with zero toolchain: download an artifact from a GitHub release, install it, grant /dev/uinput access once, install the focus watcher for their desktop, and run. No Python, Node, or checkout required.

Follows

#165 (macOS .app + DMG + release-macos.yml). Mirror the shape: a build recipe that produces a distributable artifact, and a release workflow that attaches it to the tag. Reuse the DECKD_VERSION version seam (#165) so tag and artifact naming stay consistent across platforms.

Candidate channels (to decide)

  • Tarball + install script — simplest, distro-agnostic, but nothing owns the udev rule / group / service. Good phase-0 target.
  • .deb / .rpm — the package can own the udev rule, the systemd user unit, and a desktop file, and post-install can add the user to input. Needs per-distro CI.
  • AppImage — one file, zero deps; but it can't write /etc/udev/rules.d or add groups, and ships no systemd unit. Would need a first-run helper for the privileged steps.
  • Flatpak / Snap — sandboxing conflicts with the daemon's model: it injects global input via /dev/uinput, reads other apps' windows through a compositor plugin, and talks to the session D-Bus bus. The GUIDE already rules out Docker for exactly these reasons, so this is likely a poor fit — but worth a sentence in the exploration.
  • Distro repos / AUR / Copr — community path, later; out of scope for a first artifact.

Linux-specific complications

  • uinput needs root (udev rule + input group), so the artifact can't be fully unprivileged — unlike the macOS DMG.
  • The focus watcher is desktop-specific (GNOME Shell extension, KWin script) and can't ship inside a desktop-neutral daemon package.
  • The [dbus] extra is required on Linux (the dbus: action primitive + MPRIS); the macOS bundle deliberately omits it.
  • Python runtime: bundle a private interpreter (PyInstaller, as macOS does; or a uv standalone CPython) vs depend on the distro's Python.
  • Arch: x86_64 + aarch64 (evdev-binary has no aarch64 wheel — source build, see setup-linux).
  • Client + layouts must be bundled (client/dist, layouts/, layouts.*).
  • Auto-start: systemd user unit vs an XDG autostart .desktop.

Open questions

  • Which channel first? (Tarball → deb/rpm → AppImage? Or straight to .deb?)
  • PyInstaller onedir (reuse the macOS spec shape) vs a uv-built standalone CPython + venv.
  • How much can the package do for the user (udev rule, group, service, focus watcher) without sudo surprises?
  • One artifact per desktop, or one artifact plus an "install the focus watcher" step that detects GNOME/KDE/X11?
  • Do we ship a tray equivalent, or keep the systemd user unit? (macOS needed a menu-bar app only because AppKit owns the main thread.)

Plan (draft)

Phase 0 — doable anywhere, testable on a checkout

  1. A build recipe (just build-linux-app / build-linux-tarball) that assembles the runtime + client + layouts into a relocatable tree.
  2. A first-run/install script that installs the udev rule, adds the input group, and writes the systemd user unit.
  3. Docs: install steps and the uinput caveat.

Phase 1 — on a Linux desktop
4. Run the artifact, fix packaging issues, verify the service + focus watcher + input injection.

Phase 2 — release pipeline
5. A GitHub Actions job that builds the artifact and attaches it to the release (mirror release-macos.yml).

Related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestspikeDesign-doc spike work (input injection, focus watcher, etc.)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions