- status.sh: runtime health check (service state, boot wiring, display backend auto-detect, encoder, ports, web UI, /dev/uinput, pairing) ending in a g2g verdict or concrete TODO list
- docs: TROUBLESHOOTING §12 (headless graphical-session.target boot trap) + §13 (X11/NVENC path & stale wlr drop-in env conflict); ARCHITECTURE capture-backend comparison; FOLLOWUPS P3 (installer X11/Ubuntu support); README diagnostics pointer
- lib/{config,packages,permissions,service}.sh, files/sway-headless.service: in-progress Debian/Ubuntu + headless support refinements
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
262 lines
10 KiB
Bash
262 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# Install Sunshine, Moonlight, and GPU-specific hardware-encode dependencies.
|
|
# Branches by $DISTRO (set by lib/distro.sh).
|
|
|
|
# --- Arch defaults --------------------------------------------------------
|
|
|
|
# Default to the precompiled AUR build for a fast install (~seconds instead of
|
|
# the ~10 minute source compile). Override with SUNSHINE_PKG=sunshine to build
|
|
# from source.
|
|
: "${SUNSHINE_PKG:=sunshine-bin}"
|
|
: "${MOONLIGHT_PKG:=moonlight-qt}"
|
|
|
|
# --- Debian/Ubuntu defaults ----------------------------------------------
|
|
|
|
# LizardByte ships official .deb builds per Ubuntu release on GitHub.
|
|
# Resolved at runtime by _ubuntu_sunshine_deb_url to match this host.
|
|
: "${SUNSHINE_DEB_URL:=}"
|
|
: "${SUNSHINE_DEB_VERSION:=latest}"
|
|
|
|
install_sunshine() {
|
|
case "$DISTRO" in
|
|
arch) _install_sunshine_arch ;;
|
|
debian) _install_sunshine_debian ;;
|
|
*) err "install_sunshine: unsupported distro '$DISTRO'"; return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# --- Arch implementation -------------------------------------------------
|
|
|
|
_install_sunshine_arch() {
|
|
# Ensure runtime deps useful for capture/diagnostics across vendors.
|
|
yay_install pipewire-pulse vulkan-tools libva-utils jq
|
|
|
|
# If a working sunshine (source build) is already installed, don't touch it.
|
|
# Otherwise re-running install.sh would replace the source build with
|
|
# sunshine-bin (the default $SUNSHINE_PKG), trip the ldd check, then rebuild
|
|
# from source — a redundant ~10 minute compile on every re-run.
|
|
if pkg_installed sunshine && sunshine_runtime_deps_ok; then
|
|
ok "sunshine (source build) already installed with all shared libs resolved"
|
|
return 0
|
|
fi
|
|
# Same idempotency for sunshine-bin when it happens to be working.
|
|
if pkg_installed sunshine-bin && sunshine_runtime_deps_ok; then
|
|
ok "sunshine-bin already installed and runtime deps resolved"
|
|
return 0
|
|
fi
|
|
|
|
yay_install "$SUNSHINE_PKG"
|
|
|
|
# sunshine-bin is a precompiled AUR package; it breaks whenever Arch bumps a
|
|
# major shared library (most commonly ICU). Detect that and fall back to the
|
|
# source build, which links against whatever's currently installed.
|
|
if ! sunshine_runtime_deps_ok; then
|
|
if [[ "$SUNSHINE_PKG" == "sunshine-bin" ]]; then
|
|
warn "sunshine-bin has unresolved shared library deps (rolling-Arch library drift)."
|
|
warn "Falling back to the source build (AUR 'sunshine'). This takes ~5-10 minutes."
|
|
ldd "$(command -v sunshine)" 2>/dev/null | grep 'not found' | sed 's/^/ /' >&2 || true
|
|
# Remove both sunshine-bin AND its sibling sunshine-bin-debug, otherwise
|
|
# the source build's sunshine-debug package collides on
|
|
# /usr/lib/debug/usr/bin/sunshine.debug.
|
|
for stale in sunshine-bin-debug sunshine-bin; do
|
|
if pacman -Qi "$stale" >/dev/null 2>&1; then
|
|
as_root pacman -Rns --noconfirm "$stale"
|
|
fi
|
|
done
|
|
SUNSHINE_PKG="sunshine"
|
|
yay_install sunshine
|
|
if ! sunshine_runtime_deps_ok; then
|
|
err "Source build also reports unresolved deps. Inspect: ldd \$(command -v sunshine)"
|
|
return 1
|
|
fi
|
|
ok "Recovered with source build"
|
|
else
|
|
err "sunshine binary has unresolved shared library deps:"
|
|
ldd "$(command -v sunshine)" 2>/dev/null | grep 'not found' | sed 's/^/ /' >&2 || true
|
|
err "Try: SUNSHINE_PKG=sunshine ./install.sh"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# --- Debian/Ubuntu implementation ----------------------------------------
|
|
|
|
# LizardByte's sunshine .deb assets are named per Ubuntu codename / version,
|
|
# e.g. sunshine-ubuntu-24.04-amd64.deb. Resolve the right one for this host.
|
|
_ubuntu_sunshine_deb_filename() {
|
|
local arch
|
|
arch="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
|
|
local v="${DISTRO_VERSION:-24.04}"
|
|
echo "sunshine-ubuntu-${v}-${arch}.deb"
|
|
}
|
|
|
|
# Resolve the download URL. If SUNSHINE_DEB_URL is set, honor it (escape hatch
|
|
# for offline mirrors / version pinning). Otherwise build a GitHub Releases
|
|
# URL — 'latest' uses the redirecting /latest/download/ alias.
|
|
_ubuntu_sunshine_deb_url() {
|
|
if [[ -n "$SUNSHINE_DEB_URL" ]]; then
|
|
echo "$SUNSHINE_DEB_URL"
|
|
return 0
|
|
fi
|
|
local file
|
|
file="$(_ubuntu_sunshine_deb_filename)"
|
|
if [[ "$SUNSHINE_DEB_VERSION" == "latest" ]]; then
|
|
echo "https://github.com/LizardByte/Sunshine/releases/latest/download/${file}"
|
|
else
|
|
echo "https://github.com/LizardByte/Sunshine/releases/download/${SUNSHINE_DEB_VERSION}/${file}"
|
|
fi
|
|
}
|
|
|
|
_install_sunshine_debian() {
|
|
# Universal runtime deps. vainfo is what Ubuntu calls libva-utils on Arch.
|
|
# pipewire-pulse is the Ubuntu 24.04+ default audio path; on older releases
|
|
# `pulseaudio-utils` works too — we don't force the codename split since
|
|
# sunshine just needs *a* PulseAudio API endpoint.
|
|
pkg_install jq vulkan-tools vainfo curl ca-certificates
|
|
|
|
if pkg_installed sunshine; then
|
|
ok "sunshine already installed (dpkg)"
|
|
return 0
|
|
fi
|
|
|
|
local url
|
|
url="$(_ubuntu_sunshine_deb_url)"
|
|
local tmpdir deb_path
|
|
tmpdir="$(mktemp -d /tmp/omarchy-sunshine.XXXXXX)"
|
|
# shellcheck disable=SC2064
|
|
trap "rm -rf '$tmpdir'" RETURN
|
|
deb_path="$tmpdir/$(_ubuntu_sunshine_deb_filename)"
|
|
|
|
info "Downloading Sunshine .deb: $url"
|
|
if ! curl -fL --retry 3 -o "$deb_path" "$url"; then
|
|
err "Failed to download $url"
|
|
err "If your Ubuntu version doesn't have a prebuilt .deb, set SUNSHINE_DEB_URL"
|
|
err "or SUNSHINE_DEB_VERSION (e.g. SUNSHINE_DEB_VERSION=v2025.118.84544 ./install.sh)."
|
|
return 1
|
|
fi
|
|
|
|
deb_install_local "$deb_path"
|
|
|
|
if ! command -v sunshine >/dev/null 2>&1; then
|
|
err "sunshine command not on PATH after install — package layout unexpected."
|
|
return 1
|
|
fi
|
|
ok "Installed sunshine from $(basename "$deb_path")"
|
|
}
|
|
|
|
# True if every shared library sunshine links against resolves on this system.
|
|
sunshine_runtime_deps_ok() {
|
|
local bin
|
|
bin="$(command -v sunshine 2>/dev/null || true)"
|
|
[[ -n "$bin" ]] || return 1
|
|
! ldd "$bin" 2>/dev/null | grep -q 'not found'
|
|
}
|
|
|
|
install_moonlight() {
|
|
case "$DISTRO" in
|
|
arch)
|
|
yay_install "$MOONLIGHT_PKG"
|
|
;;
|
|
debian)
|
|
# moonlight-qt is published as a PPA + flatpak. On a typical Ubuntu host
|
|
# the flatpak is the lowest-friction install path; falling back to apt
|
|
# requires adding the cloudsmith PPA. For a headless server (the primary
|
|
# Ubuntu target here) the client side is almost never wanted — so this
|
|
# is best-effort.
|
|
if pkg_installed moonlight-qt; then
|
|
ok "moonlight-qt already installed"
|
|
return 0
|
|
fi
|
|
if command -v flatpak >/dev/null 2>&1; then
|
|
info "Installing moonlight-qt via flatpak"
|
|
as_root flatpak install -y flathub com.moonlight_stream.Moonlight || {
|
|
warn "flatpak install of Moonlight failed — install it manually if needed."
|
|
}
|
|
else
|
|
warn "moonlight-qt: no apt package in Ubuntu's default repos and no flatpak available."
|
|
warn " Install flatpak first, or grab the .deb from https://github.com/moonlight-stream/moonlight-qt/releases"
|
|
warn " Skipping — headless hosts rarely need the client anyway."
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_gpu_encoder_packages() {
|
|
case "$DISTRO:$GPU_VENDOR" in
|
|
arch:nvidia)
|
|
yay_install nvidia-utils libva-nvidia-driver
|
|
;;
|
|
arch:amd)
|
|
# VAAPI (mesa) + Vulkan for AMD hardware encode paths.
|
|
# libva-mesa-driver is now provided by mesa (merged upstream); mesa-vdpau
|
|
# was removed from official repos. Naming them here makes yay fall back to
|
|
# AUR and pull in random forks like mesa-rk35xx-git that advertise
|
|
# `provides=(libva-mesa-driver mesa-vdpau)`.
|
|
yay_install mesa vulkan-radeon
|
|
;;
|
|
arch:intel)
|
|
yay_install intel-media-driver vulkan-intel
|
|
;;
|
|
debian:nvidia)
|
|
# Ubuntu's nvidia-driver-NNN(-server) metapackage pulls in the matching
|
|
# libnvidia-encode-NNN(-server) as a dependency, so NVENC is normally
|
|
# already present. Only intervene if it's missing — and derive the right
|
|
# package name from the loaded driver's major version instead of
|
|
# guessing.
|
|
if dpkg-query -W -f='${Status}\n' 'libnvidia-encode-*' 2>/dev/null \
|
|
| grep -q '^install ok installed$'; then
|
|
ok "NVENC runtime library already installed via the driver metapackage"
|
|
else
|
|
local drv_major drv_full
|
|
drv_full="$(nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits 2>/dev/null | head -n1)"
|
|
drv_major="${drv_full%%.*}"
|
|
if [[ -z "$drv_major" ]]; then
|
|
warn "Could not detect NVIDIA driver version; NVENC may be missing."
|
|
else
|
|
# Try -server first (cloud GPUs usually run server branches), then the
|
|
# consumer branch. Stop at the first one apt knows about.
|
|
local picked=""
|
|
for cand in "libnvidia-encode-${drv_major}-server" "libnvidia-encode-${drv_major}"; do
|
|
if apt-cache show "$cand" >/dev/null 2>&1; then
|
|
picked="$cand"; break
|
|
fi
|
|
done
|
|
if [[ -n "$picked" ]]; then
|
|
pkg_install "$picked"
|
|
else
|
|
warn "No libnvidia-encode-${drv_major}* package in apt — install it manually if NVENC fails."
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
debian:amd)
|
|
pkg_install mesa-va-drivers mesa-vulkan-drivers vainfo
|
|
;;
|
|
debian:intel)
|
|
pkg_install intel-media-va-driver-non-free mesa-vulkan-drivers
|
|
;;
|
|
*)
|
|
info "Unknown distro/GPU combination ($DISTRO:$GPU_VENDOR); skipping vendor-specific encoder packages."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Install a wlroots-based compositor for headless capture on systems without
|
|
# Hyprland. Currently means: Sway on Debian/Ubuntu. On Arch the existing
|
|
# Hyprland flow is the canonical path; we only fall back to Sway if Hyprland
|
|
# isn't installed (rare on Omarchy).
|
|
install_headless_compositor() {
|
|
case "$DISTRO" in
|
|
debian)
|
|
pkg_install sway wlr-randr
|
|
;;
|
|
arch)
|
|
# Hyprland is presumed installed on Omarchy. Only act if it's missing.
|
|
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
warn "hyprctl not found on Arch — falling back to Sway for headless capture."
|
|
yay_install sway wlr-randr
|
|
fi
|
|
;;
|
|
esac
|
|
}
|