fzakaria/guix-transfer
{ "createdAt": "2026-06-04T03:57:37Z", "defaultBranch": "main", "description": "A CLI tool for performing bottom-up translation of GNU Guix derivations into Nix.", "fullName": "fzakaria/guix-transfer", "homepage": "", "language": "Rust", "name": "guix-transfer", "pushedAt": "2026-06-25T22:25:11Z", "stargazersCount": 13, "topics": [ "guix", "linux", "nix", "nixos" ], "updatedAt": "2026-06-26T16:01:59Z", "url": "https://github.com/fzakaria/guix-transfer"}guix-transfer 🏗️
Section titled “guix-transfer 🏗️”Import a GNU Guix derivation graph into Nix and let
nix-daemonbuild it — no rewriting to Nix expressions, nostdenv, no re-bootstrapping.
❯ guix-transfer /gnu/store/w9krgvil6919s2ghqgx443zb9krx75s6-minimal.drvLoading Guix derivation graph from /gnu/store/...-minimal.drv ...Loaded 1 derivations.Translating bottom-up ...Done. Final Nix derivation:/nix/store/m367ssr7zqj6mksp889gx4x177r2ngdi-minimal.drv
❯ nix-store --realise /nix/store/m367ssr7zqj6mksp889gx4x177r2ngdi-minimal.drv/nix/store/c6dk6nhykapfl951rmvw22m99p1nzjwi-minimal
❯ cat /nix/store/c6dk6nhykapfl951rmvw22m99p1nzjwi-minimalSuccessGuix and Nix feel like rival universes, but at the bottom they are the same
thing: a .drv is an ATerm
Derive(...) record, and nix-daemon / guix-daemon are both just sandboxed
builders that take such a record and produce its outputs. A Guix derivation is
already fully hermetic — it lists every input, every source, every env var.
So do we really need to port a Guix package to build it under Nix? No. We
can translate the derivation graph directly and hand it to the Nix daemon. The
only differences are cosmetic: the store prefix (/gnu/store vs /nix/store),
how output paths are hashed (same algorithm, different store dir → different
paths), and the builtin:download vs builtin:fetchurl source fetcher.
The fun part: this goes all the way down. Guix’s whole world is built from a
tiny set of statically-linked seed binaries it downloads. Those seeds have no
baked-in store paths, so once translated they run in the Nix sandbox unchanged,
and everything above them — mes, tcc, gcc-mesboot, glibc, guile,
gcc, … up to hello — builds organically in Nix from Guix’s own sources.
Note: this is a proof-of-concept / curiosity, not a packaging strategy. The resulting
/nix/storepaths are content-equivalent to Guix’s, but built by the Nix daemon. Realisinghelloend-to-end recompiles Guix’s entire source bootstrap, which takes hours.
How it works
Section titled “How it works”guix-transfer walks the .drv DAG in post-order and, for each derivation:
builtin:download→builtin:fetchurl. The URL is rewritten to Guix’s content-addressed mirror,https://bordeaux.guix.gnu.org/file/<name>/sha256/ <hash>.builtin:fetchurlcan only take one URL and can’t fall back, and the upstream mirror lists are flaky — but the CA mirror serves any source Guix’s CI has seen, keyed by the hash we already have. One reliable URL.- Sources are added to the Nix store (text files get their
/gnu/storereferences rewritten first). - Every
/gnu/storereference — input derivations, builder, args, env — is rewritten to the already-translated/nix/storecounterpart. - Output paths are blanked and the derivation is registered via
nix derivation add(JSON format v4), which lets the Nix daemon compute the canonical output paths and.drvpath itself.
There is deliberately no stdenv substitution and no bootstrap
“boundary”: the seeds translate like everything else. See
[DESIGN.md]!(DESIGN.md) for the architecture and [NOTES.md]!(NOTES.md) for the
empirical log (including a few dead ends, like why nix-store --add can’t
register a .drv).
Getting started
Section titled “Getting started”You need nix (with the nix-command experimental feature) and a working
guix to generate the input derivations.
# build it❯ nix-shell -p cargo rustc gcc --run "cargo build --release"
# generate a Guix derivation❯ guix build hello --derivations/gnu/store/...-hello-2.12.2.drv
# translate it (prints the Nix .drv on stdout; logs go to stderr)❯ ./target/release/guix-transfer /gnu/store/...-hello-2.12.2.drv/nix/store/...-hello-2.12.2.drv
# build it with Nix❯ nix-store --realise /nix/store/...-hello-2.12.2.drvNote: building the deep bootstrap (m4-boot0, hello) needs
--option filter-syscalls falseon the realise. Guix’s earlytar(gash) restores the setgid bit on directories inside some source tarballs, and Nix’s seccomp filter otherwise blocks setuid/setgidchmod(it strips those bits from outputs regardless, so this only affects build-time temp dirs). Examples 1–4 don’t need it.
Flags: -v for per-derivation logging, --upstream to fetch from the original
mirrors (ranked + probed) instead of the Guix CA mirror, --emit-nix <output.nix>
to generate a standalone Nix expression (see below).
--emit-nix: standalone Nix expressions
Section titled “--emit-nix: standalone Nix expressions”guix-transfer can emit a self-contained .nix file alongside the normal
translation. The file reconstructs every derivation in the graph as a
builtins.derivation call inside a single let … in block, with dependencies
wired via Nix string interpolation (so inputDrvs/inputSrcs are tracked
correctly).
❯ ./target/release/guix-transfer --emit-nix /tmp/hello.nix /gnu/store/…-hello-2.12.2.drvEmitted Nix expression: /tmp/hello.nix
❯ nix-build /tmp/hello.nix --no-out-link/nix/store/…-hello-2.12.2The generated expression can be imported from other Nix files:
let guixHello = import /tmp/hello.nix;in derivation { name = "use-guix"; system = "x86_64-linux"; builder = "/bin/sh"; args = [ "-c" "echo $(${guixHello}/bin/hello) from Nix > $out" ];}Examples
Section titled “Examples”A ladder of .drv-generating Scheme snippets, simplest first, lives in
[examples/]!(examples/). Run the whole suite with
[examples/validate_all.sh]!(examples/validate_all.sh).
| # | Example | Exercises | Realises under Nix |
|---|---|---|---|
| 1 | minimal | raw /bin/sh derivation | ✅ → Success |
| 2 | fod | builtin:download → builtin:fetchurl | ✅ (downloads + hash-checks) |
| 3 | dependencies | a 2-level graph with an output reference | ✅ → Captured: Shared Secret |
| 4 | bootstrap-seed | %bootstrap-guile: executable seed downloads + a generated wrapper | ✅ runs guile 2.0.9 under Nix |
| 5 | m4-boot0 | the early bootstrap chain (140 derivations) | translates clean; realise = full mesboot compile |
| 6 | hello | the full hello DAG (228 derivations) | translates clean; realise rebuilds the world |
| 7 | mixed | Guix writes “hello”, Nix appends ” world” | ✅ cross-ecosystem composition |
Examples 1–6 all translate with zero leftover /gnu/store references.
Development
Section titled “Development”❯ nix-shell -p cargo rustc gcc --run "cargo test"The logic that doesn’t need a store — ATerm parsing, hash/base32 conversion,
the CA-mirror URL, JSON v4 emission, URL selection — is covered by unit tests
(checked against nix hash where relevant).
Questions
Section titled “Questions”Is this affiliated with the Guix or Nix projects? No. It’s a personal experiment.
Does it produce bit-identical outputs to Guix? The fixed-output sources are identical (same content hash). Built outputs are produced by the Nix daemon from the same inputs; they should be functionally equivalent, but this isn’t a reproducibility claim.
Why not just use guix-daemon? That would defeat the point — the goal is to
show a Guix graph building under Nix, because the two are closer than they
look.
License
Section titled “License”MIT. Not affiliated with the GNU Guix or NixOS projects.