initial commit
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
# Headless Server Setup for Game Streaming
|
||||
|
||||
Full pipeline for running Sunshine + games on a headless Linux server (no monitor attached).
|
||||
|
||||
## Required components
|
||||
|
||||
1. **HDMI dummy plug** ($5-10) — plugs into GPU HDMI port, emulates a 4K display. Without it, NVIDIA GPUs run in low-power mode with reduced clocks and may refuse to render at full speed. Essential for headless setups.
|
||||
|
||||
**EDID reality check:** Many "4K/120" dummy plugs (e.g., FUEARAN) advertise 4K@120Hz on the label but their EDID caps 4K at 60Hz. Actual high-refresh options are 1440p@120Hz and 1080p@120Hz. Check with:
|
||||
```bash
|
||||
DISPLAY=:0 xrandr --query | grep -A 20 "^HDMI-0" | grep -E "^[ ]+[0-9]"
|
||||
```
|
||||
If 4K@120Hz is absent despite the label, the plug hardware doesn't support it — plan your game resolutions accordingly.
|
||||
|
||||
2. **Xorg** — needs to run headless pointing at the dummy display, and persist across reboots.
|
||||
|
||||
**Verify the dummy display:**
|
||||
```bash
|
||||
for conn in /sys/class/drm/card0-*/status; do echo "$conn: $(cat $conn)"; done
|
||||
# Look for "connected" — that's your dummy plug
|
||||
cat /sys/class/drm/card0-HDMI-A-1/modes | head -1 # should show 3840x2160 or similar
|
||||
```
|
||||
|
||||
**Start Xorg (manual test):**
|
||||
```bash
|
||||
sudo Xorg :0 -ac -nolisten tcp vt7 &
|
||||
sleep 2
|
||||
DISPLAY=:0 xdpyinfo | grep dimensions # verify
|
||||
```
|
||||
|
||||
**Xorg persistence (systemd system service):**
|
||||
Xorg needs root for VT access on headless servers. Create a system service so it survives reboots:
|
||||
```ini
|
||||
# /etc/systemd/system/xorg-headless.service
|
||||
[Unit]
|
||||
Description=Xorg display server for headless game streaming
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/Xorg :0 -ac -nolisten tcp vt7
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
Then: `sudo systemctl daemon-reload && sudo systemctl enable --now xorg-headless`
|
||||
|
||||
3. **Sunshine** — game streaming server. See main SKILL.md for install.
|
||||
|
||||
4. **Game launchers:**
|
||||
- **Steam (+ Proton):** Requires 32-bit architecture on Ubuntu 26.04+.
|
||||
```bash
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt update
|
||||
sudo apt install -y steam-installer
|
||||
```
|
||||
**Headless blocker — zenity license dialog:** The `steam-installer` package installs a `/usr/games/steam` wrapper script that, on first run, pops up a zenity license dialog (`--ok-label=Install --cancel-label=Cancel`). On a headless display, nobody can click "Install", so the Steam runtime never downloads. **Do not attempt debconf preseeding** — the zenity check (lines 113–139 of `/usr/games/steam`) is hardcoded and ignores debconf.
|
||||
|
||||
**Workaround — bootstrap Steam manually:** Run the `scripts/setup-headless-steam.sh` script included in this skill. It automatically detects the required version, downloads the bootstrap tarball, unpacks the embedded runtime, and links the directories so the GUI license dialog is bypassed.
|
||||
|
||||
After these steps, `DISPLAY=:0 steam` launches without any dialog. First run downloads the full Steam client (~200-500 MB) and displays a self-updating progress bar (zenity `--progress --auto-close`). The client goes through a multi-stage bootstrap: update download → restart → runtime unpack → restart → login screen. This takes 3-5 minutes on a fast connection. Steam must be running and logged in for Sunshine's "Steam Big Picture" app to work.
|
||||
|
||||
Proton is built into Steam — enable in Settings → Steam Play. Handles ~85% of Windows games.
|
||||
- **Lutris:** `sudo apt install lutris`. For non-Steam games: GOG, Epic Games Store, Battle.net, emulators.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Headless server (no monitor)
|
||||
↳ HDMI dummy plug → GPU sees "4K display" → full clocks enabled
|
||||
↳ Xorg virtual desktop on dummy display
|
||||
↳ Game launches (Steam/Lutris)
|
||||
↳ Sunshine captures X11 display via NVENC
|
||||
↳ Moonlight on client device streams it
|
||||
```
|
||||
|
||||
## GPU requirements
|
||||
|
||||
- **NVIDIA GPU with NVENC** (Turing or newer: GTX 1650 Super+, RTX 20-series+, RTX 30-series+, RTX 40-series+)
|
||||
- Consumer cards limited to 3 concurrent NVENC sessions
|
||||
- NVENC is dedicated silicon — negligible FPS hit during encoding
|
||||
|
||||
## Linux game compatibility
|
||||
|
||||
| Type | How | Compatibility |
|
||||
|---|---|---|
|
||||
| Native Linux games | Steam → Install → Play | 100% |
|
||||
| Windows games (Proton) | Steam → Enable Steam Play → Install | ~85% works out of box, ~10% needs tweaks, ~5% broken |
|
||||
| Anti-cheat games | Valorant, CoD, Fortnite, Destiny 2 | 0% — kernel-level anti-cheat incompatible with Proton |
|
||||
| Non-Steam games | Lutris (GOG, Epic, Battle.net) | Varies, mostly good |
|
||||
|
||||
ProtonDB (protondb.com) is the definitive compatibility reference for any specific game.
|
||||
|
||||
## Audio setup (headless)
|
||||
|
||||
Headless servers have no physical speakers and often no audio system at all. Sunshine needs an audio sink to capture game audio and stream it to Moonlight.
|
||||
|
||||
### Install PipeWire
|
||||
|
||||
```bash
|
||||
sudo apt install -y pipewire pipewire-pulse wireplumber pulseaudio-utils
|
||||
```
|
||||
|
||||
This gives you a PulseAudio-compatible interface over PipeWire 1.6+. The `auto_null` (Dummy Output) sink is created automatically — it accepts audio from games and makes it available for Sunshine capture without needing physical hardware.
|
||||
|
||||
### User must be in `audio` group
|
||||
|
||||
```bash
|
||||
sudo usermod -a -G audio $USER
|
||||
# Requires re-login or systemctl --user daemon-reexec to take effect
|
||||
```
|
||||
|
||||
Without the audio group, `/dev/snd/*` devices are permission-denied and PipeWire can't enumerate ALSA hardware. The `auto_null` sink still works for capture, but NVIDIA HDMI audio (from the dummy plug's EDID) won't appear as a proper hardware sink until the group is active.
|
||||
|
||||
### Sunshine config for headless dummy plug
|
||||
|
||||
**Required config — tell Sunshine which display to capture:**
|
||||
Without `output_name`, Sunshine defaults to display index 0 (DP-0), which may be disconnected even with a dummy plug in HDMI-0. The log shows:
|
||||
```
|
||||
Configuring selected display (0) to stream
|
||||
Couldn't get requested display info, defaulting to recording entire virtual desktop
|
||||
```
|
||||
Fix: add to `~/.config/sunshine/sunshine.conf`:
|
||||
```
|
||||
output_name = 2
|
||||
```
|
||||
|
||||
⚠️ **IMPORTANT:** `output_name` takes a **numeric display index**, NOT the connector name string. Passing `HDMI-0` as the value causes Sunshine to fail with `Could not stream display number, there are only [8] displays.`
|
||||
|
||||
Determine the correct index from Sunshine's own log output:
|
||||
```bash
|
||||
grep "Detected display" ~/.config/sunshine/sunshine.log
|
||||
# Detected display: DP-0 (id: 0)DP-0 connected: false
|
||||
# Detected display: DP-1 (id: 1)DP-1 connected: false
|
||||
# Detected display: HDMI-0 (id: 2)HDMI-0 connected: true ← use output_name = 2
|
||||
# Detected display: DP-2 (id: 3)DP-2 connected: false
|
||||
```
|
||||
|
||||
The `(id: N)` value is the zero-based display index. Only one display should be `connected: true` on a headless system with a single dummy plug. After setting, verify Sunshine captures the correct display:
|
||||
```
|
||||
Streaming display: HDMI-0 with res 3840x2160 offset by 0x0
|
||||
```
|
||||
|
||||
This log line confirms Sunshine is streaming the right display. Required alongside `capture = x11` for NVIDIA headless setups.
|
||||
|
||||
**Do NOT set audio sink config:**
|
||||
`audio_sink` or `virtual_sink` in `sunshine.conf` causes an immediate fatal crash (`pa_simple_new() failed: Invalid argument`) on Sunshine v2026+. Let Sunshine auto-detect PipeWire instead.
|
||||
|
||||
**CRITICAL FIX FOR CRASHING STREAMS:** If Sunshine still crashes with `Invalid argument` upon stream start, PipeWire is rejecting Sunshine's attempt to dynamically create virtual surround sinks. You must pre-create them so Sunshine finds them instead of crashing:
|
||||
|
||||
```bash
|
||||
pactl load-module module-null-sink sink_name=sink-sunshine-stereo
|
||||
pactl load-module module-null-sink sink_name=sink-sunshine-surround51 channels=6 channel_map=front-left,front-right,front-center,lfe,rear-left,rear-right
|
||||
pactl load-module module-null-sink sink_name=sink-sunshine-surround71 channels=8 channel_map=front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right
|
||||
```
|
||||
*(Run these commands before Sunshine starts, e.g. in a wrapper script or systemd ExecStartPre).*
|
||||
|
||||
### Verify audio
|
||||
|
||||
```bash
|
||||
pactl info | grep "Default Sink" # should show auto_null
|
||||
pactl list short sinks # should show auto_null, SUSPENDED
|
||||
journalctl --user -u sunshine | grep -i audio # should have no warnings/errors
|
||||
```
|
||||
|
||||
The earlier "Warning: There will be no audio" in sunshine logs should be gone after this config.
|
||||
|
||||
### Post-reboot: NVIDIA HDMI audio
|
||||
|
||||
After a reboot (once the `audio` group takes effect), WirePlumber discovers the NVIDIA HDMI audio device exposed by the dummy plug. An additional sink like `alsa_output.pci-0000_01_00.1.hdmi-stereo` appears. Either sink works — games output to the default sink and Sunshine captures whichever one is configured. The real HDMI sink has the advantage of proper EDID-negotiated sample rates (up to 96kHz on the FUERAN dummy plug).
|
||||
|
||||
## VRAM cohabitation strategy
|
||||
|
||||
When the server also runs LLM inference:
|
||||
|
||||
| Scenario | Solution |
|
||||
|---|---|
|
||||
| Model always loaded, game occasionally | Socket-activated llama-server (see llama-cpp skill, deployment-patterns.md). Model unloads when idle, 3-4s warm restart. |
|
||||
| Gaming always, model occasionally | Stop llama-server before gaming. Start manually when needed. |
|
||||
| Both simultaneously | Only works if VRAM fits both — e.g., 24 GB card with a 7B model (~4.7 GB) leaves ~19 GB for games. |
|
||||
|
||||
## Testing before buying hardware
|
||||
|
||||
- Verify `nvidia-smi` shows NVENC support (`nvidia-smi -q | grep -A5 Encoder`)
|
||||
- Run Sunshine and confirm web UI at `https://localhost:47990`
|
||||
- Install Moonlight on any device and verify auto-discovery works
|
||||
Reference in New Issue
Block a user