Run hydraheadflatscreen heads on omarchy (https://github.com/basecamp/omarchy). Omarchy is an Arch-based distribution. It runs Hyprland on Wayland, with a Quickshell shell, SDDM plus uwsm for login, and pacman for packages. This issue records the full investigation and the work plan.
hydrahead-linux-amd64 to releases.experiencenet.com (.github/workflows/release.yml:46-51). The self-updater resolves that exact name at runtime and can download, verify, and install on Linux today. The release server needs no changes.pkg/client/moonlight.go.pkg/client/moonlight_linux.go launches moonlight-qt from PATH. Omarchy preinstalls moonlight-qt and ships a window rule (default/hypr/apps/moonlight.lua) that forces the app-id com.moonlight_stream.Moonlight fullscreen with idle-inhibit. Our Qt fork keeps that app-id on Linux (app/main.cpp:880-882), so the rule matches the fork as well.hydra-experiencenet (app/app.pro:4-5). Upstream moonlight-qt supports Wayland as a first-class target (VAAPI with libva-wayland, Vulkan via libplacebo, a Wayland vsync source). The custom kiosk code (kiosk CLI, KioskView.qml, LocalServer on :9741) is cross-platform Qt. The only macOS-specific custom file, platform/macos_permissions.mm, is correctly guarded, so a Linux build compiles today.cidata drive (with authorized_keys or a Tailscale key), grim gives scriptable screenshots with no portal or TCC ceremony, and systemd user services are the native autostart pattern.The two options are incompatible and gate most other work:
moonlight-qt --pin <PIN> pair --headless, and --headless is a fork-only flag. Stock moonlight-qt calls handleUnknownOptions() and exits (commandlineparser.cpp:53-57). Without native Linux cert storage (#278) there is no pairing path at all. Kiosk mode and the exit overlay also do not exist. Stream-only heads, and only after #278.hydra-experiencenet binary). Headless pairing and kiosk mode work, but CI must build a Linux artifact (only DMGs today), and the agent hardcodes the name moonlight-qt in three places in moonlight_linux.go: exec.LookPath (launch), pkill -x (stop), and pgrep -x (running detection). Deploy the fork and all three silently break. Fix the name handling or install a moonlight-qt symlink.Recommendation: use the fork. It keeps parity with the macOS heads (kiosk grid, headless pairing, exit overlay) and the omarchy window rule already matches its app-id.
internal/cli/install_linux.go:7-9 is a stub that returns "install is only supported on Windows" (the message is also stale, darwin is supported). Write a systemd --user unit named exactly hydraheadflatscreen (the name service_name_unix.go and the updater expect), ExecStart=<exe> run --config ~/.hydraheadflatscreen/config.yaml, Restart=always, WantedBy=graphical-session.target. Do not use linger: linger conflicts with a graphical-session binding, and the agent is useless without the Hyprland session anyway. SDDM autologin provides the session at boot. Mirror in uninstall_linux.go.pkg/updater/restart_unix.go:11 runs system-scope systemctl restart hydraheadflatscreen. That fails for a user unit, so every auto-update would leave the old binary running. Add a systemctl --user variant (or detection) in hydrarelease, release it, and bump the dependency.moonlight-qt to the fork binary (see decision above).moonlight_cert_linux.go). Read and write moonlight-qt's QSettings ini (~/.config/Moonlight Game Streaming Project/Moonlight.conf) so native pairing works without the subprocess fallback. This closes #278 for Linux.GET /api/v1/screenshot serves /tmp/hydra-live-screenshot.jpg (localapi.go:412-445). On macOS a Terminal screencapture loop under TCC produces it. On omarchy a grim -t jpeg loop in the user session does the same with no permission ceremony. Note: grim bakes software-composited cursors into frames, and capture fails while the shell lock screen is up.screensaver_linux.go is an empty no-op. Important: omarchy v4 removed hypridle/hyprlock. Its Quickshell idle service honors ONLY the Wayland idle-inhibit protocol and omarchy's own flag files. systemd-inhibit and the org.freedesktop.ScreenSaver D-Bus path do nothing. During streams the shipped window rule (idle_inhibit = "fullscreen") covers us. Between streams, set the timeouts in ~/.config/omarchy/shell.json or set the flag files (omarchy toggle idle stay-awake, omarchy toggle screensaver) at provision time.tunnel_linux.go ensureWireGuardTunnel() is empty. Implement a check plus bring-up (hydraguard binary or wg-quick@hydraguard-air), and extend diagnostics.go:96 beyond darwin. Open question: hydranode and hydraguard are unverified on Arch, and the whole enrollment recipe (macOS TCC steps) needs a Linux equivalent. The Linux recipe is simpler: no TCC means no physical-access step.autologin_linux.go hardcodes true. Omarchy uses SDDM. Check /etc/sddm.conf.d/autologin.conf. Note: unencrypted omarchy installs get NO autologin by default (encrypted installs get it, the LUKS prompt is the auth boundary). Kiosk provisioning must write the file itself.micrelay_linux.go reports unsupported, streams work without it. If voice experiences are wanted, mirror the Windows shape: ffmpeg with a PipeWire/Pulse source, Opus RTP to bodyIP:47995.pkill/pgrep -x, so a manually started client is not mistaken for the agent's stream.scripts/build-appimage.sh exists upstream but is not wired in. Add a Linux job (AppImage or Arch tarball) and publish it. Arch build deps: qt6-base, qt6-declarative, qt6-svg, qt6-wayland, sdl2-compat, sdl2_ttf, ffmpeg, libva, libvdpau, opus, openssl, libplacebo, libx11, libdrm. Build with qmake6 after git submodule update --init --recursive.QT_QPA_PLATFORM=xcb), intermittent stream-window open failure (#1201), Wayland crashes with a Force-XWayland workaround (#1721). XWayland is present and configured on omarchy, so xcb is the safe default. Validate Wayland-native later.StreamOverlay.qml:39 relies on Qt.WindowStaysOnTopHint, which Wayland ignores, and the follow-all-Spaces trick no-ops off macOS. Use a Hyprland window rule (float plus pin) or a layer-shell surface to keep the overlay above the stream.showFullScreen()+raise()+requestActivate() (localserver.cpp:140-161) depends on compositor activation policy. Verify the kiosk-hide, stream, kiosk-show cycle under Hyprland and add window rules as needed.qtapp_linux.go must install and launch the Linux binary (tarball or AppImage, not DMG), and startKiosk() must stop returning "kiosk mode not supported on Linux"./etc/sddm.conf.d/autologin.conf themselves.hl.*/o.* Lua in ~/.config/hypr/*.lua, not hyprland.conf. Any tooling that writes monitor rotation, window rules, or autostart must emit Lua, or use hyprctl eval / hyprctl keyword at runtime.omarchy toggle bar), silence notifications, and override the Super-key bindings. Caution: "Remove > Preinstalls" also removes moonlight-qt.hl.monitor({ transform = 1 }).omarchy reinstall resets everything. The agent must re-assert its autostart, window rules, shell.json, and autologin state idempotently on boot.cidata installs with authorized_keys (enables sshd plus a firewall hole) or a Tailscale key fit the fleet model. The ufw firewall default-denies inbound; the local API binds 127.0.0.1 only, so define the cluster access path explicitly. x86_64 only, Secure Boot and TPM off; no Apple Silicon, Intel Macs are supported.omarchy update (plain pacman -Syu is guarded). Stable channel lags Arch by a month, snapper snapshots give rollback. Verify it runs non-interactively before the agent drives it; otherwise updates stay an operator action.Write an omarchy E2E testbook per the runbook-plus-testbook rule: unattended install, enroll, pair, stream, kiosk hide/show cycle, self-update with user-unit restart, reboot survival. Also fix the runbook: docs/runbooks/runbook.md:26 has a wrong manual-download URL (missing /production/vX.Y.Z/, and uname -m gives x86_64 while artifacts say amd64), and the Linux rows reference a systemd service that item 1 must first create.
We use the hydra-experiencenet fork on Linux heads, for headless autopairing and kiosk parity with macOS. Stock moonlight-qt is out.
Updater consequence: extend the existing QtAppUpdater (pkg/client/qtapp.go), do not add a new mechanism.
This supersedes work item 3 (binary name handling: solved via getMoonlightExe) and narrows item 4 (#278 native cert storage becomes optional hardening, not a blocker, since fork headless pairing covers enrollment).
Node node-5e0ea2c8 (cranky-toaster-86), a 2013 MacBook Air (i5-4250U, Haswell-ULT) running omarchy, is enrolled and validated end to end:
hydraguard air add on the hub plus a wg-quick@hydraguard-air systemd unit on the head. The Linux WG story is plain wg-quick; ensureWireGuardTunnel() can check that unit.moonlight --pin <PIN> pair <host> plus a POST of the PIN to Sunshine /api/pin. No fork needed for enrollment-time pairing when driven this way (a Qt window opens on the display during pairing, acceptable at enrollment).New constraints found:
--video-codec HEVC in pkg/client/moonlight.go; Linux heads need H.264 (per-head codec config, or capability detection).Commit 8866468, tag v2.2.0. Native GameStream pairing now runs in-process on Linux: moonlight_cert_linux.go reads and writes moonlight-qt's QSettings ini, generates the client identity on fresh heads, and writes host certs UUID-first. Validated on cranky-toaster-86: fresh pair against cosmic-pretzel-98 completed in about one second with no display, and stock /usr/bin/moonlight streamed with the agent-written identity (grim screenshot proof). This resolves work items 1, 3, 4, 5, 6, 7 (monitor side), 8, 9, 10 and the #495 race class for the primary path (subprocess pairing remains the fallback).
Also shipped: systemd --user install/uninstall, AppImage installer for the managed fork, built-in grim EYES loop, omarchy idle flag files, SDDM autologin check, PipeWire mic relay, WireGuard heartbeat diag, and a cross-platform 'pair ' CLI command. Runbook has a new "Linux heads (omarchy)" section.
Two findings from validation that stay open:
Remaining work: fork AppImage CI job, codec selection, provisioning recipe (hydracluster recipe for Arch/omarchy), E2E testbook.
Remaining, tracked separately: #500 final on-device verification, omarchy stay-awake flag behavior, HandleLidSwitch for laptop heads, #499 naming decision.