I have been working more on GuixPkgs in preparation for a talk at nix.vegas for DEFCON34. At the end of my previous GuixPkgs post I left a teaser:
We can then build a NixOS machine where every package is the Guix equivalent 😱.
Well, adeci11It has been extremely fun and rewarding to work with Alex on this project. We nix-pilled him at PlanetNix 2 years ago and since then he has been pushing the boundaries of what Nix can do and is currently employed at Shopify working on Nix. took the bait and we went even further than that. 😈
Say hello to Guix by Nix: a bootable VM where the kernel is Guix’s Linux-libre, the userland is translated Guix packages, and PID 1 is GNU Shepherd.
No systemd. No D-Bus. No NixOS activation. Not even a nix or guix binary in the guest. It’s Guile all the way down and Nix built all of it.
$ nix run --accept-flake-config github:adeci/guix-by-nix
...
Starting Guix-derived initrd
GUIX_BY_NIX_INITRD_MODULES_READY modules=virtio_pci,virtio_blk,...,overlay
GUIX_BY_NIX_INITRD_ROOT_READY device=/dev/vda
Starting Guix by Nix activation
GUIX_BY_NIX_ACTIVATION_READY
Starting Guix Shepherd as PID 1
SHEPHERD_NETWORK_READY address=10.0.2.15 gateway=10.0.2.2 resolver=10.0.2.3
SHEPHERD_PID1 parent=1 child=690
Welcome to Guix by Nix (experimental)
guix-by-nix login: root
Log in with root / guix-by-nix and poke around:
bash-5.2# ps -p 1
PID TTY TIME CMD
1 ? 00:00:00 shepherd
bash-5.2# uname -r
6.12.62-gnu
bash-5.2# readlink -f "$(command -v ls)"
/nix/store/...-coreutils-9.1/bin/ls
As a reminder, even though these binaries live in /nix/store, they are not Nixpkgs packages. They were translated from Guix derivations using guix-transfer and built by the nix-daemon.
That coreutils was built from Guix’s package definition, source bootstrap and all , but it lives in /nix/store, because nix-daemon built it.
If you want to try out any of these packages on your own machine, you can use GuixPkgs.
$ nix run --accept-flake-config github:fzakaria/guixpkgs#coreutils
Tip Guix offers all packages built from source where Nix may offer it as a prebuilt binary. You can use GuixPkgs to get a source-built bootstrapped version of OpenJDK for example and all the whacky steps to get there.
§How it works
How does this actually work? The project is a three-stage pipeline:
- guix-transfer is the tool that translates Guix derivation graphs into Nix derivations.
- GuixPkgs is the flake with all Guix packages, all built from the 357-byte seed, although there is a Cachix cache provided.
- Guix by Nix assembles ~42 of those packages, a subset of the overall set, into a useable machine.
Warning AI was leveraged to write the initrd and activation scripts. That seems to trigger people lately, so consider yourself warned.
- The initrd is a custom shell script, interpreted by Guix Bash, that loads eight modules, mounts the root disk and the 9p store, and
switch_roots. - Activation (accounts,
/etc, the setuidsudocopy) is another custom Nix-generated script run by Guix Bash. - PID 1 is
shepherdfrom Guix, with a small Scheme config that startseudev,dhcpcd,openresolv, and a serialgetty.herd statusworks like you’d expect. /etc/profileis compiled from Guix’s own search-path specifications, soGUILE_LOAD_PATHand friends point where Guix intended.
Every program that can be executed, every ELF file, every script interpreter, all traces back to a translated Guix derivation. The only things Nix authored are text files: the init scripts, the Shepherd config, /etc.
This is Guix by Nix.
§Don’t trust me. Read the receipt.
“Every executable byte comes from Guix” is exactly the kind of claim that’s easy to say and easy to fudge. 🤥
A booted demo is great but that can’t prove it. A VM where ls secretly came from Nixpkgs boots identically.
The flake ships an audit-check that classifies every store path in the shipped closure by provenance, unpacks the compressed initrd22Nix’s reference scanner can’t see inside archives and sneaky paths hide there. , and inspects every executable payload. The audit derivation fails if anything is unclassified, any ELF file or script interpreter doesn’t trace to a translated Guix output, any /gnu/store reference survived translation, or anything systemd-shaped appears anywhere.
$ nix build --accept-flake-config github:adeci/guix-by-nix#status-report
$ jq '{rejected, claims}' result/status.json
{
"rejected": {
"wrapperPaths": 0,
"nonGuixElf": 0,
"externalScriptInterpreters": 0,
"untranslatedGnuStoreReferences": 0,
"unclassifiedPaths": 0,
"systemdDbusLogindPaths": 0
},
"claims": {
"translatedLinuxLibre": true,
"wrapperFreeGuest": true,
"allElfFromGuix": true,
"allScriptInterpretersGuixDerived": true,
"systemdFreeGuest": true,
"fullyClassified": true,
"emptyFirmwareBoundary": true
}
}
The report includes a lot more information such as the exact Guix channel commit everything was translated from. There’s also some NixOS VM tests for good-measure. 🕵
Note The kernel is Guix’s
linux-libre-lts, translated and built bynix-daemonlike everything else. Nix wraps it in a thin adapter so NixOS’s VM tooling accepts it by augmenting with some additional metadata only; thebzImageis byte-for-byte Guix’s.
§What’s next
One obvious next step would be a normal NixOS machine, where every package that exists in GuixPkgs shadows its Nixpkgs equivalent on $PATH. GuixPkgs offers such an overlay, but it needs quite a lot of CPU to build all of it….
A more realistic use case is to mix and match GuixPkgs and Nixpkgs packages in a single system. Get the best of both. I heard there were people in the Nix community who still want a non-systemd system, à la sixos. 🫠
For now, this project remains a minimal VM demo. If you make it PID 1 on real hardware, please send photos. 🙇