跳过正文

NixOS(三):仓库架构——一个flake如何管理三台机器

锦李本鲤
作者
锦李本鲤
光锥之内,皆为命运。
NixOS系列 - 这篇文章属于一个选集。
§ 3: 本文

缘起
#

上一篇讲了NixOS的核心机制,这篇讲落地:一个Git仓库具体怎么组织,才能声明式地管理三台各不相同的机器。这个仓库就放在/home/lijin/nixos-config,目前280多个提交、174个系统代次都记录在里面。

前提
#

仓库与/etc/nixos的关系
#

一个常见疑问:NixOS的系统配置默认在/etc/nixos/configuration.nix,为什么我们的仓库是独立的?

这是有意为之。安装器生成的/etc/nixos保留为初始系统的配置(也是应急回退点),而日常我们构建和激活的是Git仓库里的flake:

1
sudo nixos-rebuild switch --flake /home/lijin/nixos-config#surface-pro-6

nixos-rebuild显式传入flake路径后,/etc/nixos就不再参与构建。好处是:配置永远以Git仓库为准,可以正常git pullgit diff、审查提交;坏处几乎没有——唯一要注意的是别把两边的修改搞混。

目录结构
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
nixos-config/
├── flake.nix                 # 入口:声明输入与三台机器的输出
├── flake.lock                # 锁定所有输入的版本(必须进Git)
├── modules/
│   └── common.nix            # 所有机器共享的策略
├── hosts/
│   ├── surface-pro-6/        # 每台机器一个目录
│   │   ├── configuration.nix
│   │   └── hardware-configuration.nix   # 安装器生成,勿手改
│   ├── halo/
│   └── t460s/
├── home/
│   ├── lijin.nix             # 所有机器共享的用户级配置(Home Manager)
│   ├── <program>/            # 较大程序单独成模块(wezterm、plasma…)
│   └── hosts/<machine>.nix   # 用户级的机器专属覆盖
├── secrets/                  # SOPS加密的机密(按global/host/service分层)
├── container-config/         # 容器v2的主机清单
├── packages/ scripts/ schemas/ tests/   # 自研包与工具
└── docs/                     # 操作手册、软件文档、逐代次发布说明

配置的三个作用域
#

仓库组织的第一原则是把每条设置放到尽可能窄的作用域里,判断标准只有三条:

  1. 所有机器都需要的策略 → modules/common.nix。包括网络、桌面服务、用户账号、命令行工具箱(bat、fd、fzf、ripgrep等)、SSH策略、KRDP防火墙放行、Tailscale、Nix自身设置。注意:硬件相关的模块绝不能放在这里——Surface的触摸板驱动不能"共享"给ThinkPad。
  2. 某台机器专属的策略 → hosts/<主机>/configuration.nix。例如Surface专属的OpenClaw防火墙规则和Plasma Wayland自动登录策略;Halo本地的AI服务(Ollama、q38rocm、Steam);T460s"合盖不休眠"的logind策略。
  3. 安装器生成的硬件数据 → hosts/<主机>/hardware-configuration.nix。磁盘分区、文件系统、EFI、swap这些是机器物理属性,由nixos-generate-config生成。不要随手编辑;如果存储布局变了,重新生成并审查diff:
1
2
sudo nixos-generate-config
git 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函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
mkHost = { hostModule, extraModules ? [ ], homeModules ? [ ], ... }:
  nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    specialArgs = { inherit inputs; };
    modules = [
      hostModule
      inputs."shell-config".nixosModules.default   # 共享的Shell配置
      inputs.sops-nix.nixosModules.sops             # 机密管理
      { home-manager.users.lijin.imports = [ ./home/lijin.nix ] ++ homeModules; ... }
    ] ++ extraModules;
  };

每台机器只是调用它一次,传入各自的差异:

1
2
3
4
5
6
7
surfacePro6 = mkHost {
  hostModule = ./hosts/surface-pro-6/configuration.nix;
  extraModules = [ nixos-hardware.nixosModules.microsoft-surface-pro-intel ];
  homeModules = [ ./home/hosts/surface-pro-6.nix ./home/hosts/krdp-autolock.nix ];
};
halo = mkHost { hostModule = ./hosts/halo/configuration.nix; homeModules = [ ./home/hosts/halo.nix ]; };
t460s = mkHost { hostModule = ./hosts/t460s/configuration.nix; };

