Files
Omarchy-Stream/README.md
Levi Woodard d6b0919149 Optimize installer: preflight checks, tuned conf, doctor, faster default
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.
2026-05-18 10:17:11 -06:00

6.7 KiB

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:

  1. Log out and back in if you weren't already in the input group (the installer adds you; the group only takes effect on a fresh login).
  2. Open https://localhost:47990 and set a Sunshine username + password.
  3. 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:

  1. Preflight — confirms Wayland session, GPU driver is loaded, nvidia-drm.modeset=1 isn't explicitly disabled (would silently break KMS capture), pipewire-pulse is present for audio.
  2. Packages — installs sunshine-bin (precompiled, fast) and moonlight-qt from the AUR via yay. Plus runtime helpers: pipewire-pulse, vulkan-tools, libva-utils. Use --from-source to build Sunshine from source instead.
  3. GPU encoder support:
    • NVIDIA: nvidia-utils, libva-nvidia-driver
    • AMD: libva-mesa-driver, mesa-vdpau, vulkan-radeon
    • Intel: intel-media-driver, vulkan-intel
  4. Permissions:
    • Adds you to the input group
    • Drops /etc/udev/rules.d/60-uinput.rules if no equivalent rule exists (lets Sunshine use /dev/uinput for virtual gamepad/keyboard/mouse)
    • setcap cap_sys_admin+p on the sunshine binary so KMS screen capture works without root
  5. Tuned config — writes ~/.config/sunshine/sunshine.conf for 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.
  6. Firewall — opens Sunshine's ports on firewalld / ufw if either is active. Skips silently otherwise.
  7. Service — enables sunshine.service under systemd --user and turns on loginctl enable-linger so the host is reachable without an active graphical login.
  8. Verify — runs the same checks as --doctor to 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
  • 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 input group in a freshly logged-in session (not just listed in /etc/group).
  • Confirm getcap shows cap_sys_admin on the sunshine binary.
  • Check journalctl --user -u sunshine for KMS / DRM errors.
  • On NVIDIA: confirm the proprietary driver is active (nvidia-smi) and that nvidia-drm.modeset=1 is in effect. If you have an older driver (≤555) and modeset isn't on, add nvidia-drm.modeset=1 to 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)