diff --git a/nix/libvirtd-image.nix b/nix/libvirtd-image.nix index 108009435..66e85a84c 100644 --- a/nix/libvirtd-image.nix +++ b/nix/libvirtd-image.nix @@ -1,91 +1,214 @@ -{ system ? builtins.currentSystem, size ? "10" }: let - pkgs = import {}; - config = (import { - inherit system; - modules = [ { - fileSystems."/".device = "/dev/disk/by-label/nixos"; - - boot.loader.grub.version = 2; - boot.loader.grub.device = "/dev/sda"; - boot.loader.timeout = 0; - - services.openssh.enable = true; - services.openssh.startWhenNeeded = false; - services.openssh.extraConfig = "UseDNS no"; - } ]; - }).config; - -in pkgs.vmTools.runInLinuxVM ( - pkgs.runCommand "libvirtd-image" - { memSize = 768; - preVM = + makeconfig = { system, config }: + (import { + inherit system; + modules = [ config ]; + }).config; + +in rec { + base_image_config = { + fileSystems."/".device = "/dev/disk/by-label/nixos"; + + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/sda"; + boot.loader.timeout = 0; + }; + + + create_nixos_image = { + system ? builtins.currentSystem, + pkgs ? import {}, + size ? "10", + config ? base_image_config + }: + let + cfg = makeconfig { inherit system config; }; + + in pkgs.vmTools.runInLinuxVM ( + # TODO: Use when + # https://github.com/NixOS/nixpkgs/issues/20471 is fixed + pkgs.runCommand "libvirtd-image" + { memSize = 768; + preVM = + '' + mkdir $out + diskImage=$out/image + ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 $diskImage "${size}G" + mv closure xchg/ + ''; + postVM = + '' + mv $diskImage $out/disk.qcow2 + ''; + buildInputs = [ pkgs.utillinux pkgs.perl ]; + exportReferencesGraph = + [ "closure" cfg.system.build.toplevel ]; + } + '' + # Create a single / partition. + ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos + ${pkgs.parted}/sbin/parted /dev/vda -- mkpart primary ext2 1M -1s + . /sys/class/block/vda1/uevent + mknod /dev/vda1 b $MAJOR $MINOR + + # Create an empty filesystem and mount it. + ${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1 + ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1 + mkdir /mnt + mount /dev/vda1 /mnt + + # The initrd expects these directories to exist. + mkdir /mnt/dev /mnt/proc /mnt/sys + mount --bind /proc /mnt/proc + mount --bind /dev /mnt/dev + mount --bind /sys /mnt/sys + + # Copy all paths in the closure to the filesystem. + storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure) + + echo "filling Nix store..." + mkdir -p /mnt/nix/store + set -f + cp -prd $storePaths /mnt/nix/store/ + + mkdir -p /mnt/etc/nix + echo 'build-users-group = ' > /mnt/etc/nix/nix.conf + + # Register the paths in the Nix database. + printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \ + chroot /mnt ${cfg.nix.package.out}/bin/nix-store --load-db + + # Create the system profile to allow nixos-rebuild to work. + chroot /mnt ${cfg.nix.package.out}/bin/nix-env \ + -p /nix/var/nix/profiles/system --set ${cfg.system.build.toplevel} + + # `nixos-rebuild' requires an /etc/NIXOS. + mkdir -p /mnt/etc/nixos + touch /mnt/etc/NIXOS + + # `switch-to-configuration' requires a /bin/sh + mkdir -p /mnt/bin + ln -s ${cfg.system.build.binsh}/bin/sh /mnt/bin/sh + + # Generate the GRUB menu. + ln -s vda /dev/sda + chroot /mnt ${cfg.system.build.toplevel}/bin/switch-to-configuration boot + + umount /mnt/proc /mnt/dev /mnt/sys + umount /mnt + '' + ); + + + edit_image = { + pkgs ? import {}, + base_image, + cmd + }: + pkgs.vmTools.runInLinuxVM ( + pkgs.runCommand "libvirtd-edit-image" + { memSize = 768; + preVM = + '' + mkdir $out + diskImage=$out/image + ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 -b ${base_image}/disk.qcow2 $diskImage + ''; + buildInputs = [ pkgs.utillinux ]; + postVM = + '' + mv $diskImage $out/disk.qcow2 + ''; + } '' - mkdir $out - diskImage=$out/image - ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 $diskImage "${size}G" - mv closure xchg/ - ''; - postVM = + . /sys/class/block/vda1/uevent + mknod /dev/vda1 b $MAJOR $MINOR + mkdir /mnt + mount /dev/vda1 /mnt + + ${cmd} + + umount /mnt '' - mv $diskImage $out/disk.qcow2 - ''; - buildInputs = [ pkgs.utillinux pkgs.perl ]; - exportReferencesGraph = - [ "closure" config.system.build.toplevel ]; - } - '' - # Create a single / partition. - ${pkgs.parted}/sbin/parted /dev/vda mklabel msdos - ${pkgs.parted}/sbin/parted /dev/vda -- mkpart primary ext2 1M -1s - . /sys/class/block/vda1/uevent - mknod /dev/vda1 b $MAJOR $MINOR - - # Create an empty filesystem and mount it. - ${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1 - ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1 - mkdir /mnt - mount /dev/vda1 /mnt - - # The initrd expects these directories to exist. - mkdir /mnt/dev /mnt/proc /mnt/sys - mount --bind /proc /mnt/proc - mount --bind /dev /mnt/dev - mount --bind /sys /mnt/sys - - # Copy all paths in the closure to the filesystem. - storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure) - - echo "filling Nix store..." - mkdir -p /mnt/nix/store - set -f - cp -prd $storePaths /mnt/nix/store/ - - mkdir -p /mnt/etc/nix - echo 'build-users-group = ' > /mnt/etc/nix/nix.conf - - # Register the paths in the Nix database. - printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \ - chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db - - # Create the system profile to allow nixos-rebuild to work. - chroot /mnt ${config.nix.package.out}/bin/nix-env \ - -p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} - - # `nixos-rebuild' requires an /etc/NIXOS. - mkdir -p /mnt/etc/nixos - touch /mnt/etc/NIXOS - - # `switch-to-configuration' requires a /bin/sh - mkdir -p /mnt/bin - ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh - - # Generate the GRUB menu. - ln -s vda /dev/sda - chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot - - umount /mnt/proc /mnt/dev /mnt/sys - umount /mnt - '' -) + ); + + + deploy_in_nixos_image = { + system ? builtins.currentSystem, + pkgs ? import {}, + base_image, + config + }: + let + cfg = makeconfig { inherit system config; }; + + in pkgs.vmTools.runInLinuxVM ( + pkgs.runCommand "libvirtd-deploy-in-nixos-image" + { memSize = 768; + preVM = + '' + mkdir $out + diskImage=$out/image + ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 -b ${base_image}/disk.qcow2 $diskImage + mv closure xchg/ + ''; + postVM = + '' + mv $diskImage $out/disk.qcow2 + ''; + buildInputs = [ pkgs.utillinux pkgs.perl ]; + exportReferencesGraph = + [ "closure" cfg.system.build.toplevel ]; + } + '' + . /sys/class/block/vda1/uevent + mknod /dev/vda1 b $MAJOR $MINOR + mkdir /mnt + mount /dev/vda1 /mnt + + # The initrd expects these directories to exist. + mount --bind /proc /mnt/proc + mount --bind /dev /mnt/dev + mount --bind /sys /mnt/sys + + # Avoid "groups does not exist" warnings + mkdir -p /etc/nix + echo 'build-users-group = ' > /etc/nix/nix.conf + mkdir -p /mnt/etc/nix + echo 'build-users-group = ' > /mnt/etc/nix/nix.conf + + sourceStore='${pkgs.nix}/bin/nix-store' + targetStore='chroot /mnt ${cfg.nix.package.out}/bin/nix-store' + echo "filling Nix store..." + set -f + + # Copy missing paths in the closure to the target nix store. + storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure) + missing=$(NIX_DB_DIR=/mnt/nix/var/nix/db $sourceStore --check-validity --print-invalid $storePaths) + for path in $missing; do + ${pkgs.rsync}/bin/rsync -a $path /mnt/nix/store/ + done + + # Register the paths in the Nix database. + $targetStore --register-validity < /tmp/xchg/closure + + # TODO: Replace with the following + # when https://github.com/NixOS/nix/issues/1134 is fixed + #printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | $sourceStore --load-db + #$sourceStore --export $missing | $targetStore --import + + + # Create the system profile to allow nixos-rebuild to work. + chroot /mnt ${cfg.nix.package.out}/bin/nix-env \ + -p /nix/var/nix/profiles/system --set ${cfg.system.build.toplevel} + + # Generate the GRUB menu. + ln -s vda /dev/sda + chroot /mnt ${cfg.system.build.toplevel}/bin/switch-to-configuration boot + + umount /mnt/proc /mnt/dev /mnt/sys + umount /mnt + '' + ); +} diff --git a/nix/libvirtd.nix b/nix/libvirtd.nix index 28d6ed70c..166166926 100644 --- a/nix/libvirtd.nix +++ b/nix/libvirtd.nix @@ -3,35 +3,42 @@ with lib; let + libvirt_image_helpers = import ./libvirtd-image.nix; sz = toString config.deployment.libvirtd.baseImageSize; - base_image = import ./libvirtd-image.nix { size = sz; }; - the_key = builtins.getEnv "NIXOPS_LIBVIRTD_PUBKEY"; - ssh_image = pkgs.vmTools.runInLinuxVM ( - pkgs.runCommand "libvirtd-ssh-image" - { memSize = 768; - preVM = - '' - mkdir $out - diskImage=$out/image - ${pkgs.vmTools.qemu}/bin/qemu-img create -f qcow2 -b ${base_image}/disk.qcow2 $diskImage - ''; - buildInputs = [ pkgs.utillinux ]; - postVM = - '' - mv $diskImage $out/disk.qcow2 - ''; - } - '' - . /sys/class/block/vda1/uevent - mknod /dev/vda1 b $MAJOR $MINOR - mkdir /mnt - mount /dev/vda1 /mnt + ssh_pubkey = builtins.getEnv "NIXOPS_LIBVIRTD_PUBKEY"; + base_config = libvirt_image_helpers.base_image_config // { + services.openssh.enable = true; + services.openssh.startWhenNeeded = false; + services.openssh.extraConfig = "UseDNS no"; + }; + + base_image = libvirt_image_helpers.create_nixos_image { + size = sz; + config = base_config; + }; + + ssh_image = if config.deployment.libvirtd.boot_config == null then + libvirt_image_helpers.edit_image { + inherit pkgs base_image; + cmd = '' mkdir -p /mnt/etc/ssh/authorized_keys.d - echo '${the_key}' > /mnt/etc/ssh/authorized_keys.d/root - umount /mnt - '' - ); + echo '${ssh_pubkey}' > /mnt/etc/ssh/authorized_keys.d/root + ''; + } + + else + libvirt_image_helpers.deploy_in_nixos_image { + inherit pkgs base_image; + config.imports = [ + base_config + { users.users.root.openssh.authorizedKeys.keys = [ ssh_pubkey ]; } + config.deployment.libvirtd.boot_config + ]; + } + + ; + in { @@ -89,6 +96,22 @@ in ''; }; + deployment.libvirtd.boot_config = mkOption { + default = null; + example = { + networking = { + interfaces.enp0s2.ip4 = [ { address = "10.0.0.2"; prefixLength = 24; } ]; + defaultGateway = "10.0.0.1"; + }; + }; + type = types.nullOr types.attrs; + description = '' + NixOS configuration needed for the first image to boot and be reachable via ssh. + This will be used only during image bootstrapping. + Leave null to use default configuration, which uses DHCP. + ''; + }; + deployment.libvirtd.networks = mkOption { default = [ "default" ]; type = types.listOf types.str; @@ -110,22 +133,10 @@ in ###### implementation - config = mkIf (config.deployment.targetEnv == "libvirtd") { + config = mkIf (config.deployment.targetEnv == "libvirtd") (base_config // { deployment.libvirtd.baseImage = mkDefault ssh_image; - nixpkgs.system = mkOverride 900 "x86_64-linux"; - - fileSystems."/".device = "/dev/disk/by-label/nixos"; - - boot.loader.grub.version = 2; - boot.loader.grub.device = "/dev/sda"; - boot.loader.timeout = 0; - - services.openssh.enable = true; - services.openssh.startWhenNeeded = false; - services.openssh.extraConfig = "UseDNS no"; - deployment.hasFastConnection = true; -}; + }); }