Hardware setup & tuning
The module gets you into Gaming Mode; how well it runs is decided by the
hardware configuration around it. Nothing here is Steamix-specific machinery —
it is the checklist of things worth setting up by hand until someone figures
out how to automate them. Like the rest of these pages it is written
split-ready: when Steamix becomes its own repository, this guide moves
with it.
nixos-facter: describe the hardware as data
nixos-facter is the modern take on
nixos-generate-config’s hardware detection. Instead of generating a Nix file
once and letting it rot, it writes a JSON report of everything it probes —
CPU, GPU, buses, network interfaces, bluetooth, disks — and the facter
modules (formerly the separate
nixos-facter-modules,
now upstreamed into nixpkgs as hardware.facter.*) interpret that report at
evaluation time.
Why bother, when hardware-configuration.nix exists?
- The report is data, the interpretation is code. When the modules learn a
better way to configure some device class, every host with a report picks it
up on the next rebuild — a generated
hardware-configuration.nixnever improves after the day it was written. - It sees more: firmware/microcode, graphics stacks (Mesa, 32-bit support), bluetooth, virtualisation guests — not just kernel modules and filesystems.
- It is reviewable: a hardware swap shows up as a readable diff of the report, checked in next to the host.
Generate (or refresh) a report on the machine itself:
sudo nix run \
--option experimental-features "nix-command flakes" \
nixpkgs#nixos-facter -- -o facter.json
Then wire it in:
# the module ships with nixpkgs, nothing to import:
hardware.facter.reportPath = ./facter.json;
Re-run it after every hardware change. A stale report is silently wrong: when a GTX 1060 made way for an RX 9060 XT, the old report kept describing the NVIDIA card, its driver ended up in the initrd, and AMD-specific extras gated on detection (like ROCm’s OpenCL) stayed off until the report was regenerated.
nixos-hardware: quirks other people already debugged
nixos-hardware is a community collection of per-device and per-component modules — the accumulated “this machine needs that kernel parameter” knowledge, packaged. If facter describes what hardware you have, nixos-hardware fixes how it behaves.
Add the input, then import either a named machine profile (best case: someone with your exact machine already did the work):
inputs.nixos-hardware.url = "github:nixos/nixos-hardware";
imports = [ inputs.nixos-hardware.nixosModules.framework-13-7040-amd ];
…or compose from the generic building blocks under common/ when there is
no profile for your machine, for example a Coffee Lake desktop with a Radeon
card:
imports = [
(inputs.nixos-hardware.outPath + "/common/cpu/intel/coffee-lake")
(inputs.nixos-hardware.outPath + "/common/gpu/amd")
];
Blocks worth knowing for a Steam machine:
| Import | What it buys you |
|---|---|
common/gpu/amd | amdgpu driver stack incl. early KMS — flicker-free boot into gamescope |
common/cpu/amd/pstate, common/cpu/intel | modern CPU frequency scaling / microcode |
common/pc/ssd | periodic TRIM |
common/pc/laptop | power tuning, if the “console” is a laptop |
Browse the repo’s common/ tree — the modules are tiny and readable, so when
in doubt, read what an import actually sets before adopting it.
Getting the most out of Gaming Mode
Everything below is upstream nixpkgs options — listed here because finding them is the hard part.
Match gamescope to your display. By default gamescope picks something sensible; being explicit avoids surprises on TVs. One list element per argv entry:
steamix.gamescope.args = [
"--output-width"
"3840"
"--output-height"
"2160"
"--nested-refresh"
"120"
];
HDR and VRR are not in that list because steamix.hdr.enable and
steamix.vrr.enable are on by default — they pass the gamescope flag and
tell Steam the session supports the feature, which is what puts the toggles in
Gaming Mode’s display settings.
A new GPU wants a new kernel. Release-branch LTS kernels lag GPU support by months (RDNA 4 is the current example):
boot.kernelPackages = pkgs.linuxPackages_latest;
Proton-GE fixes titles that stock Proton struggles with:
programs.steam.extraCompatPackages = [ pkgs.proton-ge-bin ];
Controllers. programs.steam enables hardware.steam-hardware (udev
rules for every controller Steam supports) by itself. On top of that:
hardware.bluetooth.enable = true; # BT gamepads
hardware.xpadneo.enable = true; # better Xbox-controller-over-BT driver
Feels-faster knobs:
programs.gamemode.enable = true; # CPU governor/niceness while a game runs
zramSwap.enable = true; # compressed-RAM swap; no disk partition needed
MangoHud is available too (MANGOHUD=1 in a game’s launch options after
adding pkgs.mangohud), though Gaming Mode’s built-in performance overlay
covers most of it.
Good to know
- Audio must be configured by you (the module does not assume a sound
stack):
services.pipewire.enableplus itsalsa/pulsesub-options is the standard choice. - First boot lands in Steam’s login screen — initial setup (account, network if NetworkManager is present) happens inside the Deck UI itself.
- Firewall:
programs.steam.remotePlay.openFirewallandlocalNetworkGameTransfers.openFirewallopen what Remote Play / local game transfers need. - Failsafe: if a driver or session change ever leaves the machine crash-looping into a black screen, switch to another VT (Ctrl+Alt+F2), log in, and roll back — the loop never blocks a text console. See known limitations.
- Updates: Gaming Mode’s own “check for updates” updates games, not the
OS — system updates stay
nixos-rebuild/ your deploy tool. SteamOS-style atomic OS updates are exactly what NixOS generations already are.
Machines with two GPUs
A desktop with a discrete card and an active integrated one (any Intel or AMD CPU with video output enabled in firmware) has a trap waiting: nothing pins which GPU gamescope composites on. It selects a Vulkan device, then opens the DRM node that matches it — and which card that turns out to be depends on kernel enumeration order, which is not stable across boots. The same machine can come up on the discrete card for weeks and then, one boot, land on the iGPU, open a DRM node with no connected output, fail to create a backend, and leave you at a black screen with greetd looping until it hits its restart limit.
Pin it by PCI ID:
steamix.gamescope.args = [
"--prefer-vk-device"
"1002:7590" # `lspci -nn | grep -i vga`, or /sys/class/drm/card*/device/{vendor,device}
];
gamescope --steam ... 2>&1 | grep 'selecting physical device' confirms the
choice; it names the card it settled on.