这是一篇迟到了的,对于 Determinate Nix的 evaluation
因为已经从去年11月份,把workstation从Linux Desktop重新换回MBP,也已经过了将近半年了。之所以没有使用Nix官方的 nix-darwin安装,因为之前在使用官方方案时,有过 GC + prune generations 时,直接把 nix-darwin 本身直接删掉了(也就是把nix本身搞挂了),当时具体是什么情况,现在已经记不清了,但是真的是非常惨痛的记忆,所以这次转向到了 Determinate Nix。
但是之所以仍然认为是 evaluation,而非 review,是因为确实还是没太把这玩意玩明白,并且至今也不确定是否什么时候就用回 nix-darwin 官方方案了。
问题起点:从发现 rebuild 没有命中 cache 开始
这篇文章真正的起点,并不是“我想评测 Determinate Nix”,而是一次很具体的问题排查。
当时我是为了让 Darwin 侧的 codex 复用 llm-agents 的包源,并顺手把对应的 cache 配上。按直觉来说,这类问题通常不会太复杂:补上 substituters 和 trusted-public-keys,然后重新 darwin-rebuild,理论上就应该优先命中二进制缓存,而不是继续在本地从头 build。
但实际情况并不是这样。
第一层现象:明明已经加了 cache,还是在本地 build
最开始看到的症状非常像一个普通的 cache 配置问题:
rebuild过程中没有稳定命中预期缓存;- 日志里不断出现
untrusted一类的提示; - 结果上看,系统依然在本地继续 build,而不是直接拉取现成闭包。
warning: ignoring untrusted substituter 'https://nix-community.cachix.org', you are not a trusted user.
Run `man nix.conf` for more information on the `substituters` configuration option.
如果只看表面,这很容易被理解成两个方向之一:
- cache 地址写错了;
- key 不匹配,导致 substituter 不被信任。
这当然不是完全错误,但它还不够接近根因。
第二层排查:问题不只是 cache,而是配置到底有没有被 daemon 读到
继续往下看之后,问题开始从“cache 配置本身”转向“配置是通过哪条链路生效的”。
这里也是我后来觉得最值得写下来的地方:在 Darwin + Determinate Nix 这套组合里,你并不能简单沿用在 NixOS 上那种“把配置写进 nix.settings,然后假设 daemon 一定会按你想的方式读取”的心智模型。
我当时真正踩中的,不是 cache 地址本身,而是配置生效路径:
- 有些配置写进 flake 的
nixConfig,更像补充; - 有些配置写进
nix-darwin的nix.settings,在官方 Nix 路径下是成立的; - 但在
Determinate Nix + nix-darwin的组合下,真正决定 daemon 行为的入口并不在这里。
这也解释了为什么会出现一种很反直觉的体验:你明明“已经配置了”,但系统表现得像“根本没配置”。
根因:Darwin 上真正接管 Nix runtime 的不是 nix-darwin
把问题追到最后,结论其实很简单:在我当前这套 Darwin 环境里,真正接管 Nix runtime 的并不是 nix-darwin,而是 Determinate 自己的运行时管理路径。
Determinate 官方文档对这一点说得很明确:如果要和 nix-darwin 一起使用,就应该让 Determinate 来负责 Nix 自身的配置,而不是继续让 nix-darwin 管理 Nix 本体;在这种模式下,自定义配置的受支持入口是 nix.custom.conf,而不是直接期待 nix-darwin 接管所有 Nix 运行时细节。
也就是说,这次排查最后把我带到了一个比 cache 更基础的问题上:
在 Darwin 上,我到底是在用
nix-darwin管 Nix,还是在用 Determinate 管 Nix?
只要这个边界没想清楚,后面很多现象都会显得很怪:为什么某些配置不生效,为什么 flake.nix 里的 nixConfig 不够,为什么最后还是得回到 nix.custom.conf 或 determinateNix.customSettings 这种入口。
从这个角度看,这篇文章的主题其实也就自然成型了:我原本只是在排查一次 cache miss,最后却被迫重新回答一个更大的问题,Determinate Nix 到底是在替我解决什么问题。
Determinate 到底是什么
如果只看名字,很多人第一次接触时很容易把 Determinate Nix 理解成“另一个 Nix 安装脚本”或者“给 Nix 换了一层包装”。但如果顺着官方文档和实际使用场景往下看,它明显不只是这个定位。
它不是单一工具,而是一套围绕 Nix runtime 的产品组合
Determinate 首先是 Determinate Systems 这家公司,而不是一个单独的二进制程序。围绕 Nix,他们现在至少做了几件相互关联的事情:
nix-installer:一个跨平台的安装器,负责把 Nix 以更一致的方式落到 macOS、Linux、WSL 等环境里;Determinate Nix:基于 Nix 的下游发行与运行时增强;FlakeHub:围绕 flakes 的发布、发现和分发平台;- 以及进一步偏组织治理、供应链和企业场景的配套能力。
所以如果只把它理解为“替代 curl | sh 的安装脚本”,其实会低估它真正想做的事情。
它的核心卖点不是配置表达,而是 runtime 托管
我现在越来越倾向于用一句话概括它的核心价值:
Determinate 的优势主要不是“把配置写得更优雅”,而是“把 Nix runtime 托管得更完整”。
这里的 runtime,指的是这些更靠近系统运行层面的事情:
- Nix 在 Darwin 上怎么安装;
/nix这套存储是怎么落地和维护的;- daemon 由谁管理;
- cache 信任、证书、升级、卸载、恢复这些动作由谁来负责;
- 当你不再只把 Nix 当成“一个包管理器”,而是当成一套长期运行的基础设施时,谁来兜住这些边边角角。
这也是为什么我会觉得它和 nix-darwin 很容易被混淆。
nix-darwin 解决的是“如何在 macOS 上声明式管理系统配置”;而 Determinate 更关心的是“如何把 Nix 这套运行时本身稳稳地装上去、跑起来、管起来”。两者当然会发生交集,但它们不是在同一个层面上提供价值。
对我这次排查最关键的启发
一旦接受“Determinate 主要在管 runtime,而不是替代 nix-darwin 的声明式能力”,很多之前看起来反直觉的事情就顺了:
- 为什么 Darwin 上的配置入口会和 NixOS 不完全一样;
- 为什么
nix.settings在某些路径下并不是最终生效点; - 为什么 Determinate 的“好用”更多体现在安装、升级、回收、卸载、系统集成这些地方,而不是模块写法本身。
换句话说,它真正擅长的不是让 Darwin/NixOS 配置完全同构,而是把 Darwin 上的 Nix runtime 变成一套更可控的系统部件。
Darwin 上安装 Nix,到底在选什么
如果把问题只写成“Darwin 上该用哪个安装方案”,其实会有点失焦。因为这里真正的差异,并不只是安装命令长什么样,而是谁来接管 Nix runtime,以及你愿不愿意接受由此带来的心智差异。
官方方案:官方安装脚本 + nix-darwin
截至目前,Nix 官方在 macOS 上推荐的入口依然是 nixos.org/nix/install 这条安装脚本。官方下载安装页给 macOS 的命令仍然是通过这条脚本完成多用户安装,而不是别的发行版或分支。
这条路线的优点很明确:
- 更贴近 upstream;
- 心智模型更统一;
- 如果你本来就想让
nix-darwin尽量直接管理 Nix 本体,那么这条路线通常最自然。
但它的代价也很真实,尤其在 Darwin 上:你需要自己更清楚地知道 Nix runtime 是怎么工作的,出了问题时排查、回收、恢复、卸载这些动作,也更依赖你自己的理解和经验。
我这次没有直接回到官方方案,很大一部分原因,其实就是被之前那次 GC / prune generations 的事故留下了心理阴影。虽然我现在已经记不清当时的完整细节,但可以 reasonably 推测,问题大概率不是“官方 Nix 会无缘无故把自己删掉”,而是当时 Darwin 侧某些关键闭包没有以我以为的方式被稳定引用住,结果在清理 generations 和 GC 之后,把整套Nix一起带崩了。
这也是为什么我后来会明显更关注“谁来托管 runtime”这个问题,而不只是“配置写法够不够优雅”。
Determinate 方案:让 nix-darwin 管系统,让 Determinate 管 Nix 本体
Determinate 和 nix-darwin 的组合,本质上是一种明确分工:
nix-darwin继续负责 Darwin 侧的声明式系统配置;- Determinate 负责 Nix 自身的安装、daemon、配置入口、以及部分运行时行为。
这个方案的直接好处,是 Darwin 上很多 historically 比较容易让人心里没底的 runtime 问题,会被它打包成一套更“工程化”的路径。例如安装器、卸载、/nix 的落地方式、daemon 行为、配置入口等,都有更强的统一性。
但代价同样很清楚:
- 你要接受它不是纯 upstream 的 Nix;
- 你要接受 Darwin 侧的配置入口会与 NixOS 存在分叉;
- 你还要接受社区对它一直存在一些两极化评价,尤其涉及下游分叉、FlakeHub 绑定、以及和上游治理关系的问题。
所以它不是“全面更好”,而是“把优先级换了一下”。
Lix 在这里是什么位置
Lix 也值得单独提一下,因为它很容易在搜索时和“官方 Darwin 安装方案”混在一起。
但至少到现在,Lix 并不是 Nix 官方在 macOS 上的新默认安装路径。它更像是另一个独立项目:有自己的发行方向、自己的安装器、自己的兼容承诺。你可以把它看成“Nix 生态里另一条明确存在的分支路线”,但不应该把它误认为“官方已经迁过去了”。
所以如果把 Darwin 上的现实选择简单粗暴地概括一下,大致是:
- 想尽量贴 upstream 心智,走官方安装脚本;
- 想把 Darwin 上的 Nix runtime 托管得更完整,走 Determinate;
- 想进一步尝试另一条分支路线,可以关注 Lix,但它不是这篇文章要重点展开的主角。
综合比较
如果一定要把这三条路线放在一起看,我更愿意按下面几个维度来比较:
- 是否贴近 upstream;
- 是否能和
nix-darwin自然配合; - 是否明显偏向 runtime 托管;
- 是否更容易维持统一的配置心智;
- 安装/卸载链路是否足够工程化;
- 是否需要接受额外的下游分叉或生态绑定。
之所以选这几项,是因为它们比“好不好用”更接近真正的技术分歧。Darwin 上几种方案的差异,核心并不在于能不能安装成功,而在于你到底更看重 upstream 一致性、声明式配置统一性,还是 runtime 的托管能力。
- name: "官方 Nix + nix-darwin"
贴近_upstream: "✅"
可与_nix_darwin_直接配合: "✅"
偏向_runtime_托管: "❌"
配置心智更统一: "✅"
安装卸载更工程化: "❌"
需要接受下游分叉或生态绑定: "❌"
判断说明: "最接近上游,也最符合把 Nix 本体继续交给 nix-darwin 管理的直觉;代价是 runtime 层面的安装、恢复、卸载、排障更依赖使用者自己理解。"
- name: "Determinate Nix + nix-darwin"
贴近_upstream: "❌"
可与_nix_darwin_直接配合: "✅"
偏向_runtime_托管: "✅"
配置心智更统一: "❌"
安装卸载更工程化: "✅"
需要接受下游分叉或生态绑定: "✅"
判断说明: "核心优势在于把 Nix runtime 的安装、daemon、配置入口、卸载等问题托管起来;代价则是需要接受 Darwin 侧与 upstream 不完全一致的配置路径,以及额外的产品边界。"
- name: "Lix + nix-darwin"
贴近_upstream: "❌"
可与_nix_darwin_直接配合: "✅"
偏向_runtime_托管: "❌"
配置心智更统一: "❌"
安装卸载更工程化: "✅"
需要接受下游分叉或生态绑定: "✅"
判断说明: "它不是 Nix 官方路径,也不是 Determinate 那种以 runtime 托管为核心卖点的产品组合,而是另一条独立实现路线;可以与 nix-darwin 协作,但同样意味着要接受分支实现本身带来的额外判断成本。"
我的决策
写到这里,我自己的结论其实已经比较明确了。
我现在并不觉得 Determinate 是一个“放之四海而皆准”的更优方案。相反,我觉得它最大的特点恰恰是价值边界非常清楚:它更强的是 runtime 托管,而不是配置统一性。
这意味着,如果你的首要目标是:
- Darwin 和 NixOS 尽量共用一套配置心智;
- 尽量减少平台分叉;
- 尽量贴近 upstream;
那么官方方案通常更顺。
但如果你的首要目标变成:
- 我主要在 macOS 上长期使用 Nix;
- 我更在意安装、升级、daemon、卸载、恢复这些 runtime 层面的稳定感;
- 我愿意接受一定程度的下游分叉,换取更完整的托管体验;
那 Determinate 确实会变得更有吸引力。
对我自己来说,这次的实际选择依然是继续站在 Determinate 这边,至少在当前阶段如此。原因并不复杂:我现在对 Darwin 上 Nix runtime 的“可恢复性”和“可托管性”比对“配置完全同构”更敏感。前一次事故留下的阴影还在,而这次 cache miss 排查又恰好提醒了我,Determinate 真正解决的,正是这类更靠近运行时边界的问题。
但这篇文章之所以仍然只能叫 evaluation,而不是 review,也正是因为我并没有因此得出一个彻底封闭的结论。
一方面,我承认自己已经明显感受到 Determinate 的取向和价值;另一方面,我也同样感受到它带来的配置分叉、心智差异,以及和 upstream 并不完全一致的那部分代价。是否会在未来某个时间点重新回到官方 Nix + nix-darwin,我现在并不敢说不会。
如果一定要把这篇文章压成最后一句话,那大概就是:
我最开始以为自己只是在排查一次 Darwin 上的 cache miss,最后却发现,我真正需要重新判断的,是在 macOS 上到底该把 Nix 当成一个自己完全掌控的工具,还是一套更值得被托管的 runtime。