.deb installation in NixOS

This is by now quite an old post. There's probably a better way, but I leave it here for reference.
February 28, 2015 (nixos)

Since the nixpkgs repository is not yet as comprehensive as those of more established distributions, it can be useful to borrow some of their packages - without having to recompile from source - without having to use heavyweight solutions like VirtualBox or Docker

Here I’ll walk through installing the Debian-packaged hello program.

First we’ll make a directory to play around in, and download the deb file.

$ mkdir ~/deb-playground && cd ~/deb-playground
$ wget -q http://ftp.us.debian.org/debian/pool/main/h/hello-traditional/hello-traditional_2.9-2_amd64.deb

We can install dpkg (to interact with .deb files) and use it to unpack our download.

$ nix-env -i dpkg
installing ‘dpkg-1.16.9’
$ dpkg -x hello-traditional_2.9-2_amd64.deb unpacked-hello

Our unpacked hello contains the precompiled binary, but we can’t run it directly (assuming that you are on a pure NixOS system, with a clean /usr). Trying to do so will give us an incredibly misleading error message.

$ ls unpacked-hello/usr/bin/
hello
$ ./unpacked-hello/usr/bin/hello
bash: ./unpacked-hello/usr/bin/hello: No such file or directory

The binary does exist at that location, regardless of what bash tells us. However, what does not exist is the ELF interpreter which the binary is requesting in order to run. We can see which interpreter that by inspecting the binary with a couple of different tools.

$ nix-env -i patchelf binutils
warning: there are multiple derivations named ‘binutils-2.23.1’; using the first one
installing ‘patchelf-0.8’
installing ‘binutils-2.23.1’
building path(s) ‘/nix/store/pxyqbr13z028x57fymvyxzy1wbwqf0wp-user-environment
created 437 symlinks in user environment
$ patchelf --print-interpreter ./unpacked-hello/usr/bin/hello
/lib64/ld-linux-x86-64.so.2
$ readelf -a ./unpacked-hello/usr/bin/hello | grep interpreter
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

On a NixOS system, the interpreter is somewhere in /nix/store rather than in /lib64, which we can confirm with a couple commands

$ ls /lib64/ld-linux-x86-64.so.2
ls: cannot access /lib64/ld-linux-x86-64.so.2: No such file or directory
$ ls /nix/store/*glibc*/lib/ld-linux-*
/nix/store/211nv7bsm5nxblp9r5k3fmqf2y8yfg11-glibc-2.20/lib/ld-linux-x86-64.so.2
/nix/store/5d59qvn7zj78dwwdn0d6nf2669xs9g30-glibc-multi-2.20/lib/ld-linux-x86-64.so.2

As a side note, we can still run the binary by explicitly specifying the interpreter path, similar to how a script’s shebang line can be overridden by invoking the interpreter directly.

$ /nix/store/211nv7bsm5nxblp9r5k3fmqf2y8yfg11-glibc-2.20/lib/ld-linux-x86-64.so.2 ./unpacked-hello/usr/bin/hello
Hello, world!

However, for a more integrated approach we will use buildFHSUserEnv, a utility package that will construct a more traditional filesystem layout, mounted read-only inside a chroot (FHS is Filesystem Heirarchy Standard http://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard). We start with a basic nix description gives us a shell in such an environment, as well as access to dpkg.

$ cat fhs-env.nix
let nixpkgs = import <nixpkgs> {};
in rec {
  fhsEnv = nixpkgs.buildFHSUserEnv {
    name = "fhs";
    targetPkgs = pkgs: [];
    multiPkgs = pkgs: [ pkgs.dpkg ];
    runScript = "bash";
  };
}

Then we can build the environment and enter it. We haven’t yet incorporated our debian package, so hello will be unavailable.

$ nix-build -A fhsEnv fhs-env.nix
/nix/store/xi84kzfhxvdn3f748v1cpl8mpkjhag7f-fhs-userenv
$ ./result/bin/fhs
$ pwd
/
$ dpkg --version
Debian `dpkg' package management program version 1.16.9 (amd64).
This is free software; see the GNU General Public License version 2 or
later for copying conditions. There is NO warranty.
$ hello
bash: hello: command not found
$ exit

We want to incorporate the hello package by including it in the targetPkgs list in our nix expression. However to do so we fist need to wrap it in a nix expression. We can first do this standalone, without worrying about the FHS aspect.

$ cat dumb-hello.nix
let nixpkgs = import <nixpkgs> {};
    stdenv = nixpkgs.stdenv;
in rec {
  dumb-hello = stdenv.mkDerivation {
    name = "dumb-hello";
    builder = ./builder.sh;
    dpkg = nixpkgs.dpkg;
    src = nixpkgs.fetchurl {
      url = "http://ftp.us.debian.org/debian/pool/main/h/hello-traditional/hello-traditional_2.9-2_amd64.deb";
      md5 = "f5f3c28b65221dae44dda6f242c23316";
    };
  };
}

We’ll also need a builder script which unpacks the .deb with dpkg and places the files in the correct location.

$ cat builder.sh
source $stdenv/setup
PATH=$dpkg/bin:$PATH

dpkg -x $src unpacked

cp -r unpacked/* $out/

Now we can build this package, which exposes the hello binary from the .deb. However we will still be unable to run it due to the interpreter issues.

$ nix-build -A dumb-hello dumb-hello.nix
/nix/store/qaygcmg5kb2gknmzb3miczvr2n001mil-dumb-hello
$ ls ./result/bin/
hello
$ ./result/bin/hello
bash: ./result/bin/hello: No such file or directory

The final step is to combine the two nix expressions, so that our FHS environment includes the hello package.

$ cat full-hello.nix
let nixpkgs = import <nixpkgs> {};
    stdenv = nixpkgs.stdenv;
in rec {
  dumb-hello = stdenv.mkDerivation {
    name = "dumb-hello";
    builder = ./builder.sh;
    dpkg = nixpkgs.dpkg;
    src = nixpkgs.fetchurl {
      url = "http://ftp.us.debian.org/debian/pool/main/h/hello-traditional/hello-traditional_2.9-2_amd64.deb";
      md5 = "f5f3c28b65221dae44dda6f242c23316";
    };
  };
  full-hello = nixpkgs.buildFHSUserEnv {
    name = "full-hello";
    targetPkgs = pkgs: [ dumb-hello ];
    multiPkgs = pkgs: [ pkgs.dpkg ];
    runScript = "hello";
  };
}

All done! We can build our expression, activate it to enter the environment, and run our debian-packaged binary.

$ nix-build -A full-hello full-hello.nix
/nix/store/j2cnf3bnzpd97qxavrxgk58dk7cmcrlf-full-hello-userenv
[anders@gurney:~/deb-playground]
$ ./result/bin/full-hello
Hello, world!

It looks like it’s not possible to pass arguments through, but it could be enabled by a straightforward modification to the buildFHSUserEnv package in the future.