make-disk-image: convert into NixOS module

As `makeDiskImages` always requires a NixOS configuration, we can
simplify the code by convering it into a NixOS module. Then we can make
it responsible for populating `system.build.diskoImages` and
`system.build.diskoImagesScript`.
This commit is contained in:
Michael Hoang 2024-09-14 16:20:53 +10:00
parent 4ef99d8ec4
commit cc4d4a4b91
7 changed files with 193 additions and 165 deletions

View file

@ -9,13 +9,6 @@ with builtins;
let let
outputs = import ../default.nix { inherit lib diskoLib; }; outputs = import ../default.nix { inherit lib diskoLib; };
diskoLib = { diskoLib = {
# like make-disk-image.nix from nixpkgs, but with disko config
makeDiskImages = args: (import ./make-disk-image.nix ({ inherit diskoLib; } // args)).pure;
# a version of makeDiskImage which runs outside of the store
makeDiskImagesScript = args: (import ./make-disk-image.nix ({ inherit diskoLib; } // args)).impure;
testLib = import ./tests.nix { inherit lib makeTest eval-config; }; testLib = import ./tests.nix { inherit lib makeTest eval-config; };
# like lib.types.oneOf but instead of a list takes an attrset # like lib.types.oneOf but instead of a list takes an attrset
# uses the field "type" to find the correct type in the attrset # uses the field "type" to find the correct type in the attrset

View file

@ -1,6 +1,4 @@
# We need to specify extendModules here to ensure that it is available { diskoLib, modulesPath, config, pkgs, lib, ... }:
# in args for makeDiskImages
{ diskoLib, modulesPath, config, pkgs, lib, extendModules, ... }@args:
let let
vm_disko = (diskoLib.testLib.prepareDiskoConfig config diskoLib.testLib.devices).disko; vm_disko = (diskoLib.testLib.prepareDiskoConfig config diskoLib.testLib.devices).disko;
@ -20,15 +18,6 @@ let
}; };
}).config; }).config;
disks = lib.attrValues cfg_.disko.devices.disk; disks = lib.attrValues cfg_.disko.devices.disk;
diskoImages = diskoLib.makeDiskImages {
nixosConfig = args;
copyNixStore = false;
extraConfig = {
disko.devices = cfg_.disko.devices;
};
testMode = true;
imageFormat = "qcow2";
};
rootDisk = { rootDisk = {
name = "root"; name = "root";
file = ''"$tmp"/${(builtins.head disks).name}.qcow2''; file = ''"$tmp"/${(builtins.head disks).name}.qcow2'';
@ -58,6 +47,14 @@ in
diskoBasedConfiguration diskoBasedConfiguration
]; ];
disko.testMode = true;
disko.imageBuilder.copyNixStore = false;
disko.imageBuilder.extraConfig = {
disko.devices = cfg_.disko.devices;
};
disko.imageBuilder.imageFormat = "qcow2";
virtualisation.useEFIBoot = config.disko.tests.efi; virtualisation.useEFIBoot = config.disko.tests.efi;
virtualisation.memorySize = config.disko.memSize; virtualisation.memorySize = config.disko.memSize;
virtualisation.useDefaultFilesystems = false; virtualisation.useDefaultFilesystems = false;
@ -72,7 +69,7 @@ in
trap 'rm -rf "$tmp"' EXIT trap 'rm -rf "$tmp"' EXIT
${lib.concatMapStringsSep "\n" (disk: '' ${lib.concatMapStringsSep "\n" (disk: ''
${pkgs.qemu}/bin/qemu-img create -f qcow2 \ ${pkgs.qemu}/bin/qemu-img create -f qcow2 \
-b ${diskoImages}/${disk.name}.qcow2 \ -b ${config.system.build.diskoImages}/${disk.name}.qcow2 \
-F qcow2 "$tmp"/${disk.name}.qcow2 -F qcow2 "$tmp"/${disk.name}.qcow2
'') disks} '') disks}
set +f set +f

View file

@ -1,30 +1,29 @@
{ nixosConfig { config
, diskoLib , diskoLib
, pkgs ? nixosConfig.config.disko.imageBuilderPkgs , lib
, lib ? pkgs.lib , extendModules
, name ? "${nixosConfig.config.networking.hostName}-disko-images" , ...
, extraPostVM ? nixosConfig.config.disko.extraPostVM
, checked ? false
, copyNixStore ? true
, testMode ? false
, extraConfig ? { }
, imageFormat ? "raw"
}: }:
let let
configSupportsZfs = nixosConfig.config.boot.supportedFilesystems.zfs or false; diskoCfg = config.disko;
cfg = diskoCfg.imageBuilder;
inherit (cfg) pkgs imageFormat;
checked = diskoCfg.checkScripts;
configSupportsZfs = config.boot.supportedFilesystems.zfs or false;
vmTools = pkgs.vmTools.override { vmTools = pkgs.vmTools.override {
rootModules = [ "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ] rootModules = [ "9p" "9pnet_virtio" "virtio_pci" "virtio_blk" ]
++ (lib.optional configSupportsZfs "zfs") ++ (lib.optional configSupportsZfs "zfs")
++ nixosConfig.config.disko.extraRootModules; ++ cfg.extraRootModules;
customQemu = nixosConfig.config.disko.imageBuilderQemu; customQemu = cfg.qemu;
kernel = pkgs.aggregateModules kernel = pkgs.aggregateModules
(with nixosConfig.config.disko.imageBuilderKernelPackages; [ kernel ] (with cfg.kernelPackages; [ kernel ]
++ lib.optional (lib.elem "zfs" nixosConfig.config.disko.extraRootModules || configSupportsZfs) zfs); ++ lib.optional (lib.elem "zfs" cfg.extraRootModules || configSupportsZfs) zfs);
}; };
cleanedConfig = diskoLib.testLib.prepareDiskoConfig nixosConfig.config diskoLib.testLib.devices; cleanedConfig = diskoLib.testLib.prepareDiskoConfig config diskoLib.testLib.devices;
systemToInstall = nixosConfig.extendModules { systemToInstall = extendModules {
modules = [ modules = [
extraConfig cfg.extraConfig
{ {
disko.testMode = true; disko.testMode = true;
disko.devices = lib.mkForce cleanedConfig.disko.devices; disko.devices = lib.mkForce cleanedConfig.disko.devices;
@ -41,9 +40,9 @@ let
nix nix
util-linux util-linux
findutils findutils
] ++ nixosConfig.config.disko.extraDependencies; ] ++ cfg.extraDependencies;
preVM = '' preVM = ''
${lib.concatMapStringsSep "\n" (disk: "${pkgs.qemu}/bin/qemu-img create -f ${imageFormat} ${disk.name}.${imageFormat} ${disk.imageSize}") (lib.attrValues nixosConfig.config.disko.devices.disk)} ${lib.concatMapStringsSep "\n" (disk: "${pkgs.qemu}/bin/qemu-img create -f ${imageFormat} ${disk.name}.${imageFormat} ${disk.imageSize}") (lib.attrValues diskoCfg.devices.disk)}
# This makes disko work, when canTouchEfiVariables is set to true. # This makes disko work, when canTouchEfiVariables is set to true.
# Technically these boot entries will no be persisted this way, but # Technically these boot entries will no be persisted this way, but
# in most cases this is OK, because we can rely on the standard location for UEFI executables. # in most cases this is OK, because we can rely on the standard location for UEFI executables.
@ -52,8 +51,8 @@ let
postVM = '' postVM = ''
# shellcheck disable=SC2154 # shellcheck disable=SC2154
mkdir -p "$out" mkdir -p "$out"
${lib.concatMapStringsSep "\n" (disk: "mv ${disk.name}.${imageFormat} \"$out\"/${disk.name}.${imageFormat}") (lib.attrValues nixosConfig.config.disko.devices.disk)} ${lib.concatMapStringsSep "\n" (disk: "mv ${disk.name}.${imageFormat} \"$out\"/${disk.name}.${imageFormat}") (lib.attrValues diskoCfg.devices.disk)}
${extraPostVM} ${cfg.extraPostVM}
''; '';
closureInfo = pkgs.closureInfo { closureInfo = pkgs.closureInfo {
@ -76,13 +75,13 @@ let
udevadm trigger --action=add udevadm trigger --action=add
udevadm settle udevadm settle
${lib.optionalString testMode '' ${lib.optionalString diskoCfg.testMode ''
export IN_DISKO_TEST=1 export IN_DISKO_TEST=1
''} ''}
${systemToInstall.config.system.build.diskoScript} ${systemToInstall.config.system.build.diskoScript}
''; '';
installer = lib.optionalString copyNixStore '' installer = lib.optionalString cfg.copyNixStore ''
# populate nix db, so nixos-install doesn't complain # populate nix db, so nixos-install doesn't complain
export NIX_STATE_DIR=${systemToInstall.config.disko.rootMountPoint}/nix/var/nix export NIX_STATE_DIR=${systemToInstall.config.disko.rootMountPoint}/nix/var/nix
nix-store --load-db < "${closureInfo}/registration" nix-store --load-db < "${closureInfo}/registration"
@ -102,17 +101,18 @@ let
(disk: (disk:
"-drive file=${disk.name}.${imageFormat},if=virtio,cache=unsafe,werror=report,format=${imageFormat}" "-drive file=${disk.name}.${imageFormat},if=virtio,cache=unsafe,werror=report,format=${imageFormat}"
) )
(lib.attrValues nixosConfig.config.disko.devices.disk)); (lib.attrValues diskoCfg.devices.disk));
in in
{ {
pure = vmTools.runInLinuxVM (pkgs.runCommand name system.build.diskoImages = vmTools.runInLinuxVM (pkgs.runCommand cfg.name
{ {
buildInputs = dependencies; buildInputs = dependencies;
inherit preVM postVM QEMU_OPTS; inherit preVM postVM QEMU_OPTS;
memSize = nixosConfig.config.disko.memSize; inherit (diskoCfg) memSize;
} }
(partitioner + installer)); (partitioner + installer));
impure = diskoLib.writeCheckedBash { inherit checked pkgs; } name ''
system.build.diskoImagesScript = diskoLib.writeCheckedBash { inherit checked pkgs; } cfg.name ''
set -efu set -efu
export PATH=${lib.makeBinPath dependencies} export PATH=${lib.makeBinPath dependencies}
showUsage() { showUsage() {

View file

@ -19,7 +19,7 @@
imageSize = lib.mkOption { imageSize = lib.mkOption {
type = lib.types.strMatching "[0-9]+[KMGTP]?"; type = lib.types.strMatching "[0-9]+[KMGTP]?";
description = '' description = ''
size of the image if the makeDiskImages function from diksoLib is used. size of the image when disko images are created
is used as an argument to "qemu-img create ..." is used as an argument to "qemu-img create ..."
''; '';
default = "2G"; default = "2G";

View file

@ -1,72 +1,108 @@
{ config, lib, pkgs, extendModules, ... }@args: { config, lib, pkgs, extendModules, diskoLib, ... }:
let let
diskoLib = import ./lib {
inherit lib;
rootMountPoint = config.disko.rootMountPoint;
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix");
};
cfg = config.disko; cfg = config.disko;
vmVariantWithDisko = extendModules { vmVariantWithDisko = extendModules {
modules = [ modules = [
./lib/interactive-vm.nix ./lib/interactive-vm.nix
{ _module.args = { inherit diskoLib; }; }
config.disko.tests.extraConfig config.disko.tests.extraConfig
]; ];
}; };
in in
{ {
imports = [ ./lib/make-disk-image.nix ];
options.disko = { options.disko = {
imageBuilderQemu = lib.mkOption { imageBuilder = {
type = lib.types.nullOr lib.types.str; qemu = lib.mkOption {
description = '' type = lib.types.nullOr lib.types.str;
the qemu emulator string used when building disk images via make-disk-image.nix. description = ''
Useful when using binfmt on your build host, and wanting to build disk the qemu emulator string used when building disk images via make-disk-image.nix.
images for a foreign architecture Useful when using binfmt on your build host, and wanting to build disk
''; images for a foreign architecture
default = null; '';
example = lib.literalExpression "\${pkgs.qemu_kvm}/bin/qemu-system-aarch64"; default = null;
}; example = lib.literalExpression "\${pkgs.qemu_kvm}/bin/qemu-system-aarch64";
imageBuilderPkgs = lib.mkOption { };
type = lib.types.attrs;
description = '' pkgs = lib.mkOption {
the pkgs instance used when building disk images via make-disk-image.nix. type = lib.types.attrs;
Useful when the config's kernel won't boot in the image-builder. description = ''
''; the pkgs instance used when building disk images via make-disk-image.nix.
default = pkgs; Useful when the config's kernel won't boot in the image-builder.
defaultText = lib.literalExpression "pkgs"; '';
example = lib.literalExpression "pkgs"; default = pkgs;
}; defaultText = lib.literalExpression "pkgs";
imageBuilderKernelPackages = lib.mkOption { example = lib.literalExpression "pkgs";
type = lib.types.attrs; };
description = ''
the kernel used when building disk images via make-disk-image.nix. kernelPackages = lib.mkOption {
Useful when the config's kernel won't boot in the image-builder. type = lib.types.attrs;
''; description = ''
default = config.boot.kernelPackages; the kernel used when building disk images via make-disk-image.nix.
defaultText = lib.literalExpression "config.boot.kernelPackages"; Useful when the config's kernel won't boot in the image-builder.
example = lib.literalExpression "pkgs.linuxPackages_testing"; '';
}; default = config.boot.kernelPackages;
extraRootModules = lib.mkOption { defaultText = lib.literalExpression "config.boot.kernelPackages";
type = lib.types.listOf lib.types.str; example = lib.literalExpression "pkgs.linuxPackages_testing";
description = '' };
extra kernel modules to pass to the vmTools.runCommand invocation in the make-disk-image.nix builder
''; extraRootModules = lib.mkOption {
default = [ ]; type = lib.types.listOf lib.types.str;
example = [ "bcachefs" ]; description = ''
}; extra kernel modules to pass to the vmTools.runCommand invocation in the make-disk-image.nix builder
extraPostVM = lib.mkOption { '';
type = lib.types.str; default = [ ];
description = '' example = [ "bcachefs" ];
extra shell code to execute once the disk image(s) have been succesfully created and moved to $out };
'';
default = ""; extraPostVM = lib.mkOption {
example = lib.literalExpression '' type = lib.types.str;
''${pkgs.zstd}/bin/zstd --compress $out/*raw description = ''
rm $out/*raw extra shell code to execute once the disk image(s) have been succesfully created and moved to $out
''; '';
default = "";
example = lib.literalExpression ''
''${pkgs.zstd}/bin/zstd --compress $out/*raw
rm $out/*raw
'';
};
extraDependencies = lib.mkOption {
type = lib.types.listOf lib.types.package;
description = ''
list of extra packages to make available in the make-disk-image.nix VM builder, an example might be f2fs-tools
'';
default = [ ];
};
name = lib.mkOption {
type = lib.types.str;
description = "name for the disk images";
default = "${config.networking.hostName}-disko-images";
};
copyNixStore = lib.mkOption {
type = lib.types.bool;
description = "whether to copy the nix store into the disk images we just created";
default = true;
};
extraConfig = lib.mkOption {
description = ''
Extra NixOS config for your test. Can be used to specify a different luks key for tests.
A dummy key is in /tmp/secret.key
'';
default = { };
};
imageFormat = lib.mkOption {
type = lib.types.enum [ "raw" "qcow2" ];
description = "QEMU image format to use for the disk images";
default = "raw";
};
}; };
memSize = lib.mkOption { memSize = lib.mkOption {
type = lib.types.int; type = lib.types.int;
description = '' description = ''
@ -74,23 +110,19 @@ in
''; '';
default = 1024; default = 1024;
}; };
devices = lib.mkOption { devices = lib.mkOption {
type = diskoLib.toplevel; type = diskoLib.toplevel;
default = { }; default = { };
description = "The devices to set up"; description = "The devices to set up";
}; };
extraDependencies = lib.mkOption {
type = lib.types.listOf lib.types.package;
description = ''
list of extra packages to make available in the make-disk-image.nix VM builder, an example might be f2fs-tools
'';
default = [ ];
};
rootMountPoint = lib.mkOption { rootMountPoint = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "/mnt"; default = "/mnt";
description = "Where the device tree should be mounted by the mountScript"; description = "Where the device tree should be mounted by the mountScript";
}; };
enableConfig = lib.mkOption { enableConfig = lib.mkOption {
description = '' description = ''
configure nixos with the specified devices configure nixos with the specified devices
@ -100,6 +132,7 @@ in
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
}; };
checkScripts = lib.mkOption { checkScripts = lib.mkOption {
description = '' description = ''
Whether to run shellcheck on script outputs Whether to run shellcheck on script outputs
@ -107,6 +140,7 @@ in
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
}; };
testMode = lib.mkOption { testMode = lib.mkOption {
internal = true; internal = true;
description = '' description = ''
@ -116,6 +150,7 @@ in
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
}; };
tests = { tests = {
efi = lib.mkOption { efi = lib.mkOption {
description = '' description = ''
@ -126,6 +161,7 @@ in
defaultText = "config.boot.loader.systemd-boot.enable || config.boot.loader.grub.efiSupport"; defaultText = "config.boot.loader.systemd-boot.enable || config.boot.loader.grub.efiSupport";
default = config.boot.loader.systemd-boot.enable || config.boot.loader.grub.efiSupport; default = config.boot.loader.systemd-boot.enable || config.boot.loader.grub.efiSupport;
}; };
extraChecks = lib.mkOption { extraChecks = lib.mkOption {
description = '' description = ''
extra checks to run in the `system.build.installTest`. extra checks to run in the `system.build.installTest`.
@ -136,6 +172,7 @@ in
machine.succeed("test -e /var/secrets/my.secret") machine.succeed("test -e /var/secrets/my.secret")
''; '';
}; };
extraConfig = lib.mkOption { extraConfig = lib.mkOption {
description = '' description = ''
Extra NixOS config for your test. Can be used to specify a different luks key for tests. Extra NixOS config for your test. Can be used to specify a different luks key for tests.
@ -155,38 +192,41 @@ in
visible = "shallow"; visible = "shallow";
}; };
config = lib.mkIf (cfg.devices.disk != { }) { config = lib.mkMerge [
system.build = (cfg.devices._scripts { inherit pkgs; checked = cfg.checkScripts; }) // { (lib.mkIf (cfg.devices.disk != { }) {
system.build = (cfg.devices._scripts { inherit pkgs; checked = cfg.checkScripts; }) // {
# we keep these old outputs for compatibility # we keep these old outputs for compatibility
disko = builtins.trace "the .disko output is deprecated, please use .diskoScript instead" (cfg.devices._scripts { inherit pkgs; }).diskoScript; disko = builtins.trace "the .disko output is deprecated, please use .diskoScript instead" (cfg.devices._scripts { inherit pkgs; }).diskoScript;
diskoNoDeps = builtins.trace "the .diskoNoDeps output is deprecated, please use .diskoScriptNoDeps instead" (cfg.devices._scripts { inherit pkgs; }).diskoScriptNoDeps; diskoNoDeps = builtins.trace "the .diskoNoDeps output is deprecated, please use .diskoScriptNoDeps instead" (cfg.devices._scripts { inherit pkgs; }).diskoScriptNoDeps;
diskoImages = diskoLib.makeDiskImages { installTest = diskoLib.testLib.makeDiskoTest {
nixosConfig = args; inherit extendModules pkgs;
}; name = "${config.networking.hostName}-disko";
diskoImagesScript = diskoLib.makeDiskImagesScript { disko-config = builtins.removeAttrs config [ "_module" ];
nixosConfig = args; testMode = "direct";
efi = cfg.tests.efi;
extraSystemConfig = cfg.tests.extraConfig;
extraTestScript = cfg.tests.extraChecks;
};
vmWithDisko = lib.mkDefault config.virtualisation.vmVariantWithDisko.system.build.vmWithDisko;
}; };
installTest = diskoLib.testLib.makeDiskoTest {
inherit extendModules pkgs; # we need to specify the keys here, so we don't get an infinite recursion error
name = "${config.networking.hostName}-disko"; # Remember to add config keys here if they are added to types
disko-config = builtins.removeAttrs config [ "_module" ]; fileSystems = lib.mkIf cfg.enableConfig cfg.devices._config.fileSystems or { };
testMode = "direct"; boot = lib.mkIf cfg.enableConfig cfg.devices._config.boot or { };
efi = cfg.tests.efi; swapDevices = lib.mkIf cfg.enableConfig cfg.devices._config.swapDevices or [ ];
extraSystemConfig = cfg.tests.extraConfig; })
extraTestScript = cfg.tests.extraChecks; {
_module.args.diskoLib = import ./lib {
inherit lib;
rootMountPoint = config.disko.rootMountPoint;
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix");
}; };
}
vmWithDisko = lib.mkDefault config.virtualisation.vmVariantWithDisko.system.build.vmWithDisko; ];
};
# we need to specify the keys here, so we don't get an infinite recursion error
# Remember to add config keys here if they are added to types
fileSystems = lib.mkIf cfg.enableConfig cfg.devices._config.fileSystems or { };
boot = lib.mkIf cfg.enableConfig cfg.devices._config.boot or { };
swapDevices = lib.mkIf cfg.enableConfig cfg.devices._config.swapDevices or [ ];
};
} }

View file

@ -1,15 +1,13 @@
{ pkgs ? import <nixpkgs> { } { pkgs ? import <nixpkgs> { }
, diskoLib ? pkgs.callPackage ../lib { } , ...
}: }:
diskoLib.makeDiskImagesScript {
nixosConfig = pkgs.nixos [
../module.nix
../example/simple-efi.nix
({ config, ... }: {
documentation.enable = false;
system.stateVersion = config.system.nixos.version;
})
];
checked = true;
}
(pkgs.nixos [
../module.nix
../example/simple-efi.nix
({ config, ... }: {
documentation.enable = false;
system.stateVersion = config.system.nixos.version;
disko.checkScripts = true;
})
]).config.system.build.diskoImagesScript

View file

@ -1,14 +1,14 @@
{ pkgs ? import <nixpkgs> { } { pkgs ? import <nixpkgs> { }
, diskoLib ? pkgs.callPackage ../lib { } , ...
}: }:
diskoLib.makeDiskImages {
nixosConfig = pkgs.nixos [ (pkgs.nixos [
../module.nix ../module.nix
../example/simple-efi.nix ../example/simple-efi.nix
({ config, ... }: { ({ config, ... }: {
documentation.enable = false; documentation.enable = false;
system.stateVersion = config.system.nixos.version; system.stateVersion = config.system.nixos.version;
disko.memSize = 2048; disko.memSize = 2048;
}) disko.checkScripts = true;
]; })
} ]).config.system.build.diskoImages