前两篇分别介绍了 NixOS 的声明式基础,以及权限、secrets 、存储、监控、测试和任务验证中 的真实案例。最后一篇讨论护栏之外的部分:NixOS 依然解决不了哪些 AI 风险,哪些动作不应 默认授权,以及怎样把整套协作流程长期维护下去。
本文涉及的公开配置与实现: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 的方案是“可以运行”,还是“适合自己的系统”。
更好的入门方式是:
- 先手工维护一台简单 NixOS ;
- 能读懂最终配置;
- 学会
nixos-option、nix eval、nix repl; - 明白如何 rollback ;
- 再让 AI 帮助抽模块和补测试;
- 最后才让它参与远程部署。
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 为了通过测试而修改测试?
先要求它说明失败测试代表的需求,再授权修复。
当实现与测试冲突时,有三种可能:
- 实现错了,应该修实现;
- 需求变化了,应该一起更新实现、测试和文档;
- 测试本来就没有意义,应该删除并解释原因。
最糟糕的做法是看到红色就把 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 协作维护操作系统最有意思的一种方式。