我让 AI 参与维护 9 台 NixOS(下):协作流程、证据边界与风险

发布于

前两篇分别介绍了 NixOS 的声明式基础,以及权限、secrets、存储、监控、测试和任务验证中的真实案例。最后一篇讨论护栏之外的部分:NixOS 依然解决不了哪些 AI 风险,哪些动作不应默认授权,以及怎样把整套协作流程长期维护下去。

本文涉及的公开配置与实现:[LokiSharp/nix-config](https://github.com/LokiSharp/nix-config)。真实凭据保存在独立私有仓库中,不包含在公开配置里。

## 十八、NixOS 也解决不了的 AI 风险
前面一直在讲 NixOS 如何约束 AI,但不能因此把 NixOS 描述成安全沙箱。它只是让一部分状态更可见,并没有消除以下风险。

### 1. 仓库并不一定等于现实
声明式系统最容易让人产生一种错觉:仓库里写了什么,机器就一定是什么。

现实中仍然可能存在:
- 手工创建、没有纳入 Nix 的数据;
- 状态目录里的旧 schema;
- 云厂商控制台中的防火墙和路由;
- DNS 服务商尚未传播的记录;
- ZeroTier、Tailscale 等控制平面的外部配置;
- 已经失效但仍留在磁盘上的 credential;
- 某台长期离线、没有收到新 generation 的节点。

AI 只读仓库时,能解释的是声明意图,不是全部现实。因此我会要求它在结论中区分“配置上应该如此”和“已经从节点验证如此”。

### 2. 可求值不等于可运行
Nix 可以证明表达式有结果,不能证明端口没有被占用、硬盘没有坏、远端 API 没有限流。

即使 derivation 构建成功,服务也可能在 activation 后因为真实数据失败。一个典型例子是 PostgreSQL:配置文件和 package 都能构建,不代表数据库升级、扩展兼容性和磁盘空间一定正常。

所以验证链不能停在:
```
nix flake check
```
它后面仍然需要 Test activation、服务探针、目标节点检查和持续监控。

### 3. 回滚不了的外部副作用
NixOS generation 可以切回旧配置,但以下动作未必可逆:
- 数据库迁移;
- 向外部服务发送邮件;
- 更新 DNS 或云防火墙;
- 删除远端对象;
- 轮换后吊销旧密钥;
- 修改文件系统和 RAID;
- 将不兼容格式写入持久数据。

AI 如果把“配置可以回滚”推广成“整个任务可以回滚”,会严重低估风险。

我的做法是把外部写操作单独列出来,先做只读检查;能使用 dry-run、事务或双写过渡时,就不直接执行不可逆切换。

### 4. 权限边界仍然取决于执行环境
Nix 代码本身可以很纯,但运行 AI 的工作站可能拥有:
- Git push 权限;
- SSH agent 中已解锁的部署密钥;
- SOPS 私钥;
- 云服务 token;
- 到全部内网节点的路由;
- `sudo` 权限。

一次错误命令造成的影响,取决于这些现实权限,而不是配置语言。

因此,健康检查账户、部署账户和 secrets 管理身份最好分开。临时 `ssh-add` 也意味着授权窗口发生变化:AI 在此之前无法连接,不代表之后仍然没有权限。

我希望权限由工具和账户限制,而不是靠一句“请不要执行危险命令”限制。

### 5. AI 会优化可见指标
如果目标被描述成“让 CI 变绿”,它可能更新 expected;如果目标是“让 Alertmanager 安静”,它可能提高阈值;如果目标是“让 Deadnix 不报警”,它可能删除模板。

这并不是 AI 独有的问题,人也会为了指标做局部最优。区别是 AI 执行得更快、更彻底。

因此目标要回到真实结果:
- CI 变绿,是因为约束得到满足;
- 告警消失,是因为风险不再持续;
- 静态检查通过,是因为代码更清楚;
- 部署命令成功,是因为目标系统健康。

任何指标都只能作为代理,不能替代目标本身。

### 6. 错误可能在多个节点上被一致复制
声明式配置和自动部署的优势是统一,风险也是统一。

一段错误的公共模块可以同时影响所有 VPS;一个错误的主机过滤条件可以漏掉或选中全部节点;一个错误的防火墙 abstraction 可以在每台机器上忠实生效。

因此我不认为“所有节点配置一致”天然更安全。统一配置必须配合:
- 代表性构建;
- Test 金丝雀;
- 明确的节点选择预览;
- 分批 deployment;
- 每台节点的最终健康检查;
- 可快速停止后续批次的失败策略。

一致性减少了随机漂移,也增加了共同故障的可能。

### 7. 监控也可能观察错东西
监控脚本、exporter 、PromQL 和告警模板同样是代码,也会有 bug。

我遇到过的典型问题包括:
- 24 小时累计事件被当成持续故障;
- 采集 coredump 时反复读取 journal,反而增加压力;
- label 名称在不同 exporter 间不一致;
- exporter target 存在,但实际节点没有启用对应 collector;
- oneshot unit 曾失败一次,此后一直显示 failed;
- 告警邮件 resolved,只代表表达式不再成立,不代表根因自动修复。

让 AI 分析告警时,第一步不应是假设告警绝对正确,而要先追踪指标怎样产生。

### 8. 长期维护仍然需要删除和简化
AI 很擅长添加防御:多一个 assertion、多一个 timer、多一个告警、多一份文档。每项单独看都有理由,叠加起来却可能让系统难以理解。

所以我的后续对话里经常不是“再加什么”,而是:
- 这个检查有什么意义?
- 还有没有冗余、无意义的测试?
- 这些模块能不能整理?

成熟的 AI 协作不只是在生成,还包括对历史生成物进行删减。判断删除的依据不应该是当前有无引用,而是它是否仍然对应真实风险、是否有更好的统一来源、删除后是否失去重要知识。

### 9. 人仍然可能授权错误
最后,人在对话中说“好的,部署吧”,也可能是在没有完全理解差异时做出的决定。

AI 的报告如果太长、太肯定,反而容易让人机械批准。一个好的交接应该突出:
- 实际改了什么;
- 哪些检查已经完成;
- 哪些风险没有被覆盖;
- 下一步会改变哪些节点;
- 失败时在哪里停止;
- 是否涉及不可逆外部状态。

这也是为什么我希望结果导向而不是过程堆砌。人需要的是足够做决定的证据,而不是一千行终端输出。

NixOS 能提供护栏,AI 能提供执行力,最终的安全仍然来自边界、证据和有意识的授权。

## 十九、AI 不应该被允许默认做什么
### 1. 不经确认修改磁盘布局
Disko 的配置是声明式的,但执行分区仍然是破坏性操作。`/dev/sda` 和 `/dev/sdb` 写反,不会因为 Nix 是声明式语言就变得安全。

### 2. 不经 Test 直接全量发布
即使求值通过,服务仍可能因为真实数据、内核、网络和 secret 在 activation 后失败。

### 3. 不经语义判断删除“未使用代码”
预留模板、接口参数和教学示例可能有组织价值。

### 4. 不读取或输出真实 secret
我的 secrets 位于独立私有仓库,通过 sops-nix 在 activation 阶段解密。公开仓库只保存声明和 CI fixture。sops-nix 本身支持声明式 owner、group、mode 以及原子切换,但 AI 仍然不应该把解密值带进日志或对话。

### 5. 不把 silence 当成 resolved
Alertmanager silence 只是不通知。禁用规则、删除指标或清空 Alertmanager 也不等于问题解决。

### 6. 不把测试通过等同于部署成功
求值测试、构建、Test activation、服务探针和长期监控分别覆盖不同层次。

## 二十、我现在使用的一套协作流程
如果把目前经验压缩成一套可复用流程,大致如下。

### 阶段一:讨论
先让 AI 回答:
- 当前实现是什么;
- 问题是否真实存在;
- 修改会影响哪些节点;
- 有哪些替代方案;
- 最坏失败模式是什么;
- 能否只读验证。

不要求它一看到问题就改。

### 阶段二:限定范围
明确:
- 只改哪一项;
- 是否拆成多个 commit;
- 哪些占位符必须保留;
- 是否允许修改 secrets;
- 是否允许访问节点;
- 是否允许部署;
- Test 通过后是否自动继续。

### 阶段三:本地修改
要求 AI:
- 先读相关模块和测试;
- 避免复制跨节点逻辑;
- 更新同主题文档;
- 保留无关工作区修改;
- 使用 Conventional Commits。

### 阶段四:静态验证
```
just fmt
statix check .
deadnix --fail .
git diff --check
```

### 阶段五:求值与构建
```
just test
nix flake check --all-systems --no-build --show-trace
```
CI 还构建一个 Server、一个 VPS 和一个桌面节点,避免所有检查都停留在 evaluation。

### 阶段六:提交
不同语义问题分别提交,例如:
```
fix(monitoring): alert only on recent application crashes
fix(monitoring): require sustained high iowait
```
不要把文档、重构、功能和无关格式化塞进同一个 commit。

### 阶段七:Test 金丝雀
先部署 Test,检查:
- SSH 和主机名;
- systemd 总体状态;
- failed units;
- 当前内核;
- audit;
- overlay 网络;
- BIRD 和 DNS;
- HTTP 探针;
- 高优先级日志。

### 阶段八:其余节点与最终检查
只有 Test 通过才继续。所有 activation 完成后,重新跑全节点健康检查。

### 阶段九:监控反馈
部署完成不代表任务结束。继续观察:
- 是否出现新的 firing;
- resolved 是否自然发生;
- 是否有 timer 第一次运行失败;
- 是否有磁盘、coredump 或日志噪声;
- 告警阈值是否符合真实风险。

## 二十一、这种组合真正改变了什么
过去维护个人基础设施,最大的成本不是写配置,而是上下文切换。

几个月后再打开一个模块,我常常需要重新回忆:
- 为什么这个节点例外;
- 为什么某个端口不能改;
- 为什么某个参数看起来没用却必须保留;
- 为什么这里使用 Loki-Net 而不是家庭 LAN;
- 为什么日志只存在内存;
- 为什么某个告警不能立刻触发;
- 上次部署失败到底是配置还是丢包。

AI 可以帮助恢复上下文,但前提是这些上下文已经以代码、测试、注释、文档和 Git 历史存在。

因此,NixOS 与 AI 的关系并不是:
> NixOS 太复杂,所以需要 AI 帮我写。

更接近:
> NixOS 把系统知识变成了 AI 可以检索和修改的结构;而 AI 促使我把原本只存在脑中的约束继续变成测试和文档。

两者形成了正反馈:
- 配置越声明式,AI 越容易理解;
- 测试越明确,AI 越不容易悄悄犯错;
- 文档越接近代码,下一轮协作越高效;
- 部署反馈越结构化,故障分析越少依赖猜测。

## 二十二、它会不会让不懂 NixOS 的人直接维护服务器
我不建议。

AI 可以降低查询语法和搜索 option 的成本,但不会替你理解:
- Nix lazy evaluation;
- 模块合并优先级;
- `mkDefault`、`mkForce` 和条件配置;
- activation 与 build 的边界;
- Nix store 和 generation;
- systemd ordering;
- 文件系统与挂载依赖;
- 网络路由和防火墙;
- secret 的威胁模型。

如果完全不理解这些概念,人很难判断 AI 的方案是“可以运行”,还是“适合自己的系统”。

更好的入门方式是:
1. 先手工维护一台简单 NixOS;
2. 能读懂最终配置;
3. 学会 `nixos-option`、`nix eval`、`nix repl`;
4. 明白如何 rollback;
5. 再让 AI 帮助抽模块和补测试;
6. 最后才让它参与远程部署。

AI 应该提高人的上限,不应该掩盖基础知识的缺失。

## 二十三、如果你也想尝试,我建议从哪里开始
不要一开始就把生产集群交给 AI。

可以选择一个低风险问题:
- 给一台测试机声明常用软件;
- 把重复配置抽成模块;
- 为 SSH、防火墙或主机名写一个求值测试;
- 给 systemd 服务增加健康探针;
- 整理 README;
- 分析一次已经发生的告警,但先不授权修改。

然后观察 AI 是否能够:
- 先读现有代码;
- 说明自己的假设;
- 保留无关修改;
- 根据仓库风格写代码;
- 接受你对业务语义的纠正;
- 运行真实检查;
- 承认测试没有覆盖到某个风险;
- 把修改拆成可审查 commit。

如果它只会不断生成新代码,却不愿意删除无意义测试、不愿意解释风险,也不检查部署结果,那它还没有成为一个合格的协作者。

## 二十四、我认为下一阶段还值得做什么
这套仓库还远没有完成。

接下来比较值得做的事情包括:
- 为 Prometheus 告警加入时间序列行为测试;
- 把 OVH 磁盘路径从 `/dev/sdX` 改为稳定的 `/dev/disk/by-id`;
- 补完整故障恢复 runbook;
- 加强 bootstrap 镜像的设备确认与覆盘保护;
- 继续清理不再有数据来源的历史告警规则;
- 观察 nix-daemon 中断崩溃是否复现;
- 为重要 timer 导出最近成功时间,而不仅检查 unit 存在;
- 逐步把更多“人工记得检查”的事项变成机器可验证的不变量。

我暂时没有打算追求完全无人值守。

对个人基础设施而言,我更喜欢一种半自动模式:
- AI 做搜索、修改、测试和证据整理;
- Nix 做求值、构建和状态切换;
- Test 节点承担金丝雀风险;
- 监控系统观察现实结果;
- 人负责目标、语义、权限和最终授权。

## 二十五、一些经常被问到的问题
### 1. NixOS 的学习成本会不会抵消 AI 带来的收益?
前期很可能会。

如果只是维护一台很少变化的服务器,学 Nix 语言、模块系统、Flake 和调试工具的时间,不一定比手工配置更省。

收益通常在配置开始复用、节点开始增加、系统需要频繁升级时出现。AI 可以降低查 option、读错误和写样板代码的成本,但它无法消除概念成本。你仍然要知道为什么一个值被 `mkDefault` 覆盖、为什么文件不在 Flake source、为什么 activation 和 build 是两回事。

### 2. AI 能不能独立完成 NixOS 安装?
技术上可以参与很多步骤,权限上不应该默认独立完成。

生成 Disko 配置、检查设备列表、构建安装镜像都很适合 AI;真正执行分区和格式化时,我仍然希望人确认目标设备、已有数据和恢复路径。

磁盘操作的错误往往不可逆,而且设备名可能因启动环境变化。声明式配置降低了重复安装成本,没有降低选错磁盘的后果。

### 3. 有 rollback,是否就可以大胆让 AI 部署?
不可以。

rollback 需要你仍然能进入机器,或者有带外控制台。如果 AI 同时破坏了 SSH、防火墙、bootloader 或远程网络,旧 generation 也不会自动替你登录控制台。

数据库 schema、外部 API 调用和删除数据也不一定随系统 generation 回滚。

我把 rollback 看成最后一道保险,不把它当作放弃审查的理由。

### 4. 为什么一定要有 Test 节点?
不一定。只有一台机器时,也可以使用 VM、`nixos-rebuild build-vm`、临时云主机或本地虚拟化。

重要的不是节点名字,而是生产之前存在一层真实 activation。纯求值无法发现端口占用、设备缺失、网络不可达和服务对真实数据的反应。

我的 Test 节点长期存在,是因为它还能验证 overlay、DNS、BIRD 和远程 SSH 路径,比临时 VM 更接近真实环境。

### 5. 为什么不让 CI 直接部署 Test ?
这是风险和凭据边界的选择。

CI 可以做纯求值和构建,不需要接触真实节点。自动部署意味着 CI runner 要持有网络访问权和部署密钥,还要定义并发、回滚与异常处理。

我的当前选择是让 CI 只运行不改变实际节点的检查,部署由明确的人机协作流程触发。将来如果引入自托管 runner 和更严格审批,再考虑自动化金丝雀也不迟。

### 6. 私有 secrets 仓库会不会让 AI 无法工作?
不会。绝大多数任务只需要知道 secret 的名称、owner、group、mode 和使用位置,不需要知道值。

CI 使用不含真实秘密的 fixture,验证模块能否求值和生成正确路径。真实 secret 只在授权节点 activation 时解密。

当确实需要更新 SMTP 密码时,我自己用 SOPS 修改私有仓库,再让 AI 检查引用和提交范围。这比把密码粘贴进聊天安全得多。

### 7. AI 误删代码怎么办?
首先用 Git 保证差异可见,修改前后都检查状态。

其次,小提交比一次几千行重构更容易发现问题。预留模板要有注释和静态检查忽略,不能只靠“我记得这里不能删”。

最后,可以让 AI 专门审查最近提交中的删除操作:
- 删除的是实现、接口、示例还是占位符;
- 是否仍有动态消费者;
- 是否只是因为静态工具报警;
- Git 历史为什么引入它;
- 删除后文档是否仍然提到它。

AI 会犯错,但 Git 和测试让错误不必成为永久事实。

### 8. AI 写的 Nix 代码质量怎么样?
局部样板通常不错,跨模块语义取决于上下文。

它很会写 option、`mkIf`、systemd unit 和简单 assertion,也很会根据已有模块模仿风格。比较容易出问题的地方是:
- 忽略模块合并后的最终值;
- 用 `mkForce` 粗暴解决冲突;
- 把可选字段假设为必有;
- 重复一个已有 abstraction;
- 为静态检查做无意义改写;
- 写出通过但没有保护风险的测试;
- 忘记新文件需要进入 Flake source。

所以我更愿意让它扩展现有模式,而不是在不了解仓库时发明第二套架构。

### 9. 这种做法适合团队吗?
适合,但团队需要比个人仓库更严格的权限和审查。

至少应该有:
- protected branch;
- 必须通过的 CI;
- code review;
- secrets 与代码分离;
- 部署身份和健康检查身份分离;
- 可追溯的审批;
- 生产节点分批发布;
- 明确的事故响应责任。

AI 生成的 commit 应该和人写的 commit 接受同一套标准,不能因为修改来自 AI 就降低审查,也不能因为来自 AI 就一律拒绝。

### 10. 维护这套测试会不会很累?
会,所以测试必须保护稳定不变量。

如果每改一个展示名称都要更新几十份 fixture,说明测试过度耦合实现。真正值得长期维护的,是地址唯一性、权限边界、部署顺序、安全选项和监控覆盖这类约束。

删除无意义测试也是维护质量的一部分。测试套件应该让人更敢改,而不是让任何合理变化都变得痛苦。

### 11. 快照、监控、类型、文档是不是把个人仓库搞得太复杂了?
复杂度没有消失,只是以前藏在人的记忆和临时命令里。

当然也有过度工程的可能。判断一项机制是否值得保留,可以问:
- 它对应发生过或后果严重的风险吗;
- 失败时有没有明确动作;
- 是否能自动派生,还是每台机器重复配置;
- 维护成本是否高于它避免的事故;
- 删除它以后,谁负责记住这件事。

我不会因为“企业里通常这样做”就在个人服务器上复制整套流程。当前这些能力大多来自真实问题:SSH 锁出风险、网络地址冲突、磁盘延迟、快照恢复、告警噪声和 AI 误删。

### 12. AI 会不会在一次长对话后失去上下文?
会,而且不应该把聊天窗口当作唯一知识库。

模型能处理的上下文再长,也不等于永远记住所有决定。任务跨越多天后,早期细节可能被压缩,人自己也可能忘记当时为什么选择某种方案。

我的处理方式是让重要上下文尽快落地:
- 稳定规则写成 assertion 或测试;
- 非显然选择写进注释;
- 操作流程写进 runbook;
- 每个语义变化独立 commit;
- 未完成事项记录在文档或 issue;
- 新一轮工作先重新读取当前仓库,而不是相信聊天记忆。

这也是 NixOS 适合 AI 的另一个原因:仓库才是事实来源,对话只是生成和讨论事实的过程。

### 13. 怎么防止 AI 为了通过测试而修改测试?
先要求它说明失败测试代表的需求,再授权修复。

当实现与测试冲突时,有三种可能:
1. 实现错了,应该修实现;
2. 需求变化了,应该一起更新实现、测试和文档;
3. 测试本来就没有意义,应该删除并解释原因。

最糟糕的做法是看到红色就把 expected 改成当前输出。这会让 CI 变绿,却不再保护任何东西。

我会特别查看测试差异是否只是批量更新 fixture,也会问“如果未来发生哪一种回归,这个测试会失败”。如果回答不出来,它大概不值得存在。

### 14. 为什么还要在意 commit 规范?
因为 Git 历史是 AI 下次调查的重要输入。

`fix: update files` 几乎没有提供信息;`fix(monitoring): alert only on recent application crashes` 则说明了范围、意图和行为变化。

小而清楚的 commit 还允许:
- 只回退有问题的一项;
- 用 bisect 定位回归;
- 区分重构与功能变化;
- 根据历史解释某个例外;
- 在部署前准确选择受影响节点。

Conventional Commits 不是为了追求形式,它是在给未来的人和 AI 建立可搜索的决策索引。

### 15. 最后,AI 到底替我省了什么?
它没有替我承担系统所有权。

它省下的是大量搜索、对照、机械修改、日志聚合、测试样板、文档同步和重复验证时间。它也迫使我把含糊经验说成可执行约束。

而我仍然负责:
- 为什么做;
- 哪种风险可以接受;
- 哪些机器可以动;
- 哪些数据不能暴露;
- 什么证据足以继续发布;
- 什么时候应该停止自动化并亲自接管。

这不是“AI 替我运维”,而是我把 AI 放进一套可审计的运维系统。

## 二十六、结语
很多人讨论 AI 编程时,关注的是它能写多少代码。

在我这次 NixOS 实践里,代码生成反而不是最重要的部分。

更重要的是,它能否参与一个有反馈的工程过程:
- 修改之前理解现状;
- 修改以后接受类型和求值检查;
- 用测试表达长期约束;
- 用 commit 保留可审计历史;
- 用 Test 节点面对真实系统;
- 用监控验证自己的判断;
- 犯错以后把经验写回仓库。

NixOS 提供了一个很适合这种合作的环境:系统不是一堆不可追踪的手工操作,而是一棵可以求值的配置;部署不是一次覆盖,而是一个新的 generation;依赖不是“我机器上刚好有”,而是被输入和 lock file 记录;跨节点规则不是管理员的记忆,而可以成为纯函数和测试。

AI 仍然会误解需求,会删除不该删的模板,会写出恒真的测试,会把“命令成功”误判为“测试真的运行了”,也会提出没有必要的保险判断。

但在 NixOS 中,这些错误更有机会在到达生产环境之前留下痕迹。

所以,如果要用一句话概括我的体验:

**NixOS 不是因为配置语言特殊才适合 AI,而是因为它把系统维护变成了一场可以反复验证、逐步收紧权限、失败后回滚、并且把经验沉淀为代码的对话。**

这大概也是我目前见过,人与 AI 协作维护操作系统最有意思的一种方式。

---

## 参考资料
- [nix.dev:Nix 官方文档](https://nix.dev/)
- [nix.dev:Flakes 概念](https://nix.dev/concepts/flakes.html)
- [NixOS Wiki:NixOS 概览与 generations](https://wiki.nixos.org/wiki/Overview_of_the_NixOS_Linux_distribution)
- [sops-nix:声明式 secrets 管理](https://github.com/Mic92/sops-nix)
- [Prometheus:告警规则单元测试](https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/)

---

原文链接:[点击查看](https://www.v2ex.com/t/1231237)

评论

暂无评论。

0.067654s