Infecting the Steam Link with NixOS
Infecting the Steam Link with NixOS
用 NixOS “感染” Steam Link
2026-09-26 While rummaging through my closet the other day I discovered a Steam Link I had bought on flash sale way back in 2018 still dutifully humming away all these years later. It occured to me that having an always-on low power Arm device with Ethernet, WiFi, Bluetooth, and several USB ports would be handy, so thus began my journey to get NixOS running on the Steam Link. 2026年9月26日,前几天我在翻找壁橱时,发现了一台我早在2018年闪购时买的 Steam Link,它在这么多年后依然在忠实地运行着。我意识到,如果能拥有一台常开、低功耗、配备以太网、WiFi、蓝牙和多个 USB 接口的 ARM 设备会非常方便,于是我开始了让 Steam Link 运行 NixOS 的旅程。
As it turns out, a fellow named fijam already figured out the hard parts involved in running a custom Linux distro on the Steam Link. The most notable obstacle is that the bootloader will only boot kernels signed by Valve; to get around this, we can boot into the Valve-blessed kernel and then kexec our new kernel. However, the kernel shipped with the Steam Link was not built with CONFIG_KEXEC enabled. This is where things get really clever: we can cobble the pertinent kexec source files into a minimal kernel module that adds the kexec syscall to the running system!
事实证明,一位名叫 fijam 的仁兄已经攻克了在 Steam Link 上运行自定义 Linux 发行版最困难的部分。最显著的障碍在于引导加载程序(bootloader)只允许启动由 Valve 签名的内核;为了绕过这个限制,我们可以先启动 Valve 官方认可的内核,然后通过 kexec 来加载我们的新内核。然而,Steam Link 自带的内核在编译时并未开启 CONFIG_KEXEC 选项。这就是最巧妙的地方:我们可以将相关的 kexec 源代码拼凑成一个最小化的内核模块,从而为正在运行的系统添加 kexec 系统调用!
Several people have successfully used this technique to get other distros booting, but they all seem to have just copied the kexec binary and kernel module from fijam’s website. fijam seems like a lovely person and all, but I’m wary of downloading kernel modules from the interwebs so I decided to compile it myself.
已经有几个人成功利用这种技术启动了其他发行版,但他们似乎都只是直接从 fijam 的网站上复制了 kexec 二进制文件和内核模块。fijam 看上去是个很不错的人,但我对从网上下载内核模块心存戒备,所以我决定自己编译它。
Booting the thing
启动设备
Compiling a NixOS userspace and kernel/initrd is pretty simple; you just pass the correct system (and because I’m using __splicedPackages/crossSystem, also pkgs) to lib.nixosSystem and add your modules. Choosing the target architecture was slightly less straightforward: Valve’s steamlink toolchain uses armv7a, but importing nixpkgs with crossSystem.config = “armv7a-unknown-linux-gnueabihf” interacts poorly with the Go build plumbing, so I used the (seemingly) equivalent armv7l instead.
编译 NixOS 用户空间和内核/initrd 非常简单;你只需要将正确的系统(由于我使用了 __splicedPackages/crossSystem,还需要传入 pkgs)传递给 lib.nixosSystem 并添加你的模块即可。选择目标架构稍微复杂一些:Valve 的 Steam Link 工具链使用的是 armv7a,但使用 crossSystem.config = “armv7a-unknown-linux-gnueabihf” 导入 nixpkgs 会与 Go 的构建流程产生冲突,所以我改用了(看起来)等效的 armv7l。
(Code block omitted for brevity) (代码块略)
The real challenge is compiling a kernel module for a vendored fork of a 13 year old kernel; the NixOS wiki is actually pretty helpful here and points out some footguns related to the default hardening flags in stdenv.
真正的挑战在于为一个 13 年前内核的供应商分支编译内核模块;NixOS Wiki 在这方面非常有帮助,它指出了与 stdenv 中默认加固标志(hardening flags)相关的一些“坑”。
(Code block omitted for brevity) (代码块略)
In order to get this to build we need to point Kbuild to a Linux kernel checkout that has been built with make modules. (Note that I’m using the moduleBuildDependencies attribute of the comparatively modern kernel from my NixOS configuration.)
为了完成构建,我们需要将 Kbuild 指向一个已经通过 make modules 构建过的 Linux 内核源码目录。(请注意,我使用了 NixOS 配置中相对较新的内核的 moduleBuildDependencies 属性。)
(Code block omitted for brevity) (代码块略)