注意Surface额外导入了nixos-hardware的Surface模块(提供触摸板IPTS驱动和修补过的Linux Surface内核),而T460s一个额外模块都不用——用标准内核即可。输出端则注册了三个名字加一个兼容别名:

1
2
3
4
5
6
nixosConfigurations = {
  surf = surfacePro6;
  "surface-pro-6" = surfacePro6;
  nixos = surfacePro6;   # 旧名字的兼容别名
  inherit halo t460s;
};

这样nixos-rebuild --flake .#t460s.#halo.#surface-pro-6各自构建对应机器,互不干扰。

Home Manager集成
#

Home Manager管理用户级配置(~/.config下的文件、用户包、shell初始化等)。我们把它作为NixOS模块引入flake,而不是独立使用:nixos-rebuild switch会同时应用系统配置和用户配置,不需要再单独跑home-manager switch。几个相关设置:

1
2
3
4
5
{
  home-manager.useGlobalPkgs = true;      # 用户包跟随系统的pkgs版本
  home-manager.useUserPackages = true;
  home-manager.backupFileExtension = "hm-backup";   # 被覆盖的旧文件留备份
}

日常变更流程
#

对任何一次配置变更,标准动作是:

1
2
3
4
5
6
7
8
9
cd /home/lijin/nixos-config
git pull                                  # 如果仓库有远程
$EDITOR modules/common.nix                # 或 hosts/<machine>/configuration.nix
nix flake check                           # 快速评估所有主机配置是否合法
sudo nixos-rebuild test --flake .#surface-pro-6   # 临时激活,本次开机有效
sudo nixos-rebuild switch --flake .#surface-pro-6 # 正式激活,生成新代次
git diff
git add home modules hosts flake.nix flake.lock README.md docs
git commit -m "描述这次变更"

要点:

  • nix flake check不构建任何东西,只评估——几秒钟就能知道配置语法/选项对不对,是AI和人都应该先跑的一步。
  • test先试、switch再固化;改坏了用sudo nixos-rebuild switch --rollback退回。
  • 提交前先git diff审一遍:NixOS里一次"提交"对应的是"下一次激活时整台机器要变成的样子”,值得看清楚。

刻意更新flake.lock
#

升级依赖(nixpkgs快照、home-manager等)是另一类变更,风险更高,因为可能触发大规模重建。我们的流程:

1
2
3
4
5
6
df -h /                                   # 先看磁盘余量
sudo nix-collect-garbage --delete-older-than 7d   # 回收旧代次(Surf尤其需要)
nix flake update
git diff                                  # 审查锁文件变化
sudo nixos-rebuild switch --flake .#<主机>
git add flake.lock && git commit -m "Update flake inputs"

为什么Surf要特别小心:它的Linux Surface内核是本地编译的,输入更新可能使缓存失效、触发完整重编。代次103那次更新花了四个多小时、峰值占用约45GiB磁盘,更早的一次尝试在快结束时以No space left on device失败。所以Surf更新前要预留至少50GiB空闲空间(//nix和构建临时区共用同一文件系统,看/的余量即可),并且留出几个不被打断的小时。

逐代次发布说明:当历史档案用的Git
#

仓库里每台机器有一份"构建笔记"(docs/update-surf.mdupdate-halo.mdupdate-t460s.md),按代次倒序记录每次实际激活:

1
2
3
4
5
6
7
8
## Generation 115

- **Time:** 2026-09-01 17:14
- **Git commit:** `0652b33`(激活后立即提交)
- **Main changes:**
  - 完成打包版MebTTY部署并设为活动服务。
  - 让systemd在应用namespace加固前用StateDirectory创建状态目录,修复了114代次的启动失败。
  - 验证了健康检查端点、PAM终端创建、PTY重连与文件浏览。

几个约定:

  • 每个代次条目必须对应一次真实激活(时间取系统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系列 - 这篇文章属于一个选集。
§ 3: 本文