Nix 仓库仅需三个函数

Lobsters Hottest 工具

摘要

本文解释了如何仅用三个简单的 HTTP 函数实现 Nix 仓库,使得像 GitHub Pages 这样的静态文件服务器能够充当 Nix 二进制缓存。

<p><a href="https://lobste.rs/s/9zytkh/nix_store_is_three_functions">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/09/15 05:10

# Nix存储的本质是三个函数 来源:https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions 在构建trynix (https://fzakaria.com/2026/09/04/any-nix-package-live-in-your-browser)的过程中,我需要托管一个在cache.nixos.org (https://cache.nixos.org/)上不存在的store-path。同时我还在等待@domenkozar (https://github.com/domenkozar)为cache.nixos.org (https://cache.nixos.org/)启用CORS,以便使用它。我想证明非Nixpkgs的store-path也能同样轻松地启动。唯一的要求似乎是宽松的跨源资源共享(CORS)(https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)策略——`access-control-allow-origin: *`,因为获取操作是在JavaScript中进行的。结果发现GitHub Pages在其托管的每个文件上都设置了该头信息。😈 我将`nix copy --to file://`的输出提交到了我的Git仓库 (https://github.com/fzakaria/trynix/tree/main/site/examples/cache),瞧,我就有了一个*免费的*Nix替换器。关于这个发现,我似乎有些后知后觉了。tomberek的github-store (https://github.com/tomberek/github-store)就是一个由GitHub release资源组合而成的缓存。为了成为Nix二进制缓存,需要从narinfo的`URL`字段中去除`nar/`前缀,因为GitHub releases是扁平的命名空间。 ```bash $ B=https://github.com/tomberek/github-store/releases/latest/download $ curl -sL $B/nix-cache-info StoreDir: /nix/store $ curl -sL $B/1zy01hjzwvvia6h9dq5xar88v77fgh9x.narinfo StorePath: /nix/store/1zy01hjzwvvia6h9dq5xar88v77fgh9x-glibc-2.38-44 URL: 0hkyywarmj4frwvs6p4lz4yl6z5q5halphswlqksh7lbkn4r75si.nar.xz Compression: xz FileHash: sha256:0hkyywarmj4frwvs6p4lz4yl6z5q5halphswlqksh7lbkn4r75si FileSize: 6514112 NarHash: sha256:1ikxmc8yxzmm9vzzaa313w9yzrrgm0p6cgscy8arq3z32kynpi94 NarSize: 30244048 References: 1zy01hjzwvvia6h9dq5xar88v77fgh9x-glibc-2.38-44 a3n1vq6fxkpk5jv4wmqa1kpd3jzqhml9-libidn2-2.3.4 ... Deriver: 3fd7s6gjwi6rxfqw00bjq9ghnvazvnnn-glibc-2.38-44.drv Sig: cache.nixos.org-1:YWkvHMXyvOw1iqWblEdvju+OZbGOvwYXyjyRUxuXluFq17xwJFSzHb0HrIuRr9BXYjV6GxfmGGv+ckVNvAOpDQ== $ curl -sL $B/0hkyywarmj4frwvs6p4lz4yl6z5q5halphswlqksh7lbkn4r75si.nar.xz | wc -c 6514112 ``` GitHub Pages和Releases都是静态文件服务器。它们根本不知道Nix是什么。如果一个普通的文件服务器就能充当Nix二进制缓存,我们还能用什么呢? ## § [接口](https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions#the-interface) 事实证明,要成为一个Nix二进制缓存,只需实现三个简单的函数。Nix客户端并不关心你使用什么媒介来实现它们,尽管HTTP是最常见的方式,并且默认包含在CppNix (https://github.com/NixOS/nix)中。如果你想实现新的协议,也可以编写一个Nix插件。 ``` GET nix-cache-info → StoreDir: /nix/store GET <32-char hash>.narinfo → 命名存档的元数据 GET <url> → 压缩的存档 ``` 就这些。任何能响应这三个请求的东西都可以用作远程*Nix存储*。我们将看到,它们甚至不需要位于相同的媒介、协议或域名上! ## § [关于签名](https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions#what-about-the-signatures) ![辛普森一家关于签名的梗图](https://fzakaria.com/assets/images/simpsons_meme_children_signatures.png) 我们可以如此随意对待传输媒介的原因在于,Nix并不信任它。narinfo的签名(`Sig`)字段覆盖了`StorePath`、`NarHash`、`NarSize`和`References`。它不包含`URL`、`FileHash`、`FileSize`或`Compression`。一旦获取到存档,Nix会解压它并检查`NarHash`是否匹配。这就是*关键秘诀*,使得由cache.nixos.org (https://cache.nixos.org/)签名的软件包可以从任何其他二进制缓存作为中介获取,并且签名仍然有效。`URL`字段甚至不必与narinfo位于同一主机上。它可以位于互联网上的任何位置,也可以是不同于HTTP的协议。Nix并不在意。唯一重要的是从`URL`获取的存档具有与narinfo相同的`NarHash`。 ## § [替代存储](https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions#alternative-stores) 对于未包含在Nix客户端默认支持中的协议,你始终可以编写一个HTTP代理,将这三个函数转换为你想要的任何媒介。在为本文进行调研时,我发现了一些有趣的例子。 **gachix (https://github.com/EphraimSiegfried/gachix)**:将存档放入git的对象数据库。Git已经对内容进行寻址并使用增量压缩存储数据块,因此存储自身就能去重;作者报告其大小比等效的普通缓存小约82%。 **DNS**:我写了一个概念验证,将narinfo和4 KiB的存档分片放入TXT记录。narinfo足够小可以放在一条记录中,但存档需要被分块。 **pastebin (https://pastebin.com/)**:一个paste服务可以存放narinfo和存档。narinfo足够小可以放在一个粘贴中,但存档需要被分块。许多paste服务有过期策略,这可以作为天然的垃圾回收器。 **nixcache-oci (https://github.com/cmspam/nixcache-oci)**:使用OCI注册表来存储Nix存档。 **infinite storage glitch (https://github.com/KKarmugil/Infinite_Storage_Glitch)**:将数据编码到视频中并上传到YouTube。 ## § [npm](https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions#npm) > “一切都可以在npm上找到”——互联网上的某位人士 毫不奇怪,npm是一个*出色的*二进制缓存,并且它具有一些我们可以利用的、与版本管理相关的有趣特性。`nix copy --to file://`会输出一个目录,而npm可以发布目录:天作之合。💑 让我们通过一个小型的`hello`示例来了解。 ```nix packages.${system}.default = pkgs.hello.overrideAttrs (old: { pname = "hello-npm"; # 上游测试套件会grep原始的问候语。 doCheck = false; postPatch = (old.postPatch or "") + '' substituteInPlace src/hello.c \ --replace-fail 'Hello, world!' 'Hello from the npm registry!' ''; }); ``` 它动态链接到glibc,因此闭包包含五个路径,大约36 MiB: ``` $ nix path-info -rSh result /nix/store/yh8rykx8wakl1ccn8rc351f6r2wbg4cn-libunistring-1.4.2 2.0 MiB /nix/store/nga9d6m9iplygw3iqghk2g840nz7b0gy-libidn2-2.3.8 2.3 MiB /nix/store/ssvq1r0xd8f7paf6zqgpfql1a4drwhy2-xgcc-15.3.0-libgcc 193.0 KiB /nix/store/n51dhmdbik1kfrsm62j5knavmigwrl1a-glibc-2.42-84 36.0 MiB /nix/store/3ssib4ic89qw7x1gha10s6mdf42fk15v-hello-npm-2.12.3 36.2 MiB ``` 我们将其复制到本地缓存,使用我们自己的密钥签名,并添加npm所需的单个文件(`package.json`): ``` $ nix key generate-secret --key-name hello-npm-1 > cache-key.sec $ nix key convert-secret-to-public < cache-key.sec > cache-key.pub $ nix copy --to "file://$PWD/cache?secret-key=$PWD/cache-key.sec" .# $ cd cache && rm -rf log build-trace-v2 && npm init --scope=@fzakaria -y ``` 然后`npm publish`会忠实地为我们打包完整的闭包: ``` $ npm publish --access public npm notice 606B 3ssib4ic89qw7x1gha10s6mdf42fk15v.narinfo npm notice 767B n51dhmdbik1kfrsm62j5knavmigwrl1a.narinfo npm notice 7.3MB nar/06fsxd8j9ck4ls5b8xj4r4zk2642xzxzmdj6bl93zfwcm4pqzx0z.nar.xz npm notice 467.5kB nar/1asdj56kfpvbqh8bpg5wns4v0yd1p3ldl6p2swv6nfwfdgvbwklc.nar.xz npm notice 21B nix-cache-info npm notice name: @fzakaria/hello-nix-cache npm notice version: 1.0.0 npm notice package size: 8.0 MB total files: 12 ``` `@fzakaria/hello-nix-cache` (https://www.npmjs.com/package/@fzakaria/hello-nix-cache)现在是公共npm注册表上的一个真实包。它现在是一个你可以直接将Nix指向的替换器: ``` $ nix copy --from https://unpkg.com/@fzakaria/hello-nix-cache@latest/ \ --to ./npmstore \ --trusted-public-keys 'hello-npm-1:u4SntZm2u3sob9zBw2OG6yePvJGoRRQUk00LQh4pnKA=' \ /nix/store/3ssib4ic89qw7x1gha10s6mdf42fk15v-hello-npm-2.12.3 copying 5 paths... copying path '/nix/store/ssvq1r0xd8f7paf6zqgpfql1a4drwhy2-xgcc-15.3.0-libgcc' from 'https://unpkg.com/@fzakaria/hello-nix-cache@latest'... copying path '/nix/store/n51dhmdbik1kfrsm62j5knavmigwrl1a-glibc-2.42-84' from 'https://unpkg.com/@fzakaria/hello-nix-cache@latest'... copying path '/nix/store/3ssib4ic89qw7x1gha10s6mdf42fk15v-hello-npm-2.12.3' from 'https://unpkg.com/@fzakaria/hello-nix-cache@latest'... $ bwrap --ro-bind ./npmstore/nix/store /nix/store --proc /proc --dev /dev \ /nix/store/3ssib4ic89qw7x1gha10s6mdf42fk15v-hello-npm-2.12.3/bin/hello Hello from the npm registry! ``` > **注意**:我们必须使用`bwrap`来运行二进制文件,因为`./npmstore`是一个*chroot存储*,所有路径仍然位于`/nix/store`下。如果我们拥有可重定位的二进制文件 (https://fzakaria.com/2026/06/21/nix-needs-relocatable-binaries),我们就可以直接运行它。 这就是Nix从npm获取完整闭包并运行它的过程。🤯 我们可以将Nix软件包分发给非Nix用户,让感染蔓延开来! 作为额外的好处,类似于Nixpkgs和NixOS,我们可以利用npm的dist-tags获得良好的“通道”语义。`latest`标签是可变的,指向最新版本,而每个版本都是不可变的,指向特定的store-path。 ``` $ npm dist-tag add @fzakaria/[email protected] staging $ npm dist-tag add @fzakaria/[email protected] production ``` 这种方法的主要缺点是npm没有增量发布功能。每个版本都是一个完整的压缩包,因此五十个共享glibc的闭包会上传glibc五十次。我们*可以*通过将每个store-path发布为单独的包,然后让一个小的索引包指向它们来解决这个问题。这样每个store-path就只会被上传一次。但我不会构建这个,因为这对npm生态系统不够尊重。我们还能找到哪些其他的存储实现?

相似文章

不到100行代码实现nix-build

Lobsters Hottest

本文通过用不到100行Go代码重新实现nix-build,揭示了Nix构建过程,表明将派生转换为存储路径本质上就是一次执行。

Any Nix package, live in your browser

Lobsters Hottest

trynix.dev is a browser-based runner that boots a Linux VM in WebAssembly to let users execute any historical Nix package instantly without local installation. It can pull binaries from the public Nix cache, Cachix, or any CORS-enabled store, enabling over 310,000 nixpkgs versions to run directly in a browser tab.

任意 Nix 包,浏览器中实时运行

Simon Willison's Blog

文章介绍了 trynix,一个基于 WebAssembly 的工具,可在浏览器中运行过去13年内的任何 Nix 包,并重点介绍了一个 GitHub Action,用于交互式地启动拉取请求。

一个 Nix flake 统治一切

Hacker News Top

本文介绍 omniflake,这是一款 Nix 工具,它将数千个 flake 合并为一个输入,以简化依赖管理并提升基于 Nix 系统的用户体验。

Nix for Haskell: 静态构建

Lobsters Hottest

本教程介绍如何使用 Nix 为 Haskell 项目创建静态链接的可执行文件,涵盖 GHC 的静态构建配置以及与 Docker 的集成。