Layers a few things on top of the initial scaffold: - Default to sunshine-bin (precompiled, ~seconds) instead of building from source. --from-source restores the old behavior. - Add lib/preflight.sh: catches the gotchas before any work is done — Wayland session, NVIDIA driver responsive, nvidia-drm.modeset, amdgpu loaded, pipewire-pulse present, SSH-without-session warning. - Add lib/config.sh: writes a tuned ~/.config/sunshine/sunshine.conf with per-vendor encoder settings (NVENC P1+ll+cbr, VAAPI ultralowlatency, QuickSync veryfast), KMS capture, pulse audio sink. Uses a "# managed-by: omarchy-moonlight" marker; removing it hands ownership back to the user and the installer won't touch the file again. - Add lib/verify.sh: post-install verification of every step (cap_sys_admin set, group resolves, udev rule present, /dev/uinput exists, encoder reachable, service active, :47990 listening). Same checks are reachable standalone via --doctor. - Install runtime helpers (pipewire-pulse, vulkan-tools, libva-utils) alongside Sunshine for diagnostics + audio. - Uninstall handles both sunshine + sunshine-bin and the -bin moonlight variant. - README documents the tuning table, the new flags, and the modeset troubleshooting path.
omarchy-moonlight
Idempotent install scripts that set up Sunshine (host) and Moonlight (client) on Omarchy / Arch Linux / Hyprland / Wayland machines.
Designed so the same script runs on:
- a primary NVIDIA desktop (host + client)
- a Framework AMD laptop (host + client)
- any other Omarchy box
After install, the machine can both stream out (via Sunshine) and view streams (via Moonlight). A Macbook can install Moonlight separately and connect to either Linux host.
Quick start
git clone <this-repo> ~/omarchy-moonlight
cd ~/omarchy-moonlight
./install.sh
Then:
- Log out and back in if you weren't already in the
inputgroup (the installer adds you; the group only takes effect on a fresh login). - Open https://localhost:47990 and set a Sunshine username + password.
- On a Moonlight client (Mac / phone / the other Linux box), add this host by LAN IP and enter the PIN that Sunshine's web UI shows during pairing.
What it does
In order, each step is "check, then act" — re-running is safe:
- Preflight — confirms Wayland session, GPU driver is loaded,
nvidia-drm.modeset=1isn't explicitly disabled (would silently break KMS capture), pipewire-pulse is present for audio. - Packages — installs
sunshine-bin(precompiled, fast) andmoonlight-qtfrom the AUR viayay. Plus runtime helpers:pipewire-pulse,vulkan-tools,libva-utils. Use--from-sourceto build Sunshine from source instead. - GPU encoder support:
- NVIDIA:
nvidia-utils,libva-nvidia-driver - AMD:
libva-mesa-driver,mesa-vdpau,vulkan-radeon - Intel:
intel-media-driver,vulkan-intel
- NVIDIA:
- Permissions:
- Adds you to the
inputgroup - Drops
/etc/udev/rules.d/60-uinput.rulesif no equivalent rule exists (lets Sunshine use/dev/uinputfor virtual gamepad/keyboard/mouse) setcap cap_sys_admin+pon thesunshinebinary so KMS screen capture works without root
- Adds you to the
- Tuned config — writes
~/.config/sunshine/sunshine.conffor low-latency LAN streaming. Per-vendor encoder settings (NVENC P1+ll, VAAPI ultralowlatency, QuickSync veryfast). Marked with# managed-by: omarchy-moonlight; remove that marker to take ownership and the installer will never touch it again. - Firewall — opens Sunshine's ports on
firewalld/ufwif either is active. Skips silently otherwise. - Service — enables
sunshine.serviceundersystemd --userand turns onloginctl enable-lingerso the host is reachable without an active graphical login. - Verify — runs the same checks as
--doctorto confirm everything's actually wired up (cap_sys_admin set, group resolved, web UI listening on :47990, encoder reachable).
Flags
./install.sh --no-autostart # install but don't enable the user service
./install.sh --no-firewall # skip firewall rules
./install.sh --no-moonlight # host-only (no client)
./install.sh --no-sunshine # client-only (no host)
./install.sh --no-config # leave Sunshine to generate its own default config
./install.sh --from-source # build Sunshine from source (slower; uses 'sunshine' AUR pkg)
./install.sh --doctor # run only the verification checks (no install)
The doctor flag is the fastest way to debug a degraded install — it'll tell you exactly which piece (group, cap, udev, encoder, service, port) is broken.
Tuned defaults written to sunshine.conf
| Setting | Value | Why |
|---|---|---|
capture |
kms |
Correct backend for Wayland; uses DRM directly. |
encoder (NVIDIA) |
nvenc + nvenc_preset=p1, nvenc_tune=ll, nvenc_rc=cbr |
P1 minimizes encode latency; low-latency tune disables look-ahead; CBR keeps bitrate predictable over LAN. |
encoder (AMD) |
vaapi + amd_usage=ultralowlatency, amd_rc=cbr |
Mirrors the NVIDIA choices on AMD's encoder. |
min_threads |
4 |
Helps keep up at high bitrates / 4K. |
audio_sink |
pulse |
Captures from PipeWire's Pulse compat layer. |
Anything else (resolution, bitrate, paired clients, app launchers) is set via the web UI.
Uninstall
./uninstall.sh # remove packages + udev rule, keep user data
./uninstall.sh --purge # also delete ~/.config/sunshine
How streaming works once it's set up
- Host (Sunshine) ports (auto-opened if a firewall is active):
- TCP:
47984 47989 47990 48010 - UDP:
47998 47999 48000 48010
- TCP:
- Pairing: on first connect, Moonlight shows a PIN. Type it into Sunshine's web UI (https://localhost:47990 → PIN tab) within a few seconds.
- Capture mode: this script configures KMS capture, which streams whatever is on the host's real monitor. A virtual-display mode (so streaming doesn't take over the desk) is a future addition — see
remote/notes when it lands.
Diagnostics
./install.sh --doctor # run all checks
systemctl --user status sunshine
journalctl --user -u sunshine -f
getcap "$(readlink -f "$(command -v sunshine)")" # should include cap_sys_admin
id -nG | tr ' ' '\n' | grep -x input # confirm group membership
If Moonlight pairs but the stream is black:
- Confirm you're in the
inputgroup in a freshly logged-in session (not just listed in/etc/group). - Confirm
getcapshowscap_sys_adminon the sunshine binary. - Check
journalctl --user -u sunshineforKMS/DRMerrors. - On NVIDIA: confirm the proprietary driver is active (
nvidia-smi) and thatnvidia-drm.modeset=1is in effect. If you have an older driver (≤555) and modeset isn't on, addnvidia-drm.modeset=1to your kernel cmdline and reboot.
Remote access (planned)
LAN-only for now. Remote access will be added later via one of: Tailscale, WireGuard, or Cloudflare. See remote/ (stub) when implemented.
Layout
omarchy-moonlight/
├── install.sh
├── uninstall.sh
├── README.md
├── lib/
│ ├── common.sh # logging, sudo, idempotency helpers
│ ├── detect.sh # GPU vendor, session type, hostname
│ ├── preflight.sh # pre-install sanity checks (driver, modeset, audio, session)
│ ├── packages.sh # yay -S sunshine-bin moonlight-qt + GPU encoders
│ ├── permissions.sh # input group, uinput udev, setcap cap_sys_admin
│ ├── config.sh # writes tuned sunshine.conf (managed-by marker)
│ ├── firewall.sh # ufw/firewalld detection + port opening
│ ├── service.sh # systemctl --user enable + loginctl enable-linger
│ └── verify.sh # post-install checks (also reused by --doctor)
└── files/ # (reserved — drop-in config files if needed later)