安装、配置与使用(NixOS 篇)
安装、配置与使用(NixOS 篇)
前言
早在去年我便说过我的下一个操作系统很有可能是 NixOS。202405 的 OS 课需要做 PPT 汇报,我的选题是包管理器杂谈,又吹了一波 nix,把我自己心吹得痒痒的。
在考试期间由于压抑的氛围和不情愿的学习,平常想做的事的欲望会被放大许多。但是我预料到 NixOS 的安装肯定会非常折磨(好预测!),所以只在 WSL 里尝尝鲜。而 WSL 终究无法发挥出 Nix 的特色。于是熬到了考完试当晚,我就开始安装 NixOS 了。
之后由于换台式机,有半年没有再用过 NixOS;后来由于有在多个设备上安装 Linux 的需求,就在 7 月中下旬又捡起了 NixOS,并且大幅改了配置。
注意
NixOS 绝对不适合 Linux 新手使用,如果你想尝试 NixOS,请务必对 Linux 的启动流程有一定了解,并具有稳定的代理/网络环境后再尝试。
NixOS 安装
安装我看的是 NixOS 中文。
NixOS 的安装比我想象的要折磨得多。原以为装过 Arch 的我已经无惧困难,结果输得非常彻底。。一大原因是因为没有 Archlinux 那样的顶级文档,而另一个则是群友人数确实更少,解答问题的也更少。不过这些都是后话了。
我是在还没有学习 nix 语言和特性的情况下装的系统,踩了不少坑。
首先,我缩了 ArchLinux 的分区大小,踩了一次 cfdisk 的坑。而后下载了 NixOS 的图形化安装程序,用 ventoy 引导启动,一切正常。然而在安装引导的分区时我发现,图形化界面没有提供 btrfs 分区,只提供 ext4。于是我停止使用图形化界面,改用里面的 konsole 终端。结果 channel update 时无法正常重启某些服务(xe-daemon.service: NewCachedXenstore error: Cannot locate xenbus dev path...)。我猜测这是图形界面导致的问题,于是我重新打开 arch,下载了 minimal iso,这里面还有一个好玩的小插曲。
果然进了 minimal iso 有一种回家的感觉,并且确实没有重启服务失败的问题了。一番鼓捣,总结出一些规律:
- 碰到
HTTP error 200 (curl error: Stream error in the HTTP/2 framing layer)不要管,后台会重试。 - 如果
denpendency fail to build,换个源重新试。- 如果换源后碰到同样的问题,把源换回去再试。
- 安装过程中遇到
core dumped。。。emmm,这有点脑残了,不过换了个源又好了,可能是因为没拉到缓存,构建时 core dump?
就这样,两个源交替查缺补漏,总算是 install success 了。“最终退后三步朝电脑跪拜祈求它能正常开机,至此基本安装教程完毕。”——才怪,sddm 进去后,root 和我都无法登录(保证密码正确)。于是类似 Arch 那样重走挂载流程,nixos-enter 进去修。
这进去一 rebuild 我就感觉不对劲,此处把报错贴出:
sudo: PAM account management error: Authentication service cannot retrieve authentication info
sudo: a password is required求助群友,群友说 nixos-install 与 nixos-rebuild 效果基本是一样的,让我去外面用 install 试。效果确实差不多,不过每次在 livecd install 都相当于重装,无法用内部的缓存,全部重新下载,还是有点耗时的。install success 后再次 nixos-enter 试,还是一样的报错,只要用到 sudo 就会炸。鼓捣了大半天,reinstall 了好几次都无法解决,另一个管理说,在 nixos-enter 中这是正常现象,内核的表现是不一致的。我一直把 nixos-enter 当 arch-chroot 用,没想到这玩意这么捞。
- 日后刷到了一篇博文,描述了另一个人是如何解决此问题的。我感觉这更像是 nixos 的 bug 而不是内核不一致。
这回重启就正常了,也不知道是其中的哪次 install 尝试起了作用。于是我成功进入系统开始激情编辑配置。
后记
第二天学校有实践课加上 cs2 出新图,没怎么折腾系统,尝试给电脑装了个 nvidia 驱动和其他玩意就去睡觉了。
第三天起床一开机,直接 dmesg 卡死无法切 tty。兄弟我还有课啊!于是带着去教室不听课,校园网认证还过不了,只好用手机开热点修。这次知道不能 nixos-enter 了,但是只能用小手机查资料,每次挂载输入一大串,用镜像输一大串,重新 install 等好久,试错成本偏高。加上我昨天配置改了很多,二分查找需要的次数也不可估量,因此还是非常慢的。(不能简单地放弃 NVIDIA 驱动,因为有 NixOS gaming 需求)
二分查错过程中我发现每次 install 并不会重新写入 EFI 分区(配置里注释了启动项,但是 grub 菜单并没有消失),因此向群友提问。群友答日常 build 和 install 是不会擦除 EFI 的,只有 gc 时会。但是我并不在系统里,nixos-enter 如上文所述,并不能执行 gc 指令。
后来怀疑是内核原因:我换了 zen 内核,按理需要用 dkms 的 NVIDIA 驱动,然而实际用的是 NVIDIA 闭源驱动。。换回原内核又发生了 EFI 空间不足的惨剧,又折腾许久。等我禁用了 NVIDIA 成功开机,都过了午饭时间了。然后一开机我就去定制了一个 iso,太折磨了。
下午折腾中文双拼输入法,详见输入法。
又过了一天,我想起来 NVIDIA 驱动一直没开,而此时我已经做好了万全的准备,btrfs 打了快照,grub 加了禁用 nvidia 的启动项,自制了启动盘,是时候挑战 NVIDIA 驱动了!于是我开启了显卡驱动,果然又卡 dmesg 了。把 hardware.nvidia.open = false; 改成 true 以后又好了。正当我高兴,一天后的 rebuild 又让我吸了口凉气,问题并没有解决。
这个问题一放就是两个月,总结了一点规律:
- X11 的锅,卡 dmesg 是 X11 起不来的表现
- 从 windows 重启到 nixos 时卡住的概率更高
- 如果不进 windows,只使用 nixos,则基本不会遇到此问题
反正现在我摆烂了,看到卡 dmesg 就 sysrq 重启。
自从我装台式后就没有再用过 NixOS,因为 1. 之前的安装给我带来了很大的心理阴影 2. NixOS 安装需要多次 bootstrap,即使有声明式配置也一点都不简单 3. 大四下我正在享受最后的青春,天天在 windows galgame,确实没有装 NixOS 的动力。因此就这样过了半年。
202507 由于我买了家庭服务器,想装成 nixos 用,于是就重新先在我的主力机上回坑 nixos,调完配置再搞。那台服务器暂时先装了个 windows 调完 Ryzen Master 参数先跑着()
我先是用我原先的同款配置,果然 nixos-install 时整天报网络 error;不过好在有了经验,知道国内几个镜像源要轮换着用,加之我的回坑大概过了半年不算太久,intel 显卡也不需要什么配置,于是就还算顺利地在主力机上装好了。
但是这是我第一次考虑使用一份配置安装多个 NixOS 主机,原先的屎山配置肯定是要拆的。然后我一咬牙,就花了几天时间把我的 tag 论设想在我的配置里实现了。拆配置是痛苦的,拆完的成就感是满的;我甚至天天在公司摸鱼读文档拆配置,晚上带回家 rebuild 解决报错,小日子过得还不错。
然后 20250726 把新配置写得差不多了,就在服务器上装好了 NixOS。期间也是切到主力机上改配置改了很多次,因为发现了各种各样的问题,包括 iso 也重做了很多次,笑死。总之没有遇到太多问题。
由于许多配置都在 NixOS,而上班必须用 WSL(问就是某个公司软件不提供 Linux 版,而项目要用 Linux 跑),之前用 ArchWSL 不够爽,因此试着用一下 NixOS-WSL。(刚好摸摸鱼)
经过了配置 feature 化改造,现在想要筛选出不需要在我的这个设备上的配置非常简单。但是 NixOS-WSL 其实还有非常非常多的坑,往后面读就知道了。
使用 NixOS-WSL 时最好也再安装一个 ArchWSL,因为微软的 WSL2 + 傻逼 fs ext4 在 NixOS 这种大量小文件 distro 上经常挂,可能要经常把 vhdx 挂到 ArchWSL 上用 fsck 修。至于为啥这么不稳定。。。感觉还是得怪 WSL。
# 关机,挂载
wsl --shutdown
wsl -d arch --mount --vhd --bare "C:/Users/lxl/AppData/Local/wsl/{72daacb7-af24-4055-bf2a-cec2dd4a35e5}/ext4.vhdx" # 用你的实际路径
# 启动,修复
wsl -d arch
lsblk
fsck.ext4 -fvy <device> # 一般虚拟磁盘是 1T 容量,有两块,选没有挂载的那块
wsl --unmount "C:/Users/lxl/AppData/Local/wsl/{72daacb7-af24-4055-bf2a-cec2dd4a35e5}/ext4.vhdx"
wsl --shutdown- nixos-anywhere 需要魔法咏唱,挺麻烦的我不太感兴趣
- lantian 佬的 dd 镜像:用过几次,但是这个示例依赖 systemd 管理网络,我曾经遇到过 dd 完后服务器连不上的情况。
- 最后我还是用了 bin456789/reinstall,一键重装实在太方便了,除了强制用 ext4 以外没有缺点。
用 reinstall 重装后,再 nixos-rebuild switch --flake .#<host> --target-host <host> --build-host <host> 即可。这个指令可以在本地 eval 后,只将较小的 drv 传给 remote,并且有 drv 去重。remote 收到 drv 后只要下载,rebuild 即可。
学习
如何学习 nix 呢?nix 没有强大的 wiki,遇到问题只能到处 google。但是也有一些好的资源。
- NixOS 中文:安装教程与初步使用
- NixOS 与 Flakes - thiscute:进阶好书
- Lan Tian @ Blog:打包与高级用法
- 中文 discourse & telegram group:可能可以来问问题
- awesome-nix:里面也有 for tutorials / developers 的链接。
一个要点是理清 nix 的 一些事实标准,例如 flake, home manager,他们是什么,有什么用。好在那本 thiscute 的书完美解决了此问题。
还有一个学习方法是多看别人的 configuration,并且大量摘抄。我的配置在下面,还有一些:ryan4yin's config nyancat wimpysworld jackdbd nmasur oo-infty AsterisMono tama-gucci(dendritic pattern) lovesegfault
语言基础
2025+ 年 AI 已经非常强大,语法问题完全可以开 online search 问 AI。
- 先阅读 NixOS 中文 - Nix 语言快速入门。
- 条件判断:一般接触多的是
if..then..else和lib.mkIf。- 两个 if 里条件只能是 bool,不能是其它类型。
lib.mkIf和if..then..else null;是不一样的!lib.mkIf求值时会被转换成类似{ _type = "if"; condition = ...; content = ...; }的形式,方便求值时验证和 lazy。
inherit x y;=x=x;y=y;,就是用来透传的。- function 的
@绑定:bargs@{a, b, ...}:is equivalent to{a, b, ...}@bargs: //用于两个 attrset 的合并,右边覆盖左边。- 最常用的一些判断条件:
mkDefault和mkForce修改合并优先级,mkBeforemkAfter修改 list 合并顺序,mkIf条件控制某些属性的有和无,optional根据条件返回 null 或[x],而optionals返回 null 或 x。- config 只能设置,不能取消。如果你需要取消已有的设置,只能去查一下默认值然后使用 mkForce (默认值)。并且如果版本更迭,默认值发生改变,配置就出现了不一致行为。
- 数据类型:
- 数组:list。
- 判断元素是否存在:
builtins.elem elem list。 - 删除元素只能用 filter。
- 判断元素是否存在:
- 数组:list。
- nix config 里,attrset to INI 的类型要求必须有 section。但是实际 ini 配置可能有些条目没有 section,所以必须使用一个伪 section: main。例如
xxx = 1需要写成main = { xxx = 1;};。 - 有的配置只支持 string type,不支持 path。这时候我们需要把一个文件弄到 store 里并返回 store 的路径。可以使用
"${./path/to/file}"。- 我以前不知道这事,写了许多麻烦的函数:
configToStore = configFile: toString (self.writeText (builtins.baseNameOf configFile) (self.lib.fileContents configFile)); binaryToStore = binaryFile: super.runCommand (builtins.baseNameOf binaryFile) { nativeBuildInputs = [ super.coreutils ]; } '' cp ${binaryFile} $out chmod +x $out ''; - 如果路径里本身就含有变量呢?此时需要利用
path + str = path的特性,使用"${./. + "/${var}/file"}"。
- 我以前不知道这事,写了许多麻烦的函数:
OS 基础
- NixOS 应用配置分三步:eval, build, apply。
- NixOS module 里有一些 top level attribute,例如
imports,options,config,还有disabledModules。其他非 top level 的项即使写在顶层,eval 时默认也会被包在config = {...}里面。- disabledModules 在求值时可以取消 imports 引入的模块。因为 list 在 merge 时不能移除元素,所以很多人会分非常多的模块(例如一行
systemPackage = ...也分一个模块),然后使用 disabledModules 控制不同 host 下排除某些包。说到底还是 Nix 的设计缺陷。
- disabledModules 在求值时可以取消 imports 引入的模块。因为 list 在 merge 时不能移除元素,所以很多人会分非常多的模块(例如一行
- nar 是 NixOS 的归档格式,具有确定性。RFC 的 nar 写得有点蠢。
flake 基础
flake 是一种 nixos config 组织形式,虽然现在还是 experiment feature,但是已经成为了事实上的标准。许多软件包懒得合进 nixpkgs,或者不满足条件,都会在 Github 根目录下提供一个 flake.nix 代表自己软件的依赖、配置等。如果需要使用此软件包,就需要通过 flake input 引入。
flake 是顶层结构,所有 nixos flake 配置的入口都应该是 flake.nix。flake.nix 里指定了所有的 input,在 pure eval 下(如果不指定 --impure,默认就是 pure eval),你使用的所有内容都必须从 input 里推导;而 input 的唯一性是由 flake.lock 锁定的。因此使用 flake + pure eval 可以确保可复现性。
flake 模式下需要自备代理,因为 flake input 经常都是从 Github 拉,大家在 flake 里也会塞很多不在 binary cache 里的 content。如果你在中国大陆且初始安装 NixOS 就直接使用 flake 配置,需要在初始的 ISO 里就有代理流量过墙的能力。
- flake 保证可复现性的情况下也带来了一些开销:如果你引入了许多软件包的 flake,而这些 flake 又引入了不同版本的 nixpkgs,那么这些 nixpkgs 就会在你的 OS 上共存,并且占用很多空间(每个 nixpkgs 大约 42MB,软件内部依赖的 pkg 也会单独占用空间)。因此我们需要用
inputs.<xxx>.inputs.nixpkgs.follows = "nixpkgs";将 input 里引入的 nixpkgs 换成我们自己的 nixpkgs 版本,减小空间占用。这样虽然不再保证“引入的这个软件包必定是可用的”,但是可复现性不受影响,且出问题的概率较小,比起节省的空间,我觉得还是可以妥协的。- 除了 nixpkgs 这种每个 flake 都有的东西,外部的 flake 也可能有一些其他依赖(stylix:是在说我吗 v_v)。所以引入 flake 的时候最好检查一下它的源码,把全部 input 都 follows 到统一的版本;并且需要在 rebuild 后检查 flake.lock,如果发现了
_2字样那就意味着你的不同 input 已经引入了同一个 flake 的不同版本。我有洁癖,我希望将其统一到一个版本。 - 我非常讨厌
_2,而且很多 flake 的依赖经常变导致我每次 flake update 都会引入新的_2。因此我 vibe 了一个脚本用来帮我自动化 follows。
- 除了 nixpkgs 这种每个 flake 都有的东西,外部的 flake 也可能有一些其他依赖(stylix:是在说我吗 v_v)。所以引入 flake 的时候最好检查一下它的源码,把全部 input 都 follows 到统一的版本;并且需要在 rebuild 后检查 flake.lock,如果发现了
- 降级 flake input 到某个特定版本(例:nixpkgs):
nix flake update nixpkgs --override-input nixpkgs github:NixOS/nixpkgs/<commit-hash>。至于 commit hash 怎么查,可以看看 搜索技巧。
nix command
常用命令:
nix-prefetch-url <url> # fetch 并输出 sha256。在打包时经常用到。
nix-collect-garbage -d # 删除所有配置的所有旧版本,并 GC。(彻底清理)
nix-store --add-fixed sha256 <file> # 手动添加文件,后续避免联网 fetchurl
nix flake update <input> # update 一个特定 input
nix rebuild switch --max-jobs 0 # --max-jobs 可以禁止本机构建,必须从缓存拉二进制包
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system # 列出所有 NixOS 的历史版本和 id
sudo /nix/var/nix/profiles/system-<id>-link/bin/switch-to-configuration switch # 重置整个系统到某个 id 的版本
sudo nix-env --profile /nix/var/nix/profiles/system --switch-generation <id> # (可选):让下次重启也使用某个 id 的版本
# nix eval .#nixosConfigurations.<host>.config.xxx # 查看 eval 后的某个配置值(flake,需要查询所有 input,比较慢)
nix repl . # 进入 repl 交互命令模式。
nix-repl> :lf . # load flake
nix-repl> nixosConfigurations.<host>.config.xxx # 这时候查询配置就比较快了
nix-repl> builtins.toJSON nixosConfigurations.<host>.config.xxx # 默认会折叠,可以 toJson 展开其他:
- 最简单的填 hash 方法是先乱填一个,rebuild 报错后再从报错信息里拿真 hash 加上去。因为 nix hash 是有坑的,默认的 nix-hash xxx 算的是 nar hash。请务必添加
--flat,这样才能算 file hash。
在 NixOS 上用得到的妙妙小工具
- selector4nix:一个 substituters 代理,让 nix 可以并行查询 binary cache,加速构建。用起来确实有用。
- 题外话:由于在频道吐槽过这玩意,被作者杀上门来交流了一些 flake 意见。我也给 selector4nix 找过 bug,编程方面的一些交流,等等。
- 由于引用它的 flake 需要下载 rust 全套工具链,所以我在自己 NUR 里维护了一个 package 去拉 prebuilt binary。(我最早就是吐槽没有 binary 的事)
- 这玩意不需要配置 trusted-public-keys,因为如何信任拉来的 binary 是 nix 自己的事,不是代理的事。
- envfs:直接在
/usr/bin等传统路径上挂载 FUSE fs,动态响应程序请求,解决 NixOS 兼容 FHS 生态的问题。 - nix-tree:交互式的依赖寻找,非常好用。打开以后按
/查找。关于具体的查找依赖,可以参考这里。- 顺带一提,Mic92 写了一个 Rust 版本的 nix-tree-rs。
- nix-index:找包位置。实际上并不好用,因为
- 需要查找的时候经常是刚安装完软件的时候,还没有 updatedb。而 nixos 的手动 updatedb 耗时极长。
- 默认 locate 时也会搜索路径,nix 路径又基于 hash,因此会有很多 hash 污染搜索结果。
- 感觉真不如
cd /nix/store && fd xxx。
搜索
安装与配置中很重要的一环是学会搜索。即使 nixos 的文档比较内个,也有一些非常好用的网站用来查询所需信息。
- https://search.nixos.org/packages:查找包。
- 主要也就看 Homepage 和 Source,分别对应项目 README 和 打包 nix 源码
- https://search.nixos.org/options:查找设置项
https://home-manager-options.extranix.com/:查找 home-manager 中的设置项(2026 年 nixos 官方的 option search 已经支持同时查找 home manager 的设置项)- https://status.nixos.org/:查看 nixpkgs 各个 channel 的测试状态。如果你喜欢追新,或者打算要 update flake 可以看看。按照版本从新到旧排序应该是 master > nixos-unstable-small > nixos-unstable > nixos-stable-small > nixos-stable-small。
https://noogle.dev/:nix 语言学习查找(其实很多定义都缺了,很捞)- https://nur.nix-community.org/:NUR 包
- Nix 落絮,搜群友聊天记录
linter / formatter
写 nixos config 就是编程的过程,说到编程那肯定少不了 linter 和 formatter。我是 all in vscode 人,使用的插件如下:
- Nix IDE - Noortheen:lsp,需要手动安装
pkgs.nil - formatter,需要手动安装
pkgs.nixfmt-rfc-style- formatter 的插件可以使用 nixfmt - brettm12345,也可以使用我的 anyformatter - lxl66566。
其他
- x to nix:将 json,xml,yaml 配置转为 nix 配置。(但是没啥必要,因为可以通过
lib.importJSON或者其他相关函数自动转。已经有 json 就真没必要硬塞到 nix 里吧。)
配置
我的配置仓库 (temporary down)
202507 回坑 NixOS 时,我将整个配置重新拆成类似 Rust feature gate 的形式,符合我的 tag 论。这样我就可以在各种系统上方便地组合 feature,以定制最符合我的需求的配置。
同时我的配置里也有一堆模块,每个模块是一个或多个软件及其配置组成的单个功能的集合。这样对于这部分功能可以方便拆装,而不需要频繁拆装的则直接扔到 feature gate 相关的模块里。
感觉上,其实有点借鉴 flake 的思想,stable 的就扔 nixpkgs,需要灵活组合的就扔 flake。(当然现在的 flake 已经丧失“灵活”代名词的地位了)。
配置的组织结构
nix 非常自由,你可以自由组织自己的配置结构。我目前见过的或者用过的有如下几种:
- 乱放型:基于 flake.nix, configuration.nix 和 home.nix 构建的配置,所有东西全部乱放。一般在 nix 早期,配置较少,还不熟悉 nix 语言和结构的时候使用。
- 开关型:有了多主机部署的要求,每台主机有着不同特点,因此原来的乱放型不适用了。开关型指的是在 flake.nix 中定义一些开关,通过 specialArgs 传到各个模块中,控制模块内部的配置。
- Dendritic Pattern:每个文件都是一个 flake-parts 模块,拥有完整的配置。这样可以组织非常庞大的配置结构,并且具有相当高的自由度。
- Dendritic Pattern 只是这样一个比较笼统的概念,你可以在此基础上任意衍生。
- Arcohol/nix-config 是一个非常好的 Dendritic Pattern 例子。
- den:还没细看,README 看起来跟我的理念挺符合的,复杂度也有点高。
我的看法:随着配置越来越多,尝试各种不同的组织结构来管理大量配置是一件非常自然的事情。如果你刚入坑 nix,看到复杂的组织结构被吓到也是很正常的。我觉得没必要一上来就抄复杂配置,在大量模块中迷失自我;从乱放慢慢过渡到复杂结构也是一个比较有意思的过程。
有一些教程会使用 flake-parts,但是这玩意绝不是必须的,比如 ryan4yin 佬的巨大无比配置也没有用到它。flake-parts 官方的文档跟狗屎一样,即使我有心入坑也没有任何头绪,而且这玩意有很多隐藏的复杂度,因此还是算了吧。
substituters
substituters 可以理解为 nixos 的 binary cache,如果命中的话就可以直接联网下载构建产物而不是自己从源码编译了。
高校镜像站的 binary cache 从 cache.nixos.org 直接同步,并原封不动保留官方的签名,因此直接将镜像站地址添加到 substituters 里即可。有一些第三方(cachix、garnix)的 binary cache 是构建机自己构建的,签名和官方不一致,这时候就还需要额外将提供者的公钥加入 trusted-public-keys 中才允许拉取。
设置 substituters 有个问题是 bootstrap,如果你是在 nix configuration 的 nix.settings 里设置的 substituters,则首次 build 是无法使用的,因为该配置还没应用到系统中,因此首次构建拉不到 binary cache,如果想引入啥包,需要拆成两次构建。解决这个问题的好办法是直接把 substituters 写在 flake.nix 的顶层 nixConfig 中,这样本次构建可以直接应用,不需要 bootstrap。
显卡驱动
官方给出了比较详细的 NixOS 显卡驱动教程 (NixOS Manual - Nvidia),看就完了。我认为还存在一些缺点:
- 有些复杂,例如双显卡需要手动查总线并写入 hardware-configuration.
- prime 功能有点残缺,官方给出的 example 里只有在启动时选择不同的启动项以应对外带和接电源两种情况,而不能动态调整性能模式:正常情况下应该是在游戏启动时启用显卡而在未游戏时关闭。
还有我自己折腾后想说的注意事项:使用 hardware.nvidia.open = true;,使用官方内核。
I 卡的驱动实在是太简单了,赞美 intel。
{ pkgs, ... }:
{
# for intel Arc A750 GPU
hardware.graphics = {
extraPackages = with pkgs; [
vpl-gpu-rt
];
};
}图形桌面
图形桌面有两大类,Window Manager (WM) 和 Desktop Environment (DE),前者比较底层一些,更难上手,不过自由度也更高。后者就跟 Windows 那种手感差不多,学习成本低。
我最开始是 KDE,现在已经转到 niri 了。
niri 是一个基于 Wayland 的平铺式 WM,使用 Rust 编写。
NixOS 的哲学是可复现,而 KDE 家的东西可复现性实在是太差了,然后我也想尝试一下新东西,所以用了 niri。我首次尝试 niri 的时候只是尝鲜心态,当时还没有 xwayland-satellite 这玩意,Wayland 基本还是一坨大便,所以没多久就弃坑回到 KDE 了。后来生态好起来了,才又慢慢迁移到 niri。
安装
niri 有两种安装方法:
- 一个是直接用 nixpkgs 里的 niri 包,直接
programs.niri.enable = true;即可,配置自己写niri.kdl然后用 home-manager 链到合适的地方。 - 另一个是用第三方的
niri-flakeniri-flake-pure。
这两个方式具体哪个更好,一直有人持不同意见。目前看社区,第一种方式比较占上风,因为原生 niri-flake 写得确实狗屎[1]。所以我自己 fork 了一个 pure 版本,移除了动态的 fetchGit 和所有 xwayland-satellite-stable 等 input。
具体要使用哪种,我的观点是:如果你需要 1. 追 niri 的最新版本 2. 在不同的电脑上使用不同的 niri 配置 3. 或者有一些模块化配置具有侵入性,需要修改 niri 的部分配置。那就用 niri-flake,可以借用 nixos 的配置合并等机制。否则就用 kdl 吧。
我最开始用的是自己写 kdl。但是现在我的模块很多有侵入性的,并且我不喜欢 kdl 这种配置语言 ref1 ref2,所以我使用 niri-flake。
配置
niri 只是一个光秃秃的 Window Manager,想要日常使用还有许多必不可少的东西。
- xwayland-satellite,这个必装,一个易于使用的兼容层,让 niri 可以运行 x11 软件。niri-flake 中有选项可以开启。
- 截图,虽然 niri 有自带窗口截图和全屏截图,但是并不好用。我目前使用 grim + slurp + satty 三件套:
grim -g "$(slurp)" - | satty -f -。 - 剪切板也需要自己搞。直接上配置:
{ self, lib, pkgs, inputs, config, devicename, username, features, ... }: { environment.systemPackages = with pkgs; [ wl-clipboard # wl-clipboard-rs # do not use this because https://github.com/YaLTeR/wl-clipboard-rs/issues/5 ]; home-manager.users.${username} = { services.cliphist = { enable = true; clipboardPackage = pkgs.wl-clipboard; }; services.wl-clip-persist = { enable = true; }; # auto record the clipboard history to cliphist programs.niri.settings.spawn-at-startup = lib.mkAfter [ { sh = "wl-paste --watch cliphist store"; } ]; # open a fuzzel menu to select the history programs.niri.settings.binds = { "Alt+V".action.spawn-sh = "cliphist list | fuzzel --dmenu | cliphist decode | wl-copy"; }; }; }
一些说明:
- niri spawn 有两种,一种是正常 spawn,接受一个参数列表,另一种是接受一个字符串的 spawn-sh(可以使用管道等),需要注意区分。
- niri flake 中,正常 spawn 就是
argv = [ "xxx", "xxx" ],spawn-sh 是sh = "xxx xxx"。
- niri flake 中,正常 spawn 就是
拼音输入法
用英文有点习惯,要不是我打开博客想写论文我都想不到中文输入法没装。
我先尝试 arch 上用习惯的 fcitx5-chinese:
i18n.defaultLocale = "zh_CN.UTF-8";
i18n.inputMethod = {
enabled = "fcitx5";
fcitx5.addons = with pkgs; [
fcitx5-chinese-addons
fcitx5-mozc
fcitx5-gtk
fcitx5-rime
fcitx5-configtool
];
};然而这并不能使用,在输入法里切到了中文键盘,打出的一直都是英文。
然后我尝试用了一下 ibus:
i18n.defaultLocale = "zh_CN.UTF-8";
i18n.inputMethod = {
enabled = "ibus";
ibus.engines = with pkgs.ibus-engines; [
rime
libpinyin
];
};用倒是能用了,但是我并不清楚 ibus 如何支持双拼。而且其自定义程度也不太行,切换输入法居然不能 Ctrl + Shift 而需要给实际键位,因此我还是折腾 fcitx。
跑了一次 fcitx5-diagnose,发现 locale 里显示的不太对,怎么全都是 en_US.UTF-8?明明已经设了 i18n.defaultLocale...后续设成这样:
defaultLocale = "zh_CN.UTF-8";
extraLocaleSettings = {
LANG = "zh_CN.UTF-8";
LC_ALL = defaultLocale;
};莫名奇妙就好了,在输入法设置里可以找到双拼键盘,开起来就行。
ps. 根据群友描述,只需要将 KDE 配置文件删除即可,与 defaultLocale 无关。
fcitx5-configtool 里双拼键盘下的“管理自定义词组”是坏的,点不开。我也懒得修了,把以前 archlinux 位于 ~/.local/share/fcitx5/pinyin/customphrase 的词库搬出来,拿到 home-manager 里 source 一下就好了(需要 重启 fcitx5:在 bash 里跑 kill `ps -A | grep fcitx5 | awk '{print $1}'` && fcitx5&),也符合 nixos 的原则。
然后被 rime 党吹的有点心动,想试试 rime。刚好 ryan4yin 佬就是 rime + 小鹤,于是我便直接开抄配置。可能是 overlays 哪出了问题,rebuild 的时候并没有把数据移到 rimedata,我也百思不得其解。后来手动移过去试了一下,发现真难用啊(包括快捷键啥都不懂)。于是滚回了 fcitx5-chinese-addon。
后来因为实在有一些多系统共用输入法的问题,然后先在 windows 上入坑了 rime,先把配置和快捷键吃明白。相关文章
然后再在 NixOS 上使用 Rime,这回我不再使用任何 overlays,并且已经有了自己的配置仓库,所以没遇到啥问题。
教训:不要学其他 NixOS 人洁癖,啥都必须给 NixOS 管。Rime 配置本身就是一个 home 里的文件夹,自己跟 Github 上的仓库维护同步即可。
代理
详见 代理客户端。我最开始使用 v2rayA 过渡,然后就跑到 dae 了。
在原生 NixOS 上跑代理,我没遇到过问题。真正棘手的是在 NixOS-WSL 上跑代理:dae 无法使用,因为 WSL 内核默认不支持 ebpf;而用 v2rayA 又很丑且支持的协议不多,我希望能使用 Windows 下的代理软件。然后就遇到了无穷多的问题:
问题一:即使设置了 HTTP_PROXY 等环境变量,在 NixOS 去 fetch github 时也无法生效。因为此时实际上去 fetch 的是 nix-daemon,而这玩意是不吃终端的代理的。解决方法是修改 nix-daemon service,有一个临时方案和一个永久方案。
使用
networking.proxy.default = proxyUrl;下面的方法也可以,但是上面更简洁,并且上面也会同时给终端环境设置 proxy env。(会设置成小写的 http_proxy 等)
systemd.services.nix-daemon.serviceConfig = {
Environment = "https_proxy=" + proxy;
};sudo mkdir -p /run/systemd/system/nix-daemon.service.d/
sudo tee /run/systemd/system/nix-daemon.service.d/override.conf << EOF
[Service]
Environment="https_proxy=http://localhost:10450"
EOF
sudo systemctl daemon-reload
sudo systemctl restart nix-daemon(ref)
问题二:WSL2 的 mirrord 网络问题太多了,其中最大的问题是 tcp 建连关闭端口本来应该收到 RST,结果现在收不到 RST 而直接 timeout (issue)。如果你关闭了 mirrord,那么自然就没法通过 127.0.0.1 访问 windows 代理端口。这时候我们可以将代理设为:https://_gateway:<port>。NixOS-WSL 的内置 nss-lookup 会自动把 _gateway 解析到网关 ip,也就是你 windows 的 ip。
问题三:虽然将代理设为 https://_gateway:<port> 可以在 cli 里解析,curl 也能正常用,但是 nix-daemon 可不这么想,curl 在 nix-daemon 的构建沙盒里运行时,无法通过系统的 nss-lookup 找到 _gateway 的映射,因此 nixos-rebuild 时仍然会 Could not resolve proxy: _gateway。最后我只能在 nix-daemon 里放弃 _gateway,改成运行时注入 env:
systemd.services.nix-daemon = {
path = with pkgs; [
iproute2
gawk
coreutils
];
# 在 nix-daemon 主进程启动前执行,动态抓取 IP
preStart = ''
# 获取 WSL 默认网关 IP
GATEWAY_IP=$(ip route show default | awk '{print $3}' | head -n1)
# 将代理地址写入临时环境文件
if [ -n "$GATEWAY_IP" ]; then
echo "http_proxy=http://$GATEWAY_IP:10450" > /run/nix-daemon-proxy.env
echo "https_proxy=http://$GATEWAY_IP:10450" >> /run/nix-daemon-proxy.env
echo "all_proxy=http://$GATEWAY_IP:10450" >> /run/nix-daemon-proxy.env
echo "no_proxy=${noProxy}" >> /run/nix-daemon-proxy.env
fi
'';
# 让 nix-daemon 加载这个动态生成的环境变量文件
# 前面的 "-" 表示如果文件不存在也不要报错
serviceConfig.EnvironmentFile = "-/run/nix-daemon-proxy.env";
};问题四:现在 nix-daemon 注入了 proxy env,构建时去拉 substituters 已经没问题了。但是如果你的 flake 有更新,nix/lix 去拉 flake input 的时候又会 5 次超时,报错 _gateway 无法解析。这时候尝试把终端 proxy env 换成真实 ipv4,则又可以成功 fetch 到 flake input。经过一番调查,发现:无论是 nix/lix,它们都会在 init 时(早于 eval 阶段)去设置 dns lookup source,这里的 FIXME 提到目前不会去 nss 拉。而 WSL 的 _gateway 解析就是在 nss-myhostname 插件里处理的,所以 eval 时自然也没法解析。
因此我只好放弃 _gateway,改为在各种 shell init 的时候,去注入 ipv4 的 proxy env。
# /run/nix-daemon-proxy.env 之前已经在 nix-daemon 的 preStart 里写入了
home-manager.users."${username}" =
let
sshProxyScript = pkgs.writeShellScript "ssh-gateway-proxy" ''
GATEWAY_IP=$(${pkgs.iproute2}/bin/ip route show default | ${pkgs.gawk}/bin/awk '{print $3}' 2>/dev/null)
if [ -n "$GATEWAY_IP" ]; then
exec ${pkgs.corkscrew}/bin/corkscrew "$GATEWAY_IP" 10450 "$@"
else
exit 1
fi
'';
in
{
programs = {
bash = {
enable = true;
initExtra = ''
set -a; . /run/nix-daemon-proxy.env 2>/dev/null; set +a
'';
};
fish = {
enable = true;
shellInit = ''
if test -f /run/nix-daemon-proxy.env
for line in (cat /run/nix-daemon-proxy.env)
# 跳过空行
if test -z "$line"
continue
end
# 拆分并存入局部变量 kv
set -l kv (string split -m1 = -- $line)
# 确保成功拆分成了 Key 和 Value 两个部分
if test (count $kv) -eq 2
set -gx $kv[1] $kv[2]
end
end
end
'';
};
ssh.matchBlocks."*".proxyCommand = "${sshProxyScript} %h %p";
};
};问题五:这玩意和 niri-flake 打出了一套巨痛的连招,因为 niri-flake 会让每次 rebuild 时都需要 fetchGit 联网,如果你把网络搞炸了,就没法直接通过 rebuild 修,只能先回滚到之前的版本,修好再 rebuild 再重试。
总之,在经历了如此一大串尝试、期间几度想要放弃在 NixOS-WSL 内使用 windows 代理后,我最终还是折腾出了一套使用 windows 代理的方法。
备份
nix 的配置显然用 git 备份的话非常舒适。起初我以为 /etc/nixos 不能放 .git 仓库。后来发现是不允许放未提交(dirty)的仓库。再后来我知道有配置可以强制 nixos 使用 dirty 仓库:
nix.settings.warn-dirty = false;这下终于可以不用 copy 到其他地方备份了。但是有一点需要注意:在 rebuild 前一定记得把新增的文件 git add 到暂存区!!否则会报 No such file or directory。
至于备份加密,我不喜欢用密钥管理,我就是喜欢单密码,因此用的是我自己写的 git-simple-encrypt(在 nur 上可用)。如果你有更高的安全需求可以看看 sops-nix 或 agenix。
home manager
我本来是不想用 home manager 的,全写在 configuration.nix 里也不麻烦。但是后来还是用了,因为有些配置文件确实不方便统一管,例如 KDE 的某些设置等。
从 configuration.nix 转移到 home manager 也不麻烦,thiscute 有很好的教程,并且它们的条目基本是兼容的。
但是进一步定制各种配置文件就没那么简单了,因为 home-manager 的 manual 就是一坨屎!建议直接用第三方的 options 搜索。
一般的 home manager 教程都会把 home manager modules 和 config modules 分开放。但是如果要将这两个 config 放在一起呢?答:只需要使用 home-manager.users."${username}" = {...}; 即可,它们是可以放在同一个文件里的。
注意,如果在这种 home manager 块里使用其他 flake 的 homeModules 等会更改 config/lib 的操作,记得写成函数形式以将 system config 透进来,例如 home-manager.users."${username}" = { config, lib, ... }: {...};。否则会出一些奇奇怪怪的问题。
plasma manager
在 nixos 下,很多 kde plasma6 的设置都不能在 conf 或 home manager 中定义。例如我尝试在 home.nix 中使用 services.random-background 更换壁纸,结果开机会显示 0.5s 壁纸然后被换回 kde 默认壁纸;还有包括锁屏时间设置的问题等等等等。
顾名思义,plasma manager 就是为了应对此情况出现的。它能保存的 kde 配置不算多,毕竟 kde 世界配置文件无穷无尽。但在一定程度上还是有用的。
Gaming
Linux 上游戏还是不太行。。。cs2 fps windows 140+,在 nix 上只有 50 左右。不过据群友说,在 vulkan 着色器编译完成后游戏可以大幅提高帧率。我暂时还未尝试。
不过平常玩点轻量级游戏问题不大,galgame,启动!你的下一台电脑又何必是游戏本!扯远了。
steam 游戏都能够点击即玩,proton 还是牛逼的。一些傻逼引擎的 galgame 无法在 wine 下正常运行,此时就需要安装虚拟机了。(某大佬:因为没法在 wine 上玩 gal 于是啃掉一本《软件安全与逆向分析》然后开始做逆向,学不来实在是学不来)
nix gaming 还有过不去的一关就是性能释放。。我这台电脑风扇总是不转,无法调整风扇转速,有点悲惨。风扇不转想打啥游戏都不行吧,立刻降频了。大量求助后仍然未果,所以立刻开寄。
虚拟机
提示
2025 年当下,最好的办法是基于 windows docker 的解决方案,例如 winboat,而不是虚拟机。
参考我的配置中的 others/vm.nix 安装 qemu kvm 及其运行库。
至于镜像我在 win10 和 win11,ltsc 和 tiny 里纠结了一下,选择了 tiny 11 23H3。可以看看教程,里面还有中文字体包,反正我来者不拒。下载从 web archive 或教程给的地址任选,反正我用了前者。
安装后,打开 Virtual Machine Manager,创建新虚拟机,选择下载的 iso 镜像。需要注意,如果 auto detect os 检测不到,需要在下面取消勾选 auto detect os 后自行输入 win11。反正这个 UI 逻辑是挺傻逼的。至于传文件,打开 USB 直通,我的移动硬盘可以分别在两端挂载,这样也不需要考虑太多。
感想:
- tiny 11 感觉也不 tiny。。。安装完占了我 17G 空间,感觉还是得 win10 吧。
- libvirt 也不好用,剪贴板和文件都没有傻瓜式解决方案。
快照
archived
NixOS 官方的图形界面安装镜像并没有提供 btrfs 的选项,合理猜测大部分人安装都是用的 ext4 分区,因此 btrfs 的资料应该不多。况且 NixOS 本身就是一个强可复现系统,按理来说并不需要快照作为保护系统的手段。然而可复现是一回事,可复现的难易度又是一回事。nixos-enter 的缺陷、minimal 镜像的折磨、外加 NVIDIA 驱动频繁崩溃,促使我用快照保护系统的安全。
在 nixos 上倒没有频繁打快照的必要,因为只要我有一个正常的快照,恢复后就可以从最新的配置文件 rebuild 回去(快照在这里起到的作用可能是 nixos-enter 的补充,使我能够使用盘里的缓存进行 rebuild),因此我选择不使用自动快照软件例如 snapper,而是手打。
nix 的根目录下都是符号链接,理论上有价值的快照理应是 nix 子卷快照而不是 root 快照。当然都打也可以。
sudo mkdir /nix/.snapshot
sudo btrfs subvolume snapshot /nix /nix/.snapshot/nix_20240629但是我还没有尝试过快照的恢复,等用到再更新吧。
理论上确实没必要为 /nix 打快照;之前解法是放一个 minimal.nix 作为崩溃的恢复,由于软件不多,重装也能快速装好。后来稳定下来以后发现根本就没有重装 nix 的机会。
root on tmpfs
由于大部分内容都是软链接,nixos 上能玩一个很骚的操作:把 root 挂载成 tmpfs。好处是每次重启所有东西都会被清,可以随便运行一些喜欢到处拉屎的软件。
这里面最重要的东西是 impermanence,它就是一个“mount mount 小工具”,可以将指定文件/文件夹 mount 到指定位置,这样它的修改也会同步到 source,并且比起 softlink 还可以跨子卷/分区。我看的教程是 Lan Tian @ Blog NixOS 系列(四):“无状态”操作系统,结果还是踩了亿点坑。
- 犯了官方文档中置顶标红的大忌:没有设 user 密码。(之前的 defaultPassword 删掉了)于是进不去系统。快照打的是
/nix,但是密码在/etc/shadow并不归/nix管;也没法直接改挂载选项把原先的/挂上,因为nixos-enter进去无法 rebuild,--bootloader也是nixos-rebuild的,nixos-install并没有。- 最后还是改挂载选项重新
nixos-install了,得益于使用 home-manager 把我的一大堆个人软件分开,本次 install 并没有花费太多时间。install 完至少能先进系统,再修配置,重启就结束了。
- 最后还是改挂载选项重新
- 然后发现我的
/etc/nixos配置本身没有被 impermanence。。。遂从原先的/里拷贝之,加入 impermanence,rebuild 即可。 - 不要把
/etc/shadow或/etc/passwd加入持久化。我加入以后开不了机,无法登录。
教程中把 /var 加入 impermanence,而我更喜欢用 btrfs 子卷管理。由于直接在 /var 创建子卷,子卷的 parent 会指向 /,所以我进了一次 live cd 创建子卷,保持 var 子卷与 root、home 等同级,然后把东西移过去,重启后写 hardware-configuration.nix 然后 rebuild 就行。
impermanence 在 NixOS 安装过程中是一个硬性的 bootstrap 来源;你必须成功安装 NixOS 后,才能获取原始的文件,进行持久化。然后教程里你需要手动将对应文件复制到 /nix/impermanence 下,这一步在 bootstraping 时也太麻烦了点,我思考以后想出一个好办法:在开启 root on tmpfs 时,将老的 / (root) 子卷挂到随便一个什么 /fakeroot 上;然后就可以用 impermanence 从 /fakeroot 里 mount 文件了,这样省去了手动 copy 文件的这一步骤,在我的配置中只需要修改一个 bool 值就可以自动切 root on tmpfs,将工作量降到了最低,非常舒服。
ISO 制作
官方的 ISO(不论 minimal 或图形界面的)都是一坨狗屎,图形界面安装没有 btrfs 而且 rebuild 会 fail,minimal 则是缺少工具(efibootmgr)并且无法匹配我的个人设置(手动挂一大堆 btrfs 子卷实在是有点折磨),所以还是自己做 iso 比较好。我自己做的 iso 有这些需求:
- 代理支持。如果正常安装 NixOS 的 flake,是没有办法在没有代理的环境成功安装的,如果 iso 没有代理的话就要用 minimal 配置进行一次 bootstrap。有代理的话就可以一次性解决了。
- btrfs 相关脚本。我在所有机器上使用同一份 btrfs 配置,但是手动 mount 一大堆子卷实在是太麻烦,因此需要内置一些脚本帮我快速创建并 mount 子卷,这样也方便滚挂了以后进 livecd 修。
- 使用 disko 一键分区是方便,但是如果你要在一块盘上装双系统不就挂比了吗?
- 其他实用工具,例如 fish shell 等。
制作 iso 可以用下面的方法将本机文件拷贝到镜像系统里:
isoImage.contents = [
{
source = ./config/absx.dae;
target = "/config.dae";
}
];但是这里有个坑,就是拷贝到的 / 位置在镜像里其实是独立的 /iso 文件夹,导致了我的 dae service fail。后来将 dae.configFile = "/iso/config.dae"; 设置后代理才能正常启动。
还有关于 btrfs 脚本,我最开始时是直接写在 fishshell 的 interactiveShellInit 里;但是后来发现脚本写锅了需要修改时,整个 iso 里没有地方可以找到这个脚本内容并修改,这是 fishshell 的问题。没办法,我只好写好 bash 脚本并 source 到 /iso 下的指定位置;后来我直接使用 pkgs.writeShellApplication 做成 derivation 了,方便调用,脚本内容也可以在 iso 里 fd 出来修改。
最后我还在构建 iso 时,将整个本机的 nixos 文件夹 source 到了镜像内,方便在 dae 挂了没法 git clone 的时候也可以拿到我的配置(虽然可能过时)进行安装。这里也有 source 时需要排除 .git 文件夹和其他文件夹的问题,免得增加 iso 镜像大小。
做完了这些,现在我的 iso 就得心应手了。iso 定义可以在配置里找到。
开发环境
我一直使用 vscode,但是在 nixos 上,还是有点难用的。
- vscode 全自动同步过程中,有部分插件没有同步过来。
- 手动在
home.nix里添加插件后,所有禁用的插件会被自动启用。 - 某些插件在非 feh 环境下无法运行;feh 环境下无法在终端使用 sudo。
- 无法使用 ssh 插件远程开发。
WebDAV
我想部署一个 WebDAV 服务,用于存放我的 galgame 存档 (game-save-manager)。然后还可以通过反代把这个 url 和端口暴露出来。
这是我使用 webdav-server-rs 的配置,日常内存占用 10MB,我很满意。
问题解决
内网 server 失联
在我搭好 easytier 虚拟组网实现内网穿透后,我可以异地连到我的内网 server。
此时我在异地修改了 easytier 配置,想在内网 server rebuild 一次。我提前想到了假如配置有问题会导致 server 失联,于是先在我的公网 VPS 上 rebuild 了一次,确保配置有效。
但是我忘记了,nixos rebuild 是会 stop 相关 service 的,一旦 stop,ssh 就会断开,本次 rebuild 就会被强行终止,导致失联。然后我错估了操作的风险,没有开 multiplexer,于是就被坑了。
后续我采取 timer 强制保活,保证 easytier 即使被 stop 也会自动拉起。
env 不生效
在 WSL 上修改了 environment.variables,rebuild 后重启终端,env 仍然不生效。然后发现需要重启 WSL 才能生效。
太大了
我要在 VPS 上安装我的 flake。但是太大了,VPS 被塞爆了,并且我在 copy path 时观察到一些例如 llvm,rustc 等我根本没有手动安装的软件,于是需要查哪些傻卵打包者引入了这些依赖。但是问题是我需要在不实际安装的情况下进行查询。
求助群友后,@wElmForest 给出了一个解法:
nix why-depends .#nixosConfigurations.<hostname>.config.system.build.toplevel nixpkgs#<依赖> --impure
# or
nix-tree .#nixosConfigurations.<hostname>.config.system.build.toplevel --impure实测是需要先使用 nix why-depends 进行 build 后,再用 nix-tree 进行查询,否则会爆 nix-tree: user error (Invalid path: ... Make sure that it is built, or pass '--derivation' if you want to work on the derivation.)。因为我是远程的配置,还没安装呢!当然没 build 过。不过抛开这个特性不谈,nix-tree 还是好用的。
找到了一个占用 3G 多的罪魁祸首 prettybat,直接把它干掉了。后续也找到 yazi 引入 ffmpeg 占用了 1.1G,也干掉了。
另外,清理当前环境,还有几个老生常谈,简单带过:
nix-collect-garbage -d
nix-store --optimise优势与劝退
我认为 NixOS 的优点有:
- Nix 语言作为一个配置语言本身设计得不错,支持复杂逻辑,考虑了配置合并与覆盖优先级与递归,支持动态类型和类型检查,支持字段描述,函数式与 std 分离等……
- 由于 NixOS 的可复现性,NixOS 可以让人敢于也愿意去尝试更多的新东西(毕竟不太需要担心它 break 你的系统或者在你的目录里拉屎)。
不过也不得不说 NixOS 自身的问题还是不小的,详见 狂喷 nixos 和 包管理器杂谈。
我的资历尚浅,只能够发出如此感叹。如果你希望看到更多对 nixos 的评价,可以看看 external 2.。
