Chapter 01

Nix 哲学与核心概念

理解"依赖地狱"的根源,以及 Nix 如何用纯函数式思路彻底解决这一问题

传统包管理的痛点

依赖地狱:全局状态的代价

传统包管理器(apt、brew、pip、npm 全局安装)都有一个共同的设计缺陷:它们将包安装到全局共享位置(/usr/lib、/usr/local/lib)。这造成了以下典型问题:

版本冲突
项目 A 需要 Python 3.9,项目 B 需要 Python 3.11。系统只能安装一个版本,两个项目无法共存。虚拟环境(venv)只是治标,底层共享库仍存在冲突。
隐式依赖
开发者在自己机器上安装了额外的库,程序正常运行,但 requirements.txt 里没写。部署到服务器后缺少依赖而崩溃——"在我机器上能跑"的经典场景。
升级破坏性
升级 libssl 解决了安全漏洞,却破坏了依赖旧版 API 的应用。apt upgrade 这一操作可能让整个系统上的多个服务同时失效,且难以回滚。
不可复现
同一个 Dockerfile 在不同时间构建,因基础镜像、apt 仓库的包版本不同,产生不同的结果。CI/CD 构建结果不稳定,调试困难。

根本原因:可变的全局状态

这些问题的根源是包管理器将软件安装到可变的全局状态中。就像全局变量让程序难以推理一样,全局共享的包安装路径让系统状态难以预测。

传统包管理(可变状态) ──────────────────────────────────────────── /usr/lib/ libssl.so.1.1 ← 全局唯一,升级即覆盖 libpython3.9/ libpython3.11/ ← 多版本共存困难 /usr/bin/ python3 ← 指向哪个版本? ──────────────────────────────────────────── 问题:所有程序共享同一个命名空间 升级一个包可能破坏多个程序

Nix 的解决方案

纯函数式包管理

Nix 将函数式编程的核心思想应用于包管理:相同的输入永远产生相同的输出。每个包的构建过程被视为一个纯函数:

Nix /nix/store(不可变内容寻址存储) ──────────────────────────────────────────────────────── /nix/store/ ├── abc123xyz-openssl-3.0.8/ ← OpenSSL 3.0.8 │ └── lib/libssl.so.3 ├── def456uvw-openssl-1.1.1t/ ← 旧版 1.1.1t 同时存在! │ └── lib/libssl.so.1.1 ├── ghi789rst-nodejs-20.11.0/ │ └── bin/node ← 依赖上面的 openssl 3.0.8 └── jkl012mno-python3-3.11.6/ └── bin/python3 ← 有自己独立的依赖链 ──────────────────────────────────────────────────────── 关键:每个包路径唯一,不同版本完全隔离,互不影响

核心概念详解

Derivation(推导)
Nix 中的"构建计划"——描述如何构建一个包的完整规格说明,包含:源码 URL 和哈希、构建依赖、编译器、构建脚本、环境变量。可类比为函数定义,执行 derivation 即"调用函数"得到构建产物。Nix 语言中用 derivation{} 或 mkDerivation{} 创建。
Store Path(存储路径)
形如 /nix/store/<hash>-<name>-<version> 的路径,是 derivation 的构建结果。哈希值(160 位 SHA256 缩写为 32 字符 base32)由所有构建输入决定,任何输入变化都会产生不同哈希和不同路径。Store 中的内容是只读的,永不修改。
Closure(闭包)
一个包及其所有传递依赖的完整集合。例如,nodejs 的 closure 包含 nodejs 本身、openssl、libc、zlib 等所有它直接或间接依赖的包。Closure 是可复现的最小单元——将一个包的 closure 从机器 A 复制到机器 B,不需要任何其他依赖即可运行。
Profile(配置集)
用户"安装"的包的集合,本质是指向 /nix/store 中各包的符号链接树,位于 ~/.nix-profile/。切换 profile 只是更改符号链接,原子操作,可以瞬间回滚。不同用户可以有完全不同的 profile,互不影响。
Generation(代)
Profile 的历史快照。每次安装/卸载包都会创建新的 generation,旧的 generation 保留,可以随时回滚。类似 Git 的 commit 历史,让系统状态完全可追溯和可回退。

Nix 生态全景

Nix 不是单一工具,而是一个完整的生态系统:

Nix(包管理器)
核心工具:评估 Nix 表达式、构建 derivation、管理 /nix/store。可以安装在 Linux 和 macOS 上,不需要切换操作系统。
Nixpkgs
全球最大的开源包仓库之一,超过 80,000 个包,托管在 GitHub(nixos/nixpkgs)。每个包都有 Nix 表达式描述其构建方式,社区持续维护更新。稳定版(nixos-24.05)和滚动更新版(nixpkgs-unstable)并存。
NixOS
基于 Nix 的完整 Linux 发行版。操作系统整个状态(已安装包、服务配置、用户账户、内核参数)都声明在 /etc/nixos/configuration.nix 中。nixos-rebuild switch 原子化地将系统切换到新状态,失败可立即回滚。
Home Manager
用 Nix 管理用户级配置(dotfiles)。声明式配置 Git、Shell、编辑器、终端等,跨机器同步用户环境。可作为 NixOS 模块集成,也可独立运行在任何有 Nix 的系统上。
Flakes(实验特性)
现代化的 Nix 项目管理系统。用 flake.nix 声明项目依赖(来自 GitHub、nixpkgs 等),flake.lock 锁定所有依赖的精确 Git commit,确保跨机器跨时间的完全一致性。类似 Cargo.toml + Cargo.lock 的组合。

安装 Nix

推荐:Determinate Nix Installer

官方推荐的安装方式是 Determinate Nix Installer,由 Determinate Systems 维护,支持 macOS 和 Linux,默认启用 Flakes,自动配置多用户安装模式:

# 一键安装(macOS 和 Linux 通用)
curl --proto '=https' --tlsv1.2 -sSf -L \
  https://install.determinate.systems/nix | \
  sh -s -- install

# 安装后,重新打开终端或执行:
source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh

# 验证安装
nix --version
# 输出示例:nix (Nix) 2.24.9

# 测试:运行 hello 包(不会安装到系统)
nix run nixpkgs#hello
# 输出:Hello, world!

macOS 特殊说明

在 macOS 上,Nix 会创建一个单独的 APFS 卷(/nix)来存放 store,因为 macOS 根分区是只读的。安装过程需要 sudo 权限,会自动配置开机挂载。

# macOS:验证 /nix 挂载点
df -h /nix
# 输出示例:
# /dev/disk3s1  ...  /nix

# 查看 Nix store 占用空间
du -sh /nix/store
# 输出示例:2.3G  /nix/store

# 查看 store 中的包数量
ls /nix/store | wc -l

官方安装方式(备选)

# 官方多用户安装(Linux,推荐用于服务器)
sh <(curl -L https://nixos.org/nix/install) --daemon

# 官方单用户安装(不推荐,权限问题较多)
sh <(curl -L https://nixos.org/nix/install) --no-daemon
启用 Flakes(如果使用官方安装器)

官方安装器默认不启用 Flakes,需要手动配置。编辑 ~/.config/nix/nix.conf(或 /etc/nix/nix.conf)添加:

experimental-features = nix-command flakes

Determinate Nix Installer 默认已启用,无需此步骤。

核心术语速查

幂等性(Idempotency)
执行一次和执行多次产生相同结果。Nix 的 nixos-rebuild switch 是幂等的——无论执行多少次,系统最终都处于 configuration.nix 所描述的状态。传统 apt install 不是幂等的:重复执行可能因包版本变化产生不同结果。
可复现性(Reproducibility)
相同的输入(代码、依赖版本、构建环境)在任何机器上、任何时间产生完全相同的输出。Nix 通过内容寻址存储和精确版本锁定(flake.lock)实现完全可复现。对比:Docker 镜像构建虽有一定可复现性,但 apt install 步骤仍受网络和时间影响。
引用透明性(Referential Transparency)
函数式编程术语。一个表达式可以被其值替换而不改变程序行为。Nix 语言是引用透明的:相同的 Nix 表达式永远求值为相同的 derivation,不受外部状态影响(无副作用、无全局变量)。
惰性求值(Lazy Evaluation)
表达式只在需要时才被求值。Nix 语言是惰性的:定义了大量包的 Nixpkgs 在你只需要其中一个包时,不会评估所有其他包的 derivation,大幅节省时间。
Sandbox(沙盒构建)
Nix 构建过程在隔离的沙盒环境中进行:无网络访问(防止构建时拉取额外资源)、只能访问声明的依赖、环境变量被清除(只保留 PATH 等基础变量)。确保构建结果只取决于声明的输入,实现真正的可复现。
本章小结

Nix 的核心洞察:传统包管理器的一切问题来自于可变的全局状态。Nix 用内容寻址的不可变存储(/nix/store)、纯函数式构建(derivation)、完整依赖闭包(closure)解决了这些问题。理解这个哲学是学好 Nix 的关键——后续所有内容都建立在这个基础上。