NixOS 的工作原理#
NixOS 基于 Nix ,一个纯函数式软件包管理系统。Nix 将所有软件包彼此隔离地存储在 /nix/store 下。
/nix/store/5rnfzla9kcx4mj5zdc7nlnv8na1najvg-firefox-3.5.4/示例路径的哈希部分标识 store 对象;常见 derivation 的输出路径由构建描述等输入决定,不能直接当作输出文件内容哈希。软件包构建完成后不会被覆盖;相反,如果您更改软件包的构建描述(即其“Nix 表达式”),它会被重新构建并安装到 /nix/store 中的不同路径,以避免与旧版本冲突。NixOS 对此进行了扩展,不仅使用 Nix 构建软件包,还使用 Nix 构建配置文件等。例如,SSH 守护进程的配置也是根据 Nix 表达式构建的,并存储在类似这样的路径下。
/nix/store/s2sjbl85xnrc18rl4fhn56irkxqxyk4p-sshd_configNixOS 通过从 Nix 表达式构建整个系统配置,确保这些配置不会相互覆盖,可以回滚等等。
Nix/NixOS 存储软件包的方式带来的一个重要影响是,它不把软件按传统发行版方式统一安装到 /bin 、 /sbin 、 /lib 、 /usr 等目录。所有软件包都保存在 /nix/store 目录中。(保留 /bin/sh 等兼容入口;具体布局随系统模块而异。)正是由于不使用 /bin 等“全局”目录,才使得软件包的多个版本可以共存。Nix 确实有一个 /etc 用于存放系统级配置文件,但该目录中的大多数文件都是指向 /nix/store 中生成文件的符号链接。
不采用传统 FHS 软件布局,会让依赖固定 /usr/lib 或动态加载器路径的第三方预编译程序需要包装、修补或兼容环境;不是 Nix 管理的依赖本身普遍失效。
声明式系统配置模型#
在 NixOS 中,整个操作系统——包括内核、应用程序、系统软件包、配置文件等等——都是由 Nix 包管理器根据纯函数式构建语言的描述构建的。纯函数式的特性意味着构建新的配置不会覆盖之前的配置。其他大部分特性都源于此。
您可以通过在 /etc/nixos/configuration.nix 文件中编写规范来配置 NixOS 系统,该规范定义了您希望在机器上实现的功能。例如,以下是 BIOS/GRUB 系统的历史配置片段,实际安装还须生成完整硬件配置并确认磁盘目标;不可直接用于新机器:
{
boot.loader.grub.device = "/dev/sda";
fileSystems."/".device = "/dev/sda1";
services.openssh.enable = true;
}修改 /etc/nixos/configuration.nix 文件后,运行以下命令即可生效:
nixos-rebuild switch该命令执行配置所需的一切操作,包括下载和编译 OpenSSH、生成 SSH 服务器的配置文件等等。
可靠的升级#
固定构建输入能减少原系统安装历史对构建结果的影响;升级仍需检查配置兼容、激活脚本和可写数据迁移。
原子升级#
NixOS 采用_事务性_配置管理方式:配置更改(例如升级)是_原子性的_ 。这意味着,新旧 store 产物分离、profile 原子切换减少了升级中途覆盖文件的风险;启动加载器写入、服务激活和可写数据仍有独立故障边界。
回滚#
由于新配置的文件不会覆盖旧文件,因此您可以(原子地)回滚到之前的配置。例如,如果在 nixos-rebuild switch 后,您发现不喜欢新配置,您可以直接回滚:
nixos-rebuild switch --rollback事实上,尚未删除且在启动菜单保留范围内的旧配置可显示在启动菜单中。因此,如果新配置崩溃或无法正常启动,您只需在启动菜单中选择旧配置即可回滚。回滚过程非常快捷:无需从备份中恢复大量文件。
可复现的系统配置#
NixOS 的声明式配置模型使得在另一台机器上重现系统配置变得轻而易举(例如,在生产服务器上进行更改之前,先在测试环境中进行测试)。固定依赖并为目标机器配置硬件、磁盘和引导后,可将声明配置带到目标 NixOS 机器,然后运行 nixos-rebuild switch 即可。这将使您获得相同的配置(内核、应用程序、系统服务等),但可变状态(例如 /var 目录中的内容)除外。
可以安全地测试更改#
回滚可以帮助恢复声明环境,但不能撤销任意服务副作用或数据迁移。例如,无论更改是像启用系统服务这样简单,还是像使用新版本的 Glibc 重建整个系统这样复杂,您都可以通过以下方式进行测试:
nixos-rebuild test此操作会构建并激活新配置,但不会将其设置为默认启动配置。因此,重启系统后,系统将采用已有启动默认配置,前提是该配置仍可用。
对适合虚拟机验证的模块,可采用以下方案:
nixos-rebuild build-vm
./result/bin/run-*-vm此命令会构建并启动一个包含新系统配置的虚拟机(即主机配置的克隆,并包含您对 configuration.nix 文件所做的所有更改)。该机制可以共享宿主机的 Nix store,VM 的可写根文件系统与主机系统激活分开;也可能通过配置额外挂载目录,不能把 VM 视为绝对无数据共享。按当前构建产物脚本和 virtualisation 选项核实磁盘、网络及共享目录。
系统回滚≠配置回滚#
Generation 回滚 和 配置文件本身 是两回事。
回滚的是: Nix store 里构建好的系统(二进制结果)
不回滚的是:/etc/nixos/configuration.nix 这个文本文件具体发生了什么
你的操作流程:
configuration.nix (v2)
↓ nixos-rebuild switch
Generation 42 ← 当前启动
Generation 41 ← 上一个
nixos-rebuild switch --rollback
↓
Generation 41 ← 当前启动
Generation 42 ← 还在 nix store 里
但是 /etc/nixos/configuration.nix 还是 v2 的内容!也就是说,你回滚之后如果再 nixos-rebuild switch,会重新部署 v2,又回到出问题的状态。
所以正确的工作流是配置文件用 Git 管理,这样两个维度都有历史记录:
cd /etc/nixos
git init
git add .
git commit -m "working config baseline"
# 改了配置
git add configuration.nix
git commit -m "try enabling some service"
nixos-rebuild switch
# 出问题了
nixos-rebuild switch --rollback # 系统恢复
git revert HEAD # 配置文件也恢复Generation 负责系统状态的回滚,Git 负责配置意图的回滚,两者配合才完整。
build-vm 更干净,VM 里的系统跟宿主机的 generation 完全隔离,宿主机的 generation 列表根本不会变,所以这个问题在 build-vm 场景下不存在。适合 VM 的服务和模块可以先这样验证;真实硬件、图形及驱动效果仍需要对应环境验证。
Arch (命令式) vs NixOS (声明式)#
"作为 Arch 用户,你习惯了‘改变系统状态’。在 NixOS,你优先通过声明‘描述系统状态’,可写运行时状态仍存在。"
核心差异对照表#
| 维度 | Arch Linux (命令式) | NixOS (声明式) |
|---|---|---|
| 安装软件 | sudo pacman -S git |
在 environment.systemPackages 中添加 git |
| 文件修改 | 手动修改 /etc/fstab |
在 fileSystems 中定义挂载点 |
| 系统状态 | 当前状态是过去所有命令的累加 | 当前状态是配置文件的计算结果 |
| 回滚 | 依赖包历史、备份或文件系统快照等额外机制 | 保留系统代际时可切换旧闭包;数据另行恢复 |
| 副作用 | 修改文件是即时的、有副作用的 | 修改配置不会生效,除非运行 nixos-rebuild |
NixOS 的“纯净”本质#
1. /nix/store:只读的世外桃源
在 Arch 中,/bin/git 就是那个二进制文件。在 NixOS,系统环境中的 Git 通常通过 /run/current-system/sw/bin/git 等 profile 链接访问,它指向一个类似这样的路径:
/nix/store/h3l9...-git-2.43.0/bin/git
- 哈希前缀:代表了构建该软件的所有依赖和配置。如果依赖变了,哈希就变了。
- 不可变性:你不能修改
/nix/store里的任何东西。这减少了覆盖包文件的风险,但不能避免激活错误、数据损坏或管理员强制改动。
2. /etc 的真相
NixOS 中的 /etc 绝大部分文件都是只读软链接。
- 命令式思维:想改 SSH 配置?打开
/etc/ssh/sshd_config修改。 - 声明式思维:在
configuration.nix中写services.openssh.settings.PermitRootLogin = "no";,Nix 会自动帮你生成软链接到正确的配置文件。
实战练习:从 Arch 逻辑转换#
Arch 命令:
sudo pacman -S hello
sudo systemctl enable --now dockerNixOS 声明:
在 configuration.nix 中:
{ pkgs, ... }: {
environment.systemPackages = [ pkgs.hello ];
virtualisation.docker.enable = true;
}运行生效:sudo nixos-rebuild switch
给 Arch 用户的建议#
优先修改负责生成该文件的 Nix 模块,避免编辑生成文件导致配置与意图脱节。/etc/nixos 的源文件和其他可写、未托管文件另当别论。
关于Nix这个工具本身#
nix-env 的设计背景#
nix-env 是 Nix 最早期的用户界面,2003年左右随 Nix 本身一起诞生。
设计目的#
Nix 最初不是为 NixOS 设计的,而是作为一个可以装在任何 Linux/macOS 上的包管理器。
设想的场景是:
你用的是 Ubuntu / macOS
但你想要:
- 多版本共存(python2 和 python3 同时装)
- 不需要 root 权限装包
- 可以回滚
- 不污染系统的 /usr/libnix-env 就是为这个场景设计的,对标的是 apt install / brew install,给 Nix 用户提供 profile 管理,NixOS 上也能使用。
nix-env -iA nixpkgs.firefox # 传统 channel/profile 的用户安装示例
nix-env -e firefox # 从该用户 profile 移除
nix-env --rollback # 回滚该用户 profile它解决了什么真实问题#
多版本共存:
# 同时装两个版本,互不干扰(不同版本的 store 路径可共存;同一 profile 的同名程序还可能冲突)
nix-env -iA nixpkgs.python2 # 历史版本示例,不保证当前 nixpkgs 提供
nix-env -iA nixpkgs.python3无 root 安装:
# 普通用户在服务器上装自己的工具,不需要找管理员
nix-env -iA nixpkgs.ripgrepNix 的早期设计已经围绕隔离包路径和 profile 回滚展开。
声明式管理与命令式入口的权衡#
时代变了,更好的替代品出现了:
nix-env 的问题 |
替代方案 |
|---|---|
| 命令式,状态不在文件里 | home.nix 声明式管理 |
| 多版本 nixpkgs 混用,难以追踪 | Flakes 锁版本 |
| 临时试用不必修改长期 profile | nix-shell -p 或 nix shell 不改长期 profile;产物仍可能留在 store |
nix-env 面向传统用户 profile,nix profile 面向新 CLI 的 profile;两种格式不能任意混用。在 Home Manager 已管理稳定软件集合时,可以减少额外命令式安装入口。
管理入口的选择#
nix-env是"在非 NixOS 系统上用 Nix 管包"的早期解法,稳定用户环境可由 Home Manager 管理,传统 profile 和 channels 工作流仍有用途。
前半部分依据 NixOS 官方工作原理说明 整理,历史示例仅用于解释机制。
参考与延伸
根据技术笔记重新整理,示例使用通用名称。