前两篇分别介绍了 NixOS 的声明式基础,以及权限、secrets 、存储、监控、测试和任务验证中 的真实案例。最后一篇讨论护栏之外的部分:NixOS 依然解决不了哪些 AI 风险,哪些动作不应 默认授权,以及怎样把整套协作流程长期维护下去。
本文涉及的公开配置与实现:LokiSharp/nix-config。 真实凭据保存在独立私有仓库中,不包含在公开配置里。 十八、NixOS 也解决不了的 AI 风险 前面一直在讲 NixOS 如何约束 AI ,但不能因此把 NixOS 描述成安全沙箱。它只是让一部分状态 更可见,并没有消除以下风险。
- 仓库并不一定等于现实 声明式系统最容易让人产生一种错觉:仓库里写了什么,机器就一定是什么。 现实中仍然可能存在:
手工创建、没有纳入 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 不应该被允许默认做什么
- 不经确认修改磁盘布局 Disko 的配置是声明式的,但执行分区仍然是破坏性操作。/dev/sda 和 /dev/sdb 写反, 不会因为 Nix 是声明式语言就变得安全。
- 不经 Test 直接全量发布 即使求值通过,服务仍可能因为真实数据、内核、网络和 secret 在 activation 后失败。
- 不经语义判断删除“未使用代码” 预留模板、接口参数和教学示例可能有组织价值。
- 不读取或输出真实 secret 我的 secrets 位于独立私有仓库,通过 sops-nix 在 activation 阶段解密。公开仓库只保存 声明和 CI fixture 。sops-nix 本身支持声明式 owner 、group 、mode 以及原子切换,但 AI 仍然 不应该把解密值带进日志或对话。
- 不把 silence 当成 resolved Alertmanager silence 只是不通知。禁用规则、删除指标或清空 Alertmanager 也不等于问题 解决。
- 不把测试通过等同于部署成功 求值测试、构建、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 节点承担金丝雀风险; 监控系统观察现实结果; 人负责目标、语义、权限和最终授权。
二十五、一些经常被问到的问题
- NixOS 的学习成本会不会抵消 AI 带来的收益? 前期很可能会。 如果只是维护一台很少变化的服务器,学 Nix 语言、模块系统、Flake 和调试工具的时间,不一定 比手工配置更省。 收益通常在配置开始复用、节点开始增加、系统需要频繁升级时出现。AI 可以降低查 option 、 读错误和写样板代码的成本,但它无法消除概念成本。你仍然要知道为什么一个值被 mkDefault 覆盖、为什么文件不在 Flake source 、为什么 activation 和 build 是两回事。
- AI 能不能独立完成 NixOS 安装? 技术上可以参与很多步骤,权限上不应该默认独立完成。 生成 Disko 配置、检查设备列表、构建安装镜像都很适合 AI ;真正执行分区和格式化时,我仍然 希望人确认目标设备、已有数据和恢复路径。 磁盘操作的错误往往不可逆,而且设备名可能因启动环境变化。声明式配置降低了重复安装成本, 没有降低选错磁盘的后果。
- 有 rollback ,是否就可以大胆让 AI 部署? 不可以。 rollback 需要你仍然能进入机器,或者有带外控制台。如果 AI 同时破坏了 SSH 、防火墙、 bootloader 或远程网络,旧 generation 也不会自动替你登录控制台。 数据库 schema 、外部 API 调用和删除数据也不一定随系统 generation 回滚。 我把 rollback 看成最后一道保险,不把它当作放弃审查的理由。
- 为什么一定要有 Test 节点? 不一定。只有一台机器时,也可以使用 VM 、nixos-rebuild build-vm、临时云主机或本地 虚拟化。 重要的不是节点名字,而是生产之前存在一层真实 activation 。纯求值无法发现端口占用、 设备缺失、网络不可达和服务对真实数据的反应。 我的 Test 节点长期存在,是因为它还能验证 overlay 、DNS 、BIRD 和远程 SSH 路径,比临时 VM 更接近真实环境。
- 为什么不让 CI 直接部署 Test ? 这是风险和凭据边界的选择。 CI 可以做纯求值和构建,不需要接触真实节点。自动部署意味着 CI runner 要持有网络访问权和 部署密钥,还要定义并发、回滚与异常处理。 我的当前选择是让 CI 只运行不改变实际节点的检查,部署由明确的人机协作流程触发。将来如果 引入自托管 runner 和更严格审批,再考虑自动化金丝雀也不迟。
- 私有 secrets 仓库会不会让 AI 无法工作? 不会。绝大多数任务只需要知道 secret 的名称、owner 、group 、mode 和使用位置,不需要知道 值。 CI 使用不含真实秘密的 fixture ,验证模块能否求值和生成正确路径。真实 secret 只在授权节点 activation 时解密。 当确实需要更新 SMTP 密码时,我自己用 SOPS 修改私有仓库,再让 AI 检查引用和提交范围。 这比把密码粘贴进聊天安全得多。
- 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 协作维护操作系统最有意思的一种方式。
参考资料
nix.dev:Nix 官方文档 nix.dev:Flakes 概念 NixOS Wiki:NixOS 概览与 generations sops-nix:声明式 secrets 管理 Prometheus:告警规则单元测试