#!/usr/bin/env bash # Permissions needed for Sunshine on Wayland: # - user in 'input' group (so /dev/uinput is usable for virtual gamepad/keyboard/mouse) # - udev rule granting 'input' group access to /dev/uinput # - cap_sys_admin on the sunshine binary (so KMS capture works without root) UINPUT_RULE_PATH="/etc/udev/rules.d/60-uinput.rules" UINPUT_RULE_CONTENT='KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess", OWNER="root", GROUP="input", MODE="0660"' ensure_input_group() { _ensure_user_in_group input # On Debian/Ubuntu, /dev/dri/renderD* nodes are mode 0660 owned by # root:render, and /dev/dri/card* are root:video. Sway's wlroots renderer # needs the render node (Vulkan/GLES FD); KMS capture needs the card node. # Arch typically grants both via udev tag=uaccess for the logged-in seat, # so we only add explicit memberships on Debian. if [[ "${DISTRO:-}" == "debian" ]]; then _ensure_user_in_group render _ensure_user_in_group video fi } # Internal: add $USER to a group if it exists and they're not already in it. _ensure_user_in_group() { local g="$1" if ! getent group "$g" >/dev/null 2>&1; then info "Group '$g' does not exist on this system — skipping." return 0 fi if id -nG "$USER" | tr ' ' '\n' | grep -qx "$g"; then ok "User '$USER' already in '$g' group" return 0 fi info "Adding '$USER' to '$g' group" as_root usermod -aG "$g" "$USER" warn "Group '$g' change takes effect on next login (or 'newgrp $g'). For systemd-user" warn "services, you must fully log out and back in so the user manager restarts." } ensure_uinput_udev_rule() { # The sunshine package may ship its own rule under /usr/lib/udev/rules.d/. # If a usable rule already exists anywhere udev looks, do nothing. if grep -rqs 'KERNEL=="uinput"' /etc/udev/rules.d /usr/lib/udev/rules.d /run/udev/rules.d 2>/dev/null; then ok "uinput udev rule already present" return 0 fi info "Writing $UINPUT_RULE_PATH" echo "$UINPUT_RULE_CONTENT" | as_root tee "$UINPUT_RULE_PATH" >/dev/null as_root udevadm control --reload-rules as_root udevadm trigger --subsystem-match=misc --action=change || true } set_sunshine_capabilities() { local bin bin="$(command -v sunshine || true)" if [[ -z "$bin" ]]; then err "sunshine binary not found on PATH after install; cannot set capabilities." return 1 fi # Follow symlinks (e.g., /usr/bin/sunshine may itself be a real file; harmless to readlink -f). bin="$(readlink -f "$bin")" local current current="$(getcap "$bin" 2>/dev/null || true)" if [[ "$current" == *"cap_sys_admin"* ]]; then ok "sunshine binary already has cap_sys_admin set" return 0 fi info "Setting cap_sys_admin+p on $bin (required for KMS capture)" as_root setcap cap_sys_admin+p "$bin" }