How can I run an image from nixos-anywhere in an emulator like VirtualBox?

I’m trying to get into setting up multiple hosts with NixOS and I figured I’d start with nixos-anywhere, cause I found a straightforward tutorial, but can I somehow also get an image that I can load into something like VirtualBox?

My first target is building a flake for an rpi with kodi, so I’d like to check that it’s working as expected in an emulator before I push it to the rpi.

Does nixos-anywhere support something like that?:wink:

I don’t think nixos-anywhere supports this specifically. You could set up a virtual machine with ssh keys and use it as a target for nixos-anywhere (you might need to do some configuration in VirtualBox in order to access the guest from the host). For testing a configuration, you can also use nixos-generators or the build-vm subcommand of nixos-rebuild (both of which are a lot less effort than manually setting up a VM).

No, disko does support it in the config.system.build.installTest attribute of the system configuration. It is implemented as a VM configuration that runs some self-checks on startup. All you have to do is to nix-build that attribute of your evaluated configuration, for example like this:

Note

This is a working sample to give you more than just the idea, but please apply Nix language best practices in reality.

# default.nix
let
  pkgs = import <nixpkgs> { };
  disko = builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz";
  # convenience wrapper to evaluate a configuration
  nixos = config: pkgs.nixos [ "${disko}/module.nix" config ];
  # a sample system configuration
  machine-config = { ... }: {
    boot.loader.systemd-boot.enable = true;
    disko.devices.disk.main = {
      device = "/dev/sda";
      content = {
        type = "gpt";
        partitions = {
          boot = {
            size = "1G";
            type = "EF00";
            content = {
              type = "filesystem";
              format = "vfat";
              mountpoint = "/boot";
            };
          };
          root = {
            content = {
              type = "filesystem";
              format = "ext4";
              mountpoint = "/";
            };
          };
        };
      };
    };
    system.stateVersion = "23.11";
  };
  # evaluated machine configuration
  machine = nixos machine-config;
in
{
  tests = machine.config.system.build.installTest;
}

To run the test:

nix-build -A tests --no-out-link

(Without --no-out-link it will produce an empty result symlink in the current directory, because the derivation, which runs the test when it’s built, doesn’t write anything to the store.)

What I also played around with recently is having two helper scripts in the development shell to further ease testing:

# default.nix
let
  # ...
  
  run-vm = pkgs.writeShellApplication {
    name = "run-vm";
    text = ''
      # make QEMU create the disk image in memory
      pushd "$(mktemp -d)" > /dev/null
      "$(nix-build ${toString ./.} -A machine.config.system.build.vm --no-out-link)"/bin/run-nixos-vm "$@"
      # clean up
      rm nixos.qcow2
      popd > /dev/null
    '';
  };
  run-tests = pkgs.writeShellApplication {
    name = "run-tests";
    text = ''
      nix-build ${toString ./.} -A tests --no-out-link "$@"
    '';
  };
in
{
  # ...
  inherit machine;
  shell = pkgs.mkShell {
    packages = [ run-vm run-tests ];
  };

Add a shell.nix for convenience, since now default.nix contains more than one derivation and otherwise you’d have to run nix-shell -A shell every time. Then you can run the configuration in a VM with run-vm, and run the tests with run-tests within your nix-shell.

Expanding the test suite to multiple machine configurations is left as an exercise for the reader.

1 Like

I’m on deep water, here, but at least I tried;)

[b0ef@ximian:~/nix-build/tst1]$ nix-build -A shell
these 3 derivations will be built:
  /nix/store/05y9s0x8nn8dlfn8fy8dmykkznqzaaci-run-tests.drv
  /nix/store/l0qg5q37kdmikyhlfxmzfvz77jyqykkh-run-vm.drv
  /nix/store/hrvh08g313wbvfr8ia26izq5hd13hxbl-nix-shell.drv
this path will be fetched (0.96 MiB download, 5.96 MiB unpacked):
  /nix/store/qc5c375far00lbcmhc55ph1bf0djqb3i-ShellCheck-0.9.0
copying path '/nix/store/qc5c375far00lbcmhc55ph1bf0djqb3i-ShellCheck-0.9.0' from 'https://cache.nixos.org'...
building '/nix/store/05y9s0x8nn8dlfn8fy8dmykkznqzaaci-run-tests.drv'...
building '/nix/store/l0qg5q37kdmikyhlfxmzfvz77jyqykkh-run-vm.drv'...

In /nix/store/a64cvvlf9d4qvspir5srpsb6p17dk507-run-vm/bin/run-vm line 6:
machine="$1"
^-----^ SC2034 (warning): machine appears unused. Verify use (or export if used externally).

For more information:
  https://www.shellcheck.net/wiki/SC2034 -- machine appears unused. Verify us...
error: builder for '/nix/store/l0qg5q37kdmikyhlfxmzfvz77jyqykkh-run-vm.drv' failed with exit code 1;
       last 7 log lines:
       >
       > In /nix/store/a64cvvlf9d4qvspir5srpsb6p17dk507-run-vm/bin/run-vm line 6:
       > machine="$1"
       > ^-----^ SC2034 (warning): machine appears unused. Verify use (or export if used externally).
       >
       > For more information:
       >   https://www.shellcheck.net/wiki/SC2034 -- machine appears unused. Verify us...
       For full logs, run 'nix log /nix/store/l0qg5q37kdmikyhlfxmzfvz77jyqykkh-run-vm.drv'.
error: 1 dependencies of derivation '/nix/store/hrvh08g313wbvfr8ia26izq5hd13hxbl-nix-shell.drv' failed to build

Ah yes, that was a remnant of a more involved setup. Edited it out, please try again.

Right, this got built;), but how do I actually run it. Where is the image?;). Sorry, I’m so confused.

If you nix-build (/t) it, it should be as easy as ./result/run-vm.

It’s not a directory here.

[b0ef@ximian:~/nix-build/tst1]$ ./result/run-vm
bash: ./result/run-vm: Not a directory
[b0ef@ximian:~/nix-build/tst1]$ file result
result: symbolic link to /nix/store/35b3p7rxngjl2ny77a32n6kqng356m4k-nix-shell

Start a nix-shell and therein run-vm ?

like nix-shell default.nix, then run-vm inside of that?

If you have a default.nix only then indeed nix-shell -A shell and inside of that run-vm and if you have a separate shell.nix for the shell attribute so to say then just nix-shell and inside of that run-vm.

You’re right;) Quite a step for mankind, or man, me, to figure that one out. Thanks a bunch;)

@573 thanks for the clarification, updated my post a bit so it hopefully is more on first reading.