A fully declarative NixOS configuration that boots directly into Steam Big Picture via gamescope, supports Valve Index VR with automatic launch/shutdown on HMD plug events, and provides a lightweight KDE Plasma desktop accessible from Steam's "Switch to Desktop" button.
The entire system is defined in a single flake with two external inputs: nixpkgs and the CachyOS kernel. There are no other runtime dependencies. Every piece of state -- kernel, drivers, session manager, udev rules, systemd services -- is reproducible from sudo nixos-rebuild switch --flake .#gamingOS.
- Boot to Steam Big Picture -- Gamescope compositor launches Steam in full-screen Big Picture mode on boot, no display manager interaction required.
- "Switch to Desktop" -- The Steam Big Picture button works. It switches to a KDE Plasma 6 Wayland session. Logging out of KDE returns to Gaming Mode.
- "Return to Gaming Mode" -- A desktop shortcut in KDE logs out and returns to the gamescope session automatically.
- Valve Index VR -- SteamVR and Monado runtimes are both supported and toggleable. HMD auto-launch detects the Index via USB and starts SteamVR. Disconnecting the HMD stops it. Monado users get OpenVR compatibility (OpenComposite or xrizer), automatic
openvrpaths.vrpathmanagement, optional lighthouse base station power control, and KDE desktop shortcuts for VR start/stop. - VR audio switching -- Optional automatic audio routing to/from the Index headset when VR starts and stops.
- NVIDIA and AMD GPU support -- A single config flag (
myOS.gpu) switches between complete NVIDIA and AMD driver stacks. - CachyOS kernel -- Binary-cached CachyOS kernel with gaming-optimized scheduler tuning and SteamOS sysctl settings. Unlimited RT scheduling for VR compositors.
- Controller support -- Xbox One/Series (xpadneo), Xbox 360, DualShock 4, DualSense, and 8BitDo controllers supported out of the box with optimized Bluetooth pairing.
- Living room ready -- EarlyOOM prevents hangs under memory pressure, udisks2 auto-mounts game drives, Steam LAN transfer ports are opened for fast local game sharing.
The system uses SDDM as the display manager with auto-login enabled. On boot, SDDM logs in as the gamer user and launches the gamescope-wayland session, which is a custom wrapper script.
Boot
|
v
SDDM auto-login
|
v
gamescope-session (wrapper loop)
|
+---> gamescope + Steam Big Picture
| |
| +-- "Switch to Desktop" pressed
| | |
| | v
| | steamos-session-select writes "plasma", kills gamescope
| |
| +-- Game crash / Steam exit
| |
| v
| Wrapper restarts gamescope after 2s pause
|
+---> KDE Plasma Wayland (when desktop was requested)
|
+-- User logs out (or "Return to Gaming Mode" shortcut)
|
v
Wrapper loops back to gamescope
The wrapper never exits under normal operation. If it does crash, SDDM's auto-relogin restarts it.
When myOS.vr.autolaunch.enable = true, a udev rule watches for the Valve Index HMD (USB 28de:2300). On plug, a systemd oneshot service waits for Steam to be running, then opens steam://run/250820 (the SteamVR app ID). On unplug, the service stops and kills VR processes.
The rule specifically targets the HMD display device (2300), not the breakout box hub (2613), to avoid false triggers when only the breakout box is powered on.
myOS.gpu = "nvidia" or "amd" controls which driver stack is activated via lib.mkIf. Both paths are always defined; only the matching one produces configuration output.
| NVIDIA | AMD | |
|---|---|---|
| Driver | nvidia (proprietary) |
amdgpu (open) |
| Vulkan | NVIDIA ICD | RADV (Valve's driver) |
| VR default | SteamVR | Monado |
| Kernel notes | GCC non-LTO (DKMS safe) | cap_sys_nice kernel patch + SteamOS GPU params |
AMD additionally receives SteamOS-aligned kernel parameters for GPU lockup recovery, TTM memory allocation, and scheduling stall prevention (amdgpu.lockup_timeout, ttm.pages_min, amdgpu.sched_hw_submission, amdgpu.dcdebugmask).
Xbox One/Series wireless controllers connect via Bluetooth using the xpadneo driver. Xbox 360 wired controllers use the built-in xpad driver. DualShock 4, DualSense, and 8BitDo controllers are supported through udev rules that grant user-level access.
Bluetooth is configured with SteamOS-aligned settings: multi-profile support (controller + headphones simultaneously), fast reconnection, and disabled ERTM (fixes pairing issues with most Bluetooth gamepads). The Blueman service provides a GUI for pairing from KDE desktop mode.
Both SteamVR and Monado are implemented as toggleable modules behind myOS.vr.runtime.
SteamVR is the default for NVIDIA. It works without extra configuration but lacks async reprojection on NixOS because Steam's bubblewrap sandbox strips CAP_SYS_NICE. An opt-in bubblewrap patch (myOS.vr.bubblewrapPatch) restores this capability at the cost of weakening the sandbox.
Monado is the default for AMD. It receives CAP_SYS_NICE through a NixOS security wrapper (outside the sandbox), so async reprojection works natively. It uses SteamVR's lighthouse tracking driver (STEAMVR_LH_ENABLE=1) for best tracking quality.
When Monado is the active runtime, the module:
- Sets
forceDefaultRuntime = trueto prevent SteamVR from overriding the active OpenXR runtime. - Creates
openvrpaths.vrpathviaExecStartPreto route OpenVR games through OpenComposite (or xrizer, configurable viamyOS.vr.openvrCompat) to Monado. Without this, OpenVR games silently fall back to SteamVR. - Cleans up
openvrpaths.vrpathviaExecStopPostto avoid stale runtime state. - Exposes the Monado IPC socket to Steam's pressure-vessel sandbox via
PRESSURE_VESSEL_FILESYSTEMS_RW. - Optionally controls lighthouse base stations (
myOS.vr.lighthouseControl) — powers them on when VR starts and off when it stops. - Provides "Start VR" and "Stop VR" desktop shortcuts for KDE desktop mode.
- Includes
monado-vulkan-layersfor Vulkan integration.
nixos-vr-gaming/
|-- flake.nix # Inputs: nixpkgs + nix-cachyos-kernel
|-- hosts/gaming/
| |-- default.nix # Host config: imports modules, sets options
| +-- hardware-configuration.nix # Machine-specific (generate per machine)
|-- modules/
| |-- core/
| | |-- boot.nix # CachyOS kernel, bootloader, sysctl tuning
| | |-- nix.nix # Flake settings, binary caches
| | +-- users.nix # User account, groups, sudo
| |-- gpu/
| | |-- default.nix # myOS.gpu option declaration
| | |-- nvidia.nix # NVIDIA driver, modesetting, VA-API
| | +-- amd.nix # AMD driver, RADV, kernel patch
| |-- gaming/
| | |-- steam-session.nix # SDDM config, Steam, gamescope, udev, polkit
| | +-- desktop.nix # KDE Plasma 6
| |-- vr/
| | |-- default.nix # myOS.vr option declarations + memlock + desktop shortcuts
| | |-- steamvr.nix # SteamVR runtime + bubblewrap patch
| | |-- monado.nix # Monado OpenXR + OpenComposite/xrizer + lighthouse
| | |-- index-autolaunch.nix # udev + systemd HMD auto-launch
| | +-- index-audio.nix # Automatic audio switching for Valve Index
| |-- controllers.nix # Xbox/PS/8BitDo controllers, Bluetooth
| +-- audio.nix # PipeWire + JACK + WirePlumber + rtkit
|-- pkgs/gamescope-session/
| +-- default.nix # Session wrapper, steamos-session-select
|-- patches/
| +-- bwrap-cap-nice.patch # Bubblewrap patch for SteamVR async reprojection
+-- scripts/
+-- install.sh # Interactive disk partitioning + nixos-install
- x86_64 PC with NVIDIA or AMD GPU
- UEFI boot (systemd-boot)
- NixOS installer USB (for fresh installs)
-
Boot from a NixOS minimal installer ISO.
-
Connect to the internet (Ethernet or
nmtuifor Wi-Fi). -
Switch to a root shell and run the install script:
sudo -i nix-shell -p git git clone https://github.com/kronflux/nixos-gaming.git /tmp/nixos-gaming bash /tmp/nixos-gaming/scripts/install.sh -
Reboot. The system will boot directly into Steam Big Picture.
The install script handles everything interactively: disk partitioning, swap setup, hardware detection, GPU selection, VR enable/disable, timezone, NixOS installation, and user password. It also works if you run it from a local copy of the repository.
If you prefer to do things manually, or the script doesn't work for your setup:
sudo -i
# Partition and mount (adjust device name)
sgdisk --zap-all /dev/sda
parted -s /dev/sda mklabel gpt
parted -s /dev/sda mkpart ESP fat32 1MiB 1024MiB
parted -s /dev/sda set 1 esp on
parted -s /dev/sda mkpart primary ext4 1024MiB 100%
mkfs.fat -F32 -n BOOT /dev/sda1
mkfs.ext4 -L nixos /dev/sda2
mount /dev/sda2 /mnt && mkdir -p /mnt/boot && mount /dev/sda1 /mnt/boot
# Clone and generate hardware config
git clone https://github.com/kronflux/nixos-gaming.git /mnt/etc/nixos
nixos-generate-config --root /mnt
cp /mnt/etc/nixos/hardware-configuration.nix /mnt/etc/nixos/hosts/gaming/hardware-configuration.nix
# Stage in git (flakes only see tracked files)
cd /mnt/etc/nixos && git add hosts/gaming/hardware-configuration.nix && cd -
# Edit hosts/gaming/default.nix to set myOS.gpu, timezone, etc.
nano /mnt/etc/nixos/hosts/gaming/default.nix
# Install
nixos-install --flake /mnt/etc/nixos#gamingOS --no-root-password
nixos-enter --root /mnt -c 'passwd gamer'
rebootImportant: Do not use nixos-generate-config --show-hardware-config — it omits fileSystems entries in some environments (Hyper-V, certain UEFI setups) and will cause the build to fail with "fileSystems does not specify your root file system."
git clone https://github.com/kronflux/nixos-gaming.git /etc/nixos
# Copy your existing hardware config into the flake
cp /etc/nixos/hardware-configuration.nix /etc/nixos/hosts/gaming/hardware-configuration.nix
# Or generate fresh: nixos-generate-config && cp /etc/nixos/hardware-configuration.nix /etc/nixos/hosts/gaming/hardware-configuration.nix
# Stage it (flakes only see git-tracked files)
cd /etc/nixos && git add hosts/gaming/hardware-configuration.nix
# Edit hosts/gaming/default.nix to set GPU, timezone, etc.
# Then build:
sudo nixos-rebuild switch --flake .#gamingOSAll user-facing options are set in hosts/gaming/default.nix:
{
# GPU driver selection
myOS.gpu = "nvidia"; # "nvidia" or "amd"
# VR configuration
myOS.vr = {
enable = true; # Enable Valve Index VR support
runtime = "steamvr"; # "steamvr" or "monado"
autolaunch.enable = true; # Auto-start SteamVR on HMD plug
bubblewrapPatch = false; # Patch bwrap for SteamVR async reprojection
# Monado-specific options (ignored when runtime = "steamvr")
openvrCompat = "opencomposite"; # "opencomposite" or "xrizer"
lighthouseControl = false; # Power base stations on/off with VR
# Valve Index audio switching (optional, requires device names from pactl)
# audio = {
# enable = true;
# card = "alsa_card.usb-Valve_Corporation_Valve_VR_Radio___HMD_Mic-01";
# profile = "output:iec958-stereo+input:mono-fallback";
# source = "alsa_input.usb-Valve_Corporation_Valve_VR_Radio___HMD_Mic-01.mono-fallback";
# sink = "alsa_output.usb-Valve_Corporation_Valve_VR_Radio___HMD_Mic-01.iec958-stereo";
# defaultSource = "your-normal-mic-source";
# defaultSink = "your-normal-speakers-sink";
# };
};
# System
networking.hostName = "gamingOS";
time.timeZone = "America/New_York";
}The optional myOS.vr.audio module automatically routes audio to the Index headset when VR starts and restores your normal audio devices when VR stops. To configure it:
- Plug in your Valve Index and run:
pactl list cards # Find the card name pactl list short sinks # Find the sink name pactl list short sources # Find the source name - Set the device names in
hosts/gaming/default.nix(see example above). - The service binds to
monado.serviceso it activates automatically with VR.
When myOS.vr.lighthouseControl = true, base stations are powered on when Monado starts and powered off when it stops, using lighthouse-steamvr over Bluetooth. This saves power and extends base station lifespan.
To temporarily disable lighthouse control without changing the config (e.g., when base stations are already on from another machine), create /tmp/disable-lighthouse-control:
touch /tmp/disable-lighthouse-control
myOS.vr.openvrCompat controls how OpenVR games are translated to OpenXR when using Monado:
opencomposite(default) -- More mature, wider game compatibility.xrizer-- Newer, actively developed alternative. May work better for some titles.
The selected layer is registered via openvrpaths.vrpath which is automatically managed by the Monado service.
Extra arguments for gamescope and Steam can be configured in /etc/gamescope-session/environment (managed by the NixOS module in steam-session.nix):
GAMESCOPE_EXTRA_ARGS="--mangoapp --adaptive-sync"
STEAM_EXTRA_ARGS=""The NVIDIA module defaults to proprietary drivers (hardware.nvidia.open = false) for VR stability. Turing and newer GPUs technically support open modules, but community reports indicate VA-API and VR regressions. To test open modules:
hardware.nvidia.open = true;sudo nix flake update --flake /etc/nixos
sudo nixos-rebuild switch --flake /etc/nixos#gamingOS
To roll back to a previous generation if an update breaks something:
sudo nixos-rebuild switch --flake /etc/nixos#gamingOS --rollback
Or select a previous generation from the systemd-boot menu at boot time.
Boot from a NixOS installer USB, mount your root and boot partitions, and rebuild:
mount /dev/disk/by-label/nixos /mnt
mount /dev/disk/by-label/BOOT /mnt/boot
nixos-install --flake /mnt/etc/nixos#gamingOS --no-root-password
The flake lock file pins every dependency. As long as the configuration is on disk, the system can be fully reconstructed.
- "fileSystems does not specify your root file system" during install. Do not use
nixos-generate-config --show-hardware-config— it omitsfileSystemsentries in some environments (Hyper-V, certain UEFI setups). Usenixos-generate-config --root /mnt(standard mode) and thencp /mnt/etc/nixos/hardware-configuration.nixintohosts/gaming/. Verify the file containsfileSystems."/"before building. - "configuration file doesn't exist" during install. Nix flakes only see git-tracked files. After generating
hardware-configuration.nix, you must rungit add hosts/gaming/hardware-configuration.nixbeforenixos-install. The install script handles this automatically. - "repository path is not owned by current user" during install. Git's safe.directory protection (CVE-2022-24765) triggers when switching between users (e.g.,
sudovs root shell). Fix with:git config --global --add safe.directory /mnt/etc/nixos. Run the entire installation from a single root shell (sudo -i) to avoid this. - Out of memory during install. The NixOS minimal installer has limited RAM. Enable zram swap before building:
modprobe zram && echo 4G > /sys/block/zram0/disksize && mkswap /dev/zram0 && swapon /dev/zram0. The install script does this automatically. - "Git tree is dirty" warning. This is harmless. It means there are uncommitted changes (like the generated hardware config). The flake still evaluates correctly as long as the files are
git added. - First boot takes a while. Steam performs initial setup on first launch. The screen may appear black for several minutes. Do not power-cycle -- reboot after the process completes.
steamos-session-selectdepends on gamescope detection. If Steam's "Switch to Desktop" button does not appear, verify that gamescope is running with--steamand that Steam was launched with-steamdeck.- SteamVR Error 405 on NVIDIA. The
libdrmpackage is added toprograms.steam.extraPackagesto fixlibdrm.sonot found inside the pressure-vessel sandbox. - Monado has no hotplug support. All controllers and trackers must be powered on before starting Monado.
openvrpaths.vrpathis managed automatically. The Monado service creates a symlink to a Nix store path on start and cleans it up on stop. SteamVR can no longer overwrite it during Monado sessions.- udev rules require re-plug after rebuild. Existing plugged-in devices do not re-trigger udev rules after
nixos-rebuild switch. Unplug and replug the Index, or runsudo udevadm control --reload && sudo udevadm trigger. - CachyOS LTO variants break NVIDIA. The
-ltokernel variants use Clang ThinLTO which can fail to compile NVIDIA DKMS modules. The defaultlinuxPackages-cachyos-latest(GCC, non-LTO) is used for this reason. - Xbox controller firmware updates require Windows. The 8BitDo Ultimate Software V2 and Xbox Accessories app do not work under Wine/Proton. Use a Windows VM or PC for firmware updates.
- Xbox One controllers over Bluetooth need xpadneo. The built-in
xpaddriver only supports wired Xbox controllers. Wireless Bluetooth connections require thexpadneodriver (enabled by default).
The flake.nix includes a commented-out nixpkgs-xr input. Uncommenting it provides bleeding-edge versions of Monado, OpenComposite, and other VR packages from the nix-community/nixpkgs-xr overlay, instead of the versions in nixpkgs. This is useful if nixpkgs lags behind on VR-critical fixes but may introduce instability. Only enable this after the base system is confirmed working.
Why SDDM and not a direct TTY launch? SDDM provides session management infrastructure (session .desktop files, auto-login, auto-relogin) that would need to be reimplemented with getty autologin. SDDM also handles PAM, seat management, and XDG session registration correctly.
Why a wrapper loop instead of SDDM session switching? SDDM session switching requires an intermediary daemon (like steamos-manager) to change the active session via D-Bus. A wrapper loop that alternates between gamescope and KDE within a single SDDM session is simpler, has fewer moving parts, and achieves the same user experience.
MIT