steam-streaming-config
Steam / Moonlight streaming rig (CT 104, michelle-steam)
Sunshine + Moonlight game streaming with NVENC, plus an emulator stack that auto-imports ROMs into Steam. Host: Proxmox 192.168.1.96, CT IP 192.168.1.140.
Hard constraints - read before changing anything
The container MUST stay privileged (unprivileged: 0, lxc.cap.drop: empty).
NVIDIA + NvFBC + uinput all depend on it. Converting it breaks the whole stack.
Do not remove picom. It adds latency, but Steam Big Picture's steamwebhelper
(CEF) silently dies without a compositing manager and Steam does not respawn it -
you get a black screen and must restart Steam entirely. The fix is
/etc/xdg/picom.conf with vsync = false and unredir-if-possible = true, so
fullscreen games bypass the compositor instead.
Device majors are dynamic. 104.conf allows 195, 510, 511; today nvidia-uvm is
511, 510 is stale, and nvidia-caps (236) is not listed at all - it only works
because the container is privileged. Re-check grep nvidia /proc/devices after any
driver change or host move.
NVIDIA userspace here is pinned to driver 595.84 (libnvidia-encode.so.595.84).
The host driver must match exactly.
The ZFS dataset must mount at /georgia (mp0: /georgia/,mp=/mnt/). Every
emulator path is relative to it.
csrf_allowed_origins hardcodes https://192.168.1.140:47990 - the Sunshine web
UI breaks if the container IP changes.
Latency findings (2026-09-05)
The encoder was never the problem. In order of impact:
min_log_level = verbosein sunshine.conf - several lines per frame to disk and journald inside the encode loop; log had reached 140 MB growing 62 KB/s. Nowinfo.- picom - see above, config fix not removal.
- Fragmented cpuset:
cores: 8gave0,2-7,10= 2 whole cores + 4 half-cores on a (0,6)(1,7)(2,8)... sibling map. Nowcores: 12/ cpuset0-11. - RAM 8192 -> 16384 MB.
Not the cause, despite appearances: CPU quota (cpu.max is max, nr_throttled 0),
software encoding (NVENC always used), WiFi (client is wired). Sent Frame ... Dupe
spam is normal idle behaviour.
Operational gotchas
pct set 104 -cores Ndoes NOT re-apply cpuset to a running container. Write/sys/fs/cgroup/lxc/104/cpuset.cpusdirectly or restart the CT.- Restarting
sunshine.servicekills any running game - Sunshine launches Steam. - Never
pkill -fa steam pattern viapct exec 104 -- bash -c '...'; the pattern matches the pct exec cmdline and kills your own shell, taking Sunshine with it. Push a script in and runpct exec 104 -- bash /tmp/x.sh. - Relaunch Steam:
runuser -u steam -- env DISPLAY=:1 XDG_RUNTIME_DIR=/run/user/1001 setsid /bin/steam -bigpicture - Black-screen health check:
pgrep -a steamwebhelpermust exist, andDISPLAY=:1 xwininfo -root -children | grep 1920x1080must show full-size windows. - There is a DECOY
/root/.config/sunshine/sunshine.confthat is not used. The service runs assteam; edit/home/steam/.config/sunshine/sunshine.conf. - Sunshine's virtual gamepad only exists WHILE a stream is active. Configure pads
during a session. It is named after whatever the client reports, so hardcoded
Device =lines break when the client controller changes. - Harmless log noise: nouveau driver load failure, glx dri3 screen failure, dbus-launch not found, av1_nvenc probe failure (Ampere has no AV1 encoder).
Emulator stack (/opt/emu)
Steam ROM Manager does not work here - do not retry the AppImage. Electron's
renderer will not start in this container; eight invocation variants all hung or
died with SIGTRAP. /opt/emu/emu-sync.py writes Steam's binary shortcuts.vdf
directly and pulls artwork from SteamGridDB instead. No display, no browser engine.
emu-sync.py- idempotent; owns only ids tracked instate/managed.json. Returns 75 (EX_TEMPFAIL) if Steam is running, because Steam caches shortcuts.vdf in memory and overwrites it on exit. Units setSuccessExitStatus=0 75.emu-update.py- weekly AppImage refresh. Supportsasset_pattern(regex) for versioned filenames,prerelease, andsource: rpcs3-api.- Units:
emu-sync.path(inotify, works because the share is local ZFS),emu-sync.timer(15 min),emu-update.timer(Sun 04:00). - Web console: http://192.168.1.140:8090 (
emu-console.service). Root, LAN-bound, no auth. Port 8090 because 8787 is taken.
Steam collections: the tags field in shortcuts.vdf is dead - modern Steam
ignores it. Collections live in a Chromium LevelDB, mirrored into localconfig.vdf
under user-collections. The collection NAME exists only in the LevelDB, so a
collection invented in localconfig alone renders blank. emu-sync only ADDS to an
existing collection (steam_collection_id, per-collection not global). Never write
the LevelDB directly.
Per-emulator traps: see the individual wrappers in emu-bin/. Summary:
- PCSX2 ships
QT_QPA_PLATFORM="xcb,wayland"which its Qt build cannot parse - wrapper exportsQT_QPA_PLATFORM=xcb. - RPCS3 Linux builds are in
RPCS3/rpcs3-binaries-linux, and GitHub releases there are ordered by commit sha not date, so they are unusable for updates. Usehttps://update.rpcs3.net/?api=v2. - Dolphin has no official AppImage and flatpak cannot work here (
/proc/sysis read-only under Proxmox AppArmor). Usepkgforge-dev/Dolphin-emu-AppImage. Its zenity update prompt BLOCKS launches - disabled via policy file plus anozenitystub on PATH. - Never hand-write Dolphin input mappings; invalid expressions crash the Controllers
dialog. Copy the bundled
SDL Gamepad.iniand supply onlyDevice =. - Never create/move/delete anything inside Dolphin's Wii NAND - it keeps
fst.binand unregistered folders cause "Could not write to/read from Wii system memory". - SteamGridDB sits behind Cloudflare and 403s the default Python-urllib User-Agent.
Secrets
/opt/emu/config.json holds the SteamGridDB API key (mode 600) - gitignored;
emu/config.example.json is the redacted copy. sunshine_state.json holds the
admin password hash and paired client certs - gitignored, never commit it.
Recent changes
- 2026-09-20
15a49e8Initial capture of Sunshine streaming + emulator stack