缘起#
上一篇讲了NixOS的核心机制,这篇讲落地:一个Git仓库具体怎么组织,才能声明式地管理三台各不相同的机器。这个仓库就放在/home/lijin/nixos-config,目前280多个提交、174个系统代次都记录在里面。
前提#
- 已读完“NixOS(二):核心概念”
- 熟悉Git的基本操作
仓库与/etc/nixos的关系#
一个常见疑问:NixOS的系统配置默认在/etc/nixos/configuration.nix,为什么我们的仓库是独立的?
这是有意为之。安装器生成的/etc/nixos保留为初始系统的配置(也是应急回退点),而日常我们构建和激活的是Git仓库里的flake:
| |
给nixos-rebuild显式传入flake路径后,/etc/nixos就不再参与构建。好处是:配置永远以Git仓库为准,可以正常git pull、git diff、审查提交;坏处几乎没有——唯一要注意的是别把两边的修改搞混。
目录结构#
| |
配置的三个作用域#
仓库组织的第一原则是把每条设置放到尽可能窄的作用域里,判断标准只有三条:
- 所有机器都需要的策略 →
modules/common.nix。包括网络、桌面服务、用户账号、命令行工具箱(bat、fd、fzf、ripgrep等)、SSH策略、KRDP防火墙放行、Tailscale、Nix自身设置。注意:硬件相关的模块绝不能放在这里——Surface的触摸板驱动不能"共享"给ThinkPad。 - 某台机器专属的策略 →
hosts/<主机>/configuration.nix。例如Surface专属的OpenClaw防火墙规则和Plasma Wayland自动登录策略;Halo本地的AI服务(Ollama、q38rocm、Steam);T460s"合盖不休眠"的logind策略。 - 安装器生成的硬件数据 →
hosts/<主机>/hardware-configuration.nix。磁盘分区、文件系统、EFI、swap这些是机器物理属性,由nixos-generate-config生成。不要随手编辑;如果存储布局变了,重新生成并审查diff:
| |
用户级配置(Home Manager)同样分两层:home/lijin.nix是共享的,机器专属的用户设置放home/hosts/<machine>.nix。例如Surf和Halo共享同一份Plasma外观与面板布局(由plasma-manager模块管理),但显示器拓扑各自不同,留在各主机目录里。
一个具体例子:KRDP远程桌面的防火墙策略。“放行3389等端口"本身是共享策略(三台机器都开了KRDP),写在common.nix里;但Surf在KRDP启动后还需要一个Home Manager的自动锁屏服务来保护无人值守的物理控制台,这是Surface专属的,就放在home/hosts/krdp-autolock.nix。
mkHost:用函数消除三台机器的重复#
flake.nix里没有把三台机器的配置各写一遍,而是定义了一个mkHost函数:
| |
每台机器只是调用它一次,传入各自的差异:
| |
注意Surface额外导入了nixos-hardware的Surface模块(提供触摸板IPTS驱动和修补过的Linux Surface内核),而T460s一个额外模块都不用——用标准内核即可。输出端则注册了三个名字加一个兼容别名:
| |
这样nixos-rebuild --flake .#t460s、.#halo、.#surface-pro-6各自构建对应机器,互不干扰。
Home Manager集成#
Home Manager管理用户级配置(~/.config下的文件、用户包、shell初始化等)。我们把它作为NixOS模块引入flake,而不是独立使用:nixos-rebuild switch会同时应用系统配置和用户配置,不需要再单独跑home-manager switch。几个相关设置:
| |
日常变更流程#
对任何一次配置变更,标准动作是:
| |
要点:
nix flake check不构建任何东西,只评估——几秒钟就能知道配置语法/选项对不对,是AI和人都应该先跑的一步。test先试、switch再固化;改坏了用sudo nixos-rebuild switch --rollback退回。- 提交前先
git diff审一遍:NixOS里一次"提交"对应的是"下一次激活时整台机器要变成的样子”,值得看清楚。
刻意更新flake.lock#
升级依赖(nixpkgs快照、home-manager等)是另一类变更,风险更高,因为可能触发大规模重建。我们的流程:
| |
为什么Surf要特别小心:它的Linux Surface内核是本地编译的,输入更新可能使缓存失效、触发完整重编。代次103那次更新花了四个多小时、峰值占用约45GiB磁盘,更早的一次尝试在快结束时以No space left on device失败。所以Surf更新前要预留至少50GiB空闲空间(/、/nix和构建临时区共用同一文件系统,看/的余量即可),并且留出几个不被打断的小时。
逐代次发布说明:当历史档案用的Git#
仓库里每台机器有一份"构建笔记"(docs/update-surf.md、update-halo.md、update-t460s.md),按代次倒序记录每次实际激活:
| |
几个约定:
- 每个代次条目必须对应一次真实激活(时间取系统profile链接的时间戳),绝不虚构;只改文档或工具的提交记在"Git-only follow-up changes"一节,不占代次编号。
- 这些笔记是历史档案:即使
nix-collect-garbage删掉了旧代次的store路径,记录仍然保留——它描述的是"当时实际激活过什么配置",而不是"现在还能回滚到哪里"(后者用nixos-rebuild list-generations查看)。 - 对AI维护者来说,这份笔记加上Git历史就是完整的上下文:接手任何一台机器前,先读它的构建笔记。
Git注意事项#
- 不要随意把整个家目录提交进Git:
/home/lijin里可能有密码、token、SSH私钥、浏览器数据。声明式的系统配置只进这个仓库;用户级"想管"的文件通过Home Manager的home.file等机制显式声明,而不是git add ~。 - 仓库初期只有本地提交;备份方式是创建私有远程仓库并推上去,推送前检查机密没有混入(机密一律走SOPS加密,见本系列第9篇)。
小结#
| 要放的东西 | 去哪里 |
|---|---|
| 所有机器共享的策略 | modules/common.nix |
| 某台机器专属的策略 | hosts/<主机>/configuration.nix |
| 安装器生成的硬件数据 | hosts/<主机>/hardware-configuration.nix(重新生成+审diff,不手改) |
| 共享的用户级配置 | home/lijin.nix(大程序拆到home/<program>/) |
| 机器专属的用户设置 | home/hosts/<主机>.nix |
| 机密 | secrets/下SOPS加密文件 |
| 容器v2主机清单 | container-config/hosts/<主机>.yaml |
| 每次激活的记录 | docs/update-<主机>.md |
下一篇讲我们怎么把这套流程"交给AI":一个维护者技能(maintainer skill)如何定义标准工作流、验证顺序和安全规则。
相关文章#
- “NixOS(二):核心概念——声明式配置、flake与回滚”:本篇的机制基础
- “shell config系列”:
shell-config这个flake输入的上游仓库,我们的Zsh/Oh My Posh配置源头
