The Guix Nix Abomination: Leveraging Guix derivations in Nix
The Guix Nix Abomination: Leveraging Guix derivations in Nix
Guix 与 Nix 的“混血”实验:在 Nix 中利用 Guix 推导式
Nix and Guix look like rival ecosystems, but under the hood they’re the same “Input Output Machine”. Need proof? 🕵 How about we build a Guix derivation with Nix. Nix 和 Guix 看起来像是相互竞争的生态系统,但从底层来看,它们本质上都是同一个“输入输出机”。需要证据吗?🕵 不如我们尝试用 Nix 来构建一个 Guix 推导式(derivation)。
First let’s create a super basic derivation in Guix: Hello world. 首先,让我们在 Guix 中创建一个最基础的推导式:Hello world。
❯ guix repl -- /dev/stdin <<'EOF'
(use-modules (guix derivations) (guix store))
(with-store %store
(let ((drv (derivation %store "simple" "/bin/sh" '("-c" "echo Hello World > $out")
#:env-vars '(("PATH" . "/bin"))
#:system "x86_64-linux")))
(format #t "~a\n" (derivation-file-name drv))))
EOF
/gnu/store/zr0q11srv4yir8a6wrz582js7zsi17ij-simple.drv
We then ask Nix to build it. 🪄 We ask to use /gnu/store as the Nix store and have it write its state, database and log files in alternate directories, so it does not collide or mess with Guix.
接着,我们让 Nix 来构建它。🪄 我们指定使用 /gnu/store 作为 Nix 存储库,并将其状态、数据库和日志文件写入其他目录,以避免与 Guix 发生冲突或干扰。
Note: It’s slightly more complicated. Nix happens to check its SQLite database for the derivation, so we need to register it first. The version of Guix (v1.5.0) I’m using leverages a guix-daemon user that runs inside a private mount namespace where /gnu/store is writable, but everyone else (including me) sees it as read-only. The unshare —mount creates a new private mount namespace so I can mount it as read-write and run the Nix command against it.
注意:过程稍微复杂一些。Nix 会检查其 SQLite 数据库以确认推导式,因此我们需要先注册它。我使用的 Guix 版本 (v1.5.0) 利用了一个在私有挂载命名空间中运行的 guix-daemon 用户,在该空间内 /gnu/store 是可写的,但对其他人(包括我)来说它是只读的。unshare --mount 创建了一个新的私有挂载命名空间,这样我就可以将其挂载为读写模式,并针对它运行 Nix 命令。
❯ cat > /tmp/register.txt <<'EOF'
/gnu/store/zr0q11srv4yir8a6wrz582js7zsi17ij-simple.drv 822a79886102e5ca392cd14358aef0866c36ca526ff1b156f1ded2808a2095df 336 0
EOF
❯ NIX_STORE=/nix/store/fla7gi1dvkw4hvwxar8m7z25p2yv7r40-nix-2.34.7/bin/nix-store
❯ NIX_STORE_DIR=/gnu/store NIX_STATE_DIR=/tmp/nix-gnu/var/nix \
$NIX_STORE --load-db < /tmp/register.txt
❯ sudo unshare --mount bash -c '
mount -o remount,rw /gnu/store
su -s /bin/bash guix-daemon -c \
"NIX_STORE_DIR=/gnu/store NIX_STATE_DIR=/tmp/nix-gnu/var/nix \
NIX_LOG_DIR=/tmp/nix-gnu/var/log/nix \
/nix/store/fla7gi1dvkw4hvwxar8m7z25p2yv7r40-nix-2.34.7/bin/nix-store \
--realise /gnu/store/zr0q11srv4yir8a6wrz582js7zsi17ij-simple.drv"
'
warning: creating directory "/var/empty/.cache/nix": Permission denied
this derivation will be built: /gnu/store/zr0q11srv4yir8a6wrz582js7zsi17ij-simple.drv
building '/gnu/store/zr0q11srv4yir8a6wrz582js7zsi17ij-simple.drv'...
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
/gnu/store/kd5szqbl9asz5hravhnxgd9plm4a9gzh-simple
❯ cat /gnu/store/kd5szqbl9asz5hravhnxgd9plm4a9gzh-simple
Hello World
We just built a Guix derivation using Nix. 🔥 How is that possible? Both take a language frontend, Nix or Guile (Scheme), that compiles to a derivation (recipe) and pass that onto a builder (daemon) that executes it to produce an output. 我们刚刚用 Nix 构建了一个 Guix 推导式。🔥 这怎么可能呢?两者都采用一种语言前端(Nix 或 Guile/Scheme),将其编译为推导式(配方),然后传递给构建器(守护进程)执行,从而产生输出。
What makes them both special is they both promise the same thing: hermetic builds. Everything needed to build the output is declared in the recipe: sources, environment variables, dependencies, etc. 它们之所以特别,是因为它们都承诺了同一件事:密封构建(hermetic builds)。构建输出所需的一切都在配方中声明:源代码、环境变量、依赖项等。
“Under Nix, a build process will only find resources that have been declared explicitly as dependencies. There’s no way it can build until everything it needs has been correctly declared. If it builds, you will know you’ve provided a complete declaration.” – Nix OS Website “在 Nix 下,构建过程只能找到被显式声明为依赖项的资源。除非所有需求都被正确声明,否则它无法构建。如果构建成功,你就知道你已经提供了完整的声明。” —— Nix OS 官网
Guix, specifically the daemon, was forked from Nix early on, and as a result the two are very similar; they both share the same derivation format, ATerm, for instance. Guix(特别是其守护进程)早期是从 Nix 分支出来的,因此两者非常相似;例如,它们都共享相同的推导式格式:ATerm。
Guix is based on the Nix package manager – Guix Website Guix 基于 Nix 包管理器 —— Guix 官网
That’s why our earlier example of building the Guix derivation with Nix was possible without much translation. What if we could leverage an existing recipe from Guix in Nix in its traditional /nix/store? If we could convert from one recipe file to the other, we could use the existing recipes from Guix in Nix and vice versa.
这就是为什么我们之前用 Nix 构建 Guix 推导式的例子无需太多转换就能实现。如果我们能在 Nix 中利用 Guix 现有的配方,并将其放入传统的 /nix/store 中会怎样?如果我们能将一种配方文件转换为另一种,我们就能在 Nix 中使用 Guix 的现有配方,反之亦然。
Turns out this is far more feasible than you would think, because Guix is Nix or at least a superset of it. I, with the help of Claude, built a tool to do just that: guix-transfer 🤯.
事实证明,这比你想象的要可行得多,因为 Guix 本质上就是 Nix,或者至少是它的超集。在 Claude 的帮助下,我构建了一个工具来完成这项工作:guix-transfer 🤯。
guix-transfer is a CLI tool for performing bottom-up translation of GNU Guix derivations into Nix. Confused? Let us see it in action:
guix-transfer 是一个命令行工具,用于将 GNU Guix 推导式自底向上转换为 Nix 推导式。感到困惑?让我们看看它的实际操作:
# generate a Guix derivation
❯ guix build hello --derivations
/gnu/store/2nfg943asrl9dv64zrr1a4kpb25mfafd-hello-2.12.2.drv
# translate it
❯ ./guix-transfer /gnu/store/2nfg943asrl9dv64zrr1a4kpb25mfafd-hello-2.12.2.drv
Loading Guix derivation graph from /gnu/store/2nfg943asrl9dv64zrr1a4kpb25mfafd-hello-2.12.2.drv ...
Loaded 228 derivations.
Translating bottom-up ...
[228/228] done
Done. Final Nix derivation: /nix/store/brdd8zw3j9hhq8zf27ixqyi3l61nwppn-hello-2.12.2.drv
Realise it with:
nix-store --realise --option filter-syscalls false /nix/store/brdd8zw3j9hhq8zf27ixqyi3l61nwppn-hello-2.12.2.drv
# build it with Nix
# this is a LONG multi-hour build since we build everything from source
❯ nix-store --option filter-syscalls false --realise \
/nix/store/brdd8zw3j9hhq8zf27ixqyi3l61nwppn-hello-2.12.2.drv
❯ /nix/store/j3940mdzr6qmw4ydhyla663s501vb8ns-hello-2.12.2/bin/hello
Hello, world!
Note: When you unpack a tarball, tar restores each file’s original permissions, including setuid/setgid bits. Nix’s sandbox installs a seccomp filter that blocks any chmod call that sets these bits, returning “Operation not permitted”. Guix’s early bootstrap uses a Scheme-based tar (gash-utils) that treats this error as fatal, unlike GNU tar which silently skips it. The fix is —option filter-syscalls false, which disables the filter.
注意:当你解压 tarball 时,tar 会恢复每个文件的原始权限,包括 setuid/setgid 位。Nix 的沙盒安装了一个 seccomp 过滤器,会阻止任何设置这些位的 chmod 调用,并返回“Operation not permitted”(操作不允许)。Guix 的早期引导程序使用基于 Scheme 的 tar (gash-utils),它将此错误视为致命错误,而 GNU tar 则会静默跳过。解决方法是使用 --option filter-syscalls false,这会禁用该过滤器。
If it’s not clear what we just did: we took a Guix derivation and all of its dependencies (down to the bootstrap seeds), translated it to a Nix derivation, and built it with Nix. 😲 What is this abomination and how was this possible!? 如果还不清楚我们刚才做了什么:我们获取了一个 Guix 推导式及其所有依赖项(一直到引导种子),将其转换为 Nix 推导式,并用 Nix 构建了它。😲 这是什么怪物,又是如何实现的!?
It’s important to revisit what a derivation is, and how it’s used in Nix and Guix to better understand how this is possible. Let’s look at the same basic derivation from earlier, Hello World. You might want to check out my other post on Nix derivations by hand if this interests you 🤓. 为了更好地理解这是如何实现的,我们需要重新审视什么是推导式,以及它在 Nix 和 Guix 中是如何使用的。让我们看看之前那个基础的 Hello World 推导式。如果你对此感兴趣,可以查看我关于手动编写 Nix 推导式的另一篇文章 🤓。
derivation {
name = "simple";
builder = "/bin/sh";
system = builtins.currentSystem;
args = ["-c" ''
echo "Hello World" > $out
''];
}
When we evaluate (nix-instantiate) this derivation, we get a path to a file that con… 当我们评估(nix-instantiate)这个推导式时,我们会得到一个文件路径,其中包含……