[{"content":"若说到当代编程领域的神教，我会想起这3样：Rust、Arch Linux以及Nix。这3个神教都得有一些魔力，吸引信徒虔诚追随。这篇文章主要介绍Nix能做到的事情，用于像初学者传教。\n安装 你可以按照Download | Nix \u0026amp; NixOS中描述的安装Nix，推荐使用multi-user installation。安装后，我们通常会编辑/etc/nix/nix.conf开启下面两个实验特性。\n1experimental-features = nix-command flakes这些特性虽然被标记为“实验”，Nix开发团队并不保证CLI的稳定性。但确实很好用，社区内也已经基于此构造了庞大的生态。后文都会假设这两个选项已经开启。\n接下来我们会介绍一下Nix能做的事。\n包管理器 Nix是一个包管理器。你可以像apt、yum、pacman那样命令式地安装软件。\n1$ nix profile add nixpkgs#kubectl 这个命令无需用root身份运行，只为当前用户安装软件。当然要真正使用Nix提供的软件，你需要配置一下shell，把~/.nix-profile/bin添加到PATH路径即可。\n1export PATH=~/.nix-profile/bin:$PATH不同于其他包管理器，Nix支持原子回滚。我们会在后文介绍一下Nix是如何实现这个魔法的。\n1$ nix profile rollback 它还支持你ad-hoc运行一个软件，类似于npx，go run那样。你可以配置上Nix的GC daemon。这样这些ad-hoc下载的软件过一阵子会被垃圾回收。\n1$ nix run nixpkgs#bun 你也可以同时运行同一个软件的不同版本。\n1$ nix run nixpkgs#python313 -- --version 2Python 3.13.12 3$ nix run nixpkgs#python314 -- --version 4Python 3.14.3 甚至你可以直接运行GitHub上发布的一个软件。当然如果不添加上游二进制源时，Nix会按照仓库中的Nix代码构建整个应用。\n1$ nix run github:nix-community/nixos-anywhere -- --help 自然而然地，你就可以把Nix作为shebang，进而在自己的脚本里声明所有依赖。只要用户装了Nix，就不用担心脚本中的依赖不存在了。这使得我们可以放心使用bleeding edge的软件。如果还担心软件本身不稳定，你可以修改nix-shell参数，固定nixpkgs版本。这样就相当于固定了所有软件版本，能拥有极致的可复现性。\nshowip.nu 1#!/usr/bin/env nix-shell 2#!nix-shell -i nu -p nushell 3 4let resp = http get \u0026#34;https://api.ipify.org/?format=json\u0026#34; 5print $\u0026#34;Your IP is ($resp.ip)\u0026#34; 这里提到的nixpkgs是刚才所有命令隐式依赖的一个GitHub仓库。它是个巨大的monorepo，使用Nix语言描述了刚才所有命令中软件的构建方式。下图是Repology整理的常见仓库包数量和新旧程度。可以从图中看到nixpkgs在包数量上一骑绝尘。这得益于Nix提供的可复现的构建、完善的CI/CD以及开放的社区协作。\n构建系统 nixpkgs并没有什么魔法，你也可以使用Nix打包软件。通常大家会使用nixpkgs提供的各种设施。下面的代码就能打包bbolt，一个用来查看etcd数据库的工具。\nbbolt.nix 1{ 2 pkgs ? import \u0026lt;nixpkgs\u0026gt; { }, 3}: 4let 5 inherit (pkgs) 6 fetchFromGitHub 7 buildGoModule 8 ; 9in 10buildGoModule (final: { 11 pname = \u0026#34;bbolt\u0026#34;; 12 version = \u0026#34;1.4.3\u0026#34;; 13 src = fetchFromGitHub { 14 owner = \u0026#34;etcd-io\u0026#34;; 15 repo = \u0026#34;bbolt\u0026#34;; 16 rev = \u0026#34;v${final.version}\u0026#34;; 17 hash = \u0026#34;sha256-awBkr2ObRxPQkMlfVFZxEbQ9JQJsFrJvSBHtqP4Hb3I=\u0026#34;; 18 }; 19 vendorHash = \u0026#34;sha256-TzVmAMrNrNkFE9jQ+SILJXvbhBK1WenNPqA0FfuDU+M=\u0026#34;; 20 subPackages = [ \u0026#34;cmd/bbolt\u0026#34; ]; 21}) 1$ nix build --file ./bbolt.nix 2$ ./result/bin/bbolt --version 3bbolt Version: 1.4.3 4Go Version: go1.25.9 5Go OS/Arch: linux/amd64 Nix是个惰性求值的语言，只有代码中用到的包才会参与求值和构建。而构建时的依赖正是代码中用到的那些包。上面示例中的构建依赖可简化成下图。\nflowchart LR src[\u0026#34;`source *fixed-output (hash)*`\u0026#34;] go-modules[\u0026#34;`go-modules *fixed-output (vendorHash)*`\u0026#34;] src --\u0026gt; join1(( )) go --\u0026gt; join1 join1 -- go mod download --\u0026gt; go-modules src --\u0026gt; join2(( )) go-modules --\u0026gt; join2 go --\u0026gt; join2 join2 -- go build --\u0026gt; bbolt bbolt包的构建时依赖图 Nix在求值时会为每个包计算一个hash。构建后的包就位于/nix/store/\u0026lt;hash\u0026gt;-\u0026lt;name\u0026gt;。nix build命令还会在当前目录下创建一个符号链接并加入GC root，以避免被GC。\n1$ ls -l result 2lrwxrwxrwx 1 sun sun 55 Jun 3 01:04 result -\u0026gt; /nix/store/b1vbalgjm2andc0624rv036kc1m2lb9d-bbolt-1.4.3 3$ ls -l /nix/var/nix/gcroots/auto | grep \u0026#34;$PWD/result\u0026#34; 4lrwxrwxrwx 1 root root 16 Jun 3 23:46 2lax87vim8cgib3f7ksblr98srj2mljj -\u0026gt; /home/sun/result 根据hash的计算方式，Nix的包可分为两类：fixed-output和input-addressed。1\n类型 Hash计算方式 构建环境 Fixed-Output 代码中指定，并在构建后校验 impure，可以访问网络、环境变量 Input-Addressed 根据输入计算 pure，不可访问网络、环境变量 这个设计极为巧妙。首先，它确保了可复现性。对于有外部依赖的包，其可复现性由代码中写死的hash保证； 而对于没外部依赖的包，其构建结果由其输入决定。其次，这个设计让缓存和二进制分发更加简单。根据hash即可判断是否命中缓存。之前的例子中，go这个包的构建代码位于nixpkgs里。在求值阶段，Nix会计算go包的hash。在构建阶段，当Nix发现Binary Cache存在这个包时，就可以避免编译，直接下载。而再次运行nix build时，Nix会发现本地已经有bbolt包，就直接跳过了构建。\n对比之下，Docker没有区分Fixed-Output和Input-Addressed，因此在可复现性和缓存命中上存在先天不足。例如在Dockerfile中，如果curl某个链接的内容发生了变化，那么同一个Dockerfile在有无本地缓存的情况下可能构建出不同的结果。而当所有内容不变，无本地缓存且远端已有构建结果时，即使理论上构建结果等价，Docker也常常因为mtime等细枝末节的差异而产生全新的layer和image。\nNix在构建完后，还会自动计算包的运行时依赖。它的做法很简单：在构建产物中搜索所有形似/nix/store/\u0026lt;hash\u0026gt;-\u0026lt;name\u0026gt;的字符串。这个技巧行之有效。因为构建发生在一个沙箱里，PATH环境变量指向了不存在的路径，也没有/lib这类隐式依赖。因此构建产物必须显式记录它的依赖才能正常运行。\n1$ nix-store --query --tree result 2/nix/store/b1vbalgjm2andc0624rv036kc1m2lb9d-bbolt-1.4.3 3├───/nix/store/75pp3hj82iirdfl7c153akl56kpffn1z-iana-etc-20250505 4│ └───/nix/store/75pp3hj82iirdfl7c153akl56kpffn1z-iana-etc-20250505 [...] 5├───/nix/store/7nbi22pcc92y2fqbkyp7h3srvvklmckb-glibc-2.40-224 6│ ├───/nix/store/fv5lgysa3hmf3l3dkkpwvndcg6xwhy8m-xgcc-14.3.0-libgcc 7│ ├───/nix/store/qywg7bxskvihq62ms2g51fkzkrdnyfkh-libidn2-2.3.8 8│ │ ├───/nix/store/hjwppd89fk8781xl4r35xqlddwqi5f66-libunistring-1.4.1 9│ │ │ └───/nix/store/hjwppd89fk8781xl4r35xqlddwqi5f66-libunistring-1.4.1 [...] 10│ │ └───/nix/store/qywg7bxskvihq62ms2g51fkzkrdnyfkh-libidn2-2.3.8 [...] 11│ └───/nix/store/7nbi22pcc92y2fqbkyp7h3srvvklmckb-glibc-2.40-224 [...] 12└───/nix/store/hvcpzbmw8hq5fzb424g9z6kkzhyx2iaw-tzdata-2026a 13$ strings result/bin/bbolt | grep -o \u0026#39;/nix/store/[^/]\\+\u0026#39; 14/nix/store/7nbi22pcc92y2fqbkyp7h3srvvklmckb-glibc-2.40-224 15/nix/store/hvcpzbmw8hq5fzb424g9z6kkzhyx2iaw-tzdata-2026a 16/nix/store/75pp3hj82iirdfl7c153akl56kpffn1z-iana-etc-20250505 而传统的打包流程大多需要手动声明包的构建依赖和运行依赖。手动声明依赖很容易出错。在AUR中，遗漏依赖是很常见的错误。\n当然也有命令查看构建依赖闭包。但构建依赖闭包通常很大。 bbolt的构建至少依赖它的代码、bash、git以及go。而bash、git、go的构建又依赖它们各自的代码以及gcc等等。直到一切的源头：某个由Nix语言内置的fetcher下载的初始版gcc和busybox。因此我这里只提供查看的命令，略去输出。\n1$ nix-store --query --tree \u0026#34;$(nix-store --query --deriver result)\u0026#34; 编程设施 Nix是个惰性求值的函数式语言。这些语法特性和包管理碰撞产生了意想不到的火花。\nnixpkgs提供了override机制，这使得你可以修改nixpkgs提供的包。你可以修改软件版本，添加patch，修改编译参数等等。例如，我们可以把Python中int的默认值改成42。\npython-int42.patch 1--- a/Objects/longobject.c 2+++ b/Objects/longobject.c 3@@ -6535,7 +6535,7 @@ long_vectorcall(PyObject *type, PyObject * const*args, 4 } 5 switch (nargs) { 6 case 0: 7- return _PyLong_GetZero(); 8+ return PyLong_FromLong(42L); 9 case 1: 10 return PyNumber_Long(args[0]); 11 case 2: 当然使用这个定制版本的Python可能会花费一小会编译。\nanswer.py 1#!/usr/bin/env nix-shell 2#!nix-shell -i python3 -p \u0026#34;python3.overrideAttrs (oldAttrs: { patches = (oldAttrs.patches or [ ]) ++ [./python-int42.patch]; })\u0026#34; 3print(f\u0026#34;The answer to life, the universe, and everything: {int()}\u0026#34;) nixpkgs还提供了overlay机制。 override提供了单个包层面的自定义，而overlay机制则提供了nixpkgs整体的自定义。你可以使用overlay和override固定nixpkgs中的包版本、增添新的包。 overlay可以叠加，因此也能分发。例如NUR就是社区维护的众多Nix包仓库的一个。\n我们还可以搭配direnv和VSCode direnv插件，为项目的开发提供一个可复现的开发环境。这里Nix代码定义的就是一个shell脚本，这个脚本最终被direnv运行。本博客就是使用这个方式开发的，可以参见blog/shell.nix at main · tillycode/blog。我使用Nix下载了博客主题，加上了自己喜欢的patch，并且配置了各种formatter和linter工具。\nnixpkgs还提供了module机制，让大家以配置的方式，自定义一个像操作系统那样复杂的软件。 Nix提供的module支持类型检查、组合、扩展，这使得它比单纯的YAML更可靠。\n大家可以将自己喜欢的配置想宝可梦卡片那样放在GitHub上，彼此参考、交换。\n/etc/nixos/configuration.nix 1{ config, pkgs, ... }: 2{ 3 imports = [ 4 ./hardware-configuration.nix 5 ]; 6 7 # Networking 8 networking.hostName = \u0026#34;nixos\u0026#34;; 9 10 # Timezone and Locale 11 time.timeZone = \u0026#34;Asia/Shanghai\u0026#34;; 12 i18n.defaultLocale = \u0026#34;en_US.UTF-8\u0026#34;; 13 14 # Define a user account 15 users.users.myuser = { 16 isNormalUser = true; 17 extraGroups = [ \u0026#34;wheel\u0026#34; ]; # Enable sudo 18 }; 19 20 # System-wide packages 21 environment.systemPackages = with pkgs; [ 22 vim 23 ]; 24 25 # Enable OpenSSH 26 services.openssh.enable = true; 27 28 system.stateVersion = \u0026#34;25.11\u0026#34;; 29} 操作系统 Nix代码甚至可以构建一个操作系统NixOS。一个NixOS不过就是一个更大的，包含kernel、initrd以及以systemd为主的各类配置文件的包而已。\n得益于Nix包的不可变性，NixOS可以实现原子更新和回滚。你可以在bootloader里选择本次开机使用的NixOS配置。\n借助Nix提供的可复现性、原子更新，加之配置可分发。大家就可以无惧使用bleeding edge的技术。社区里比较著名的项目有：\nnix-community/impermanence 以tmpfs为根文件系统，管理持久化状态 Mic92/sops-nix ryantm/agenix GitOps管理secret nix-community/disko 声明式磁盘分区 nix-community/nix-ld 运行非Nix打包、需要动态链接的二进制 学习建议 实际上还有Content-Addressed，但那是实验特性。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://szp15.com/zh/posts/nix-introduction/","summary":"\u003cp\u003e若说到当代编程领域的神教，我会想起这3样：Rust、Arch Linux以及Nix。这3个神教都得有一些魔力，吸引信徒虔诚追随。这篇文章主要介绍Nix能做到的事情，用于像初学者传教。\u003c/p\u003e","title":"Nix入坑指南"},{"content":"大家好呀！👋 我叫孙子平，本硕毕业于清华大学软件学院。硕士研究方向是静态分析。目前我在佳期投资从事K8s平台相关的开发运维工作。\n这又不知道是我第几次建站了。不过这次，我会对内容有更高的要求。博客的内容计划是原创的技术分享和自己的一些思考。\n从初中开始，我就痴迷于编程，这么多年来可以说是“码耕不辍”。然而在最近一两年AI普及后，我开始感到迷茫。我很难说服自己写博客还有什么意义。当人们可以从AI那里快速地获得精准的答案时，还有人需要阅读博客吗？ 当文章没有读者，只有作者和以之为数据集的AI们时，文章还有意义吗？ 我不知道。\nAnyway，happy coding！Happy learning！\n","permalink":"https://szp15.com/about/","summary":"\u003cp\u003e大家好呀！👋\n我叫孙子平，本硕毕业于清华大学软件学院。硕士研究方向是静态分析。目前我在佳期投资从事K8s平台相关的开发运维工作。\u003c/p\u003e","title":"关于本站"}]