Merge pull request #713 from nix-community/interactiveVM

add interactive VMs
This commit is contained in:
lassulus 2024-08-21 16:12:23 +02:00 committed by GitHub
commit 6433571556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 206 additions and 63 deletions

View file

@ -41,9 +41,8 @@ recursive layouts.
Disko doesn't require installation: it can be run directly from nix-community Disko doesn't require installation: it can be run directly from nix-community
repository. The [Quickstart Guide](./docs/quickstart.md) documents how to run repository. The [Quickstart Guide](./docs/quickstart.md) documents how to run
Disko in its simplest form when installing NixOS. Alternatively, you can Disko in its simplest form when installing NixOS. Alternatively, you can also
also use the new [disko-install](./docs/disko-install.md) tool, which combines use the new [disko-install](./docs/disko-install.md) tool, which combines `disko` and `nixos-install` into one step.
the `disko` and `nixos-install` into one step.
For information on other use cases, including upgrading from an older version of For information on other use cases, including upgrading from an older version of
**disko**, using **disko** without NixOS and downloading the module, see the **disko**, using **disko** without NixOS and downloading the module, see the

17
docs/interactive-vm.md Normal file
View file

@ -0,0 +1,17 @@
# Running Interactive VMs with disko
disko now exports its own flavor of interactive VMs (similiar to config.system.build.vm).
Simply import the disko module and build the vm runner with:
```
nix build -L '.#nixosConfigurations.mymachine.config.system.build.vmWithDisko'
```
afterwards you can run the interactive VM with:
```
result/bin/disko-vm
```
extraConfig that is set in disko.tests.extraConfig is also applied to the interactive VMs.
imageSize of the VMs will be determined by the imageSize in the disk type in your disko config.
memorySize is set by disko.memSize

View file

@ -15,7 +15,8 @@ existing partitions. Dual booting with other operating systems is not supported.
### Step 1: Choose a Disk Configuration ### Step 1: Choose a Disk Configuration
Real-world templates are provided in this [repository](https://github.com/nix-community/disko-templates). Real-world templates are provided in this
[repository](https://github.com/nix-community/disko-templates).
More disk layouts for all filesystems can be also found in the More disk layouts for all filesystems can be also found in the
[example](https://github.com/nix-community/disko/tree/master/example) directory [example](https://github.com/nix-community/disko/tree/master/example) directory

View file

@ -16,6 +16,8 @@ let
# a version of makeDiskImage which runs outside of the store # a version of makeDiskImage which runs outside of the store
makeDiskImagesScript = args: (import ./make-disk-image.nix ({ inherit diskoLib; } // args)).impure; makeDiskImagesScript = args: (import ./make-disk-image.nix ({ inherit diskoLib; } // args)).impure;
makeVMRunner = args: (import ./interactive-vm.nix ({ inherit diskoLib; } // args)).pure;
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

88
lib/interactive-vm.nix Normal file
View file

@ -0,0 +1,88 @@
{ nixosConfig
, diskoLib
, pkgs ? nixosConfig.pkgs
, name ? "${nixosConfig.config.networking.hostName}-disko-images"
, extraConfig ? { }
}:
let
lib = pkgs.lib;
vm_disko = (diskoLib.testLib.prepareDiskoConfig nixosConfig.config diskoLib.testLib.devices).disko;
cfg_ = (lib.evalModules {
modules = lib.singleton {
# _file = toString input;
imports = lib.singleton { disko.devices = vm_disko.devices; };
options = {
disko.devices = lib.mkOption {
type = diskoLib.toplevel;
};
disko.testMode = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
};
}).config;
disks = lib.attrValues cfg_.disko.devices.disk;
diskoImages = diskoLib.makeDiskImages {
nixosConfig = nixosConfig;
copyNixStore = false;
extraConfig = {
disko.devices = cfg_.disko.devices;
};
testMode = true;
};
rootDisk = {
name = "root";
file = ''"$tmp"/${(builtins.head disks).name}.qcow2'';
driveExtraOpts.cache = "writeback";
driveExtraOpts.werror = "report";
deviceExtraOpts.bootindex = "1";
deviceExtraOpts.serial = "root";
};
otherDisks = map
(disk: {
name = disk.name;
file = ''"$tmp"/${disk.name}.qcow2'';
driveExtraOpts.werror = "report";
})
(builtins.tail disks);
vm = (nixosConfig.extendModules {
modules = [
({ modulesPath, ... }: {
imports = [
(modulesPath + "/virtualisation/qemu-vm.nix")
];
})
{
virtualisation.useEFIBoot = nixosConfig.config.disko.tests.efi;
virtualisation.memorySize = nixosConfig.config.disko.memSize;
virtualisation.useDefaultFilesystems = false;
virtualisation.diskImage = null;
virtualisation.qemu.drives = [ rootDisk ] ++ otherDisks;
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
boot.zfs.forceImportAll = true;
}
{
# generated from disko config
virtualisation.fileSystems = cfg_.disko.devices._config.fileSystems;
boot = cfg_.disko.devices._config.boot or { };
swapDevices = cfg_.disko.devices._config.swapDevices or [ ];
}
nixosConfig.config.disko.tests.extraConfig
];
}).config.system.build.vm;
in
{
pure = pkgs.writeDashBin "disko-vm" ''
set -efux
export tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
${lib.concatMapStringsSep "\n" (disk: ''
${pkgs.qemu}/bin/qemu-img create -f qcow2 \
-b ${diskoImages}/${disk.name}.raw \
-F raw "$tmp"/${disk.name}.qcow2
'') disks}
set +f
${vm}/bin/run-*-vm
'';
}

View file

@ -5,6 +5,9 @@
, name ? "${nixosConfig.config.networking.hostName}-disko-images" , name ? "${nixosConfig.config.networking.hostName}-disko-images"
, extraPostVM ? nixosConfig.config.disko.extraPostVM , extraPostVM ? nixosConfig.config.disko.extraPostVM
, checked ? false , checked ? false
, copyNixStore ? true
, testMode ? false
, extraConfig ? { }
}: }:
let let
vmTools = pkgs.vmTools.override { vmTools = pkgs.vmTools.override {
@ -16,10 +19,14 @@ let
}; };
cleanedConfig = diskoLib.testLib.prepareDiskoConfig nixosConfig.config diskoLib.testLib.devices; cleanedConfig = diskoLib.testLib.prepareDiskoConfig nixosConfig.config diskoLib.testLib.devices;
systemToInstall = nixosConfig.extendModules { systemToInstall = nixosConfig.extendModules {
modules = [{ modules = [
extraConfig
{
disko.testMode = true;
disko.devices = lib.mkForce cleanedConfig.disko.devices; disko.devices = lib.mkForce cleanedConfig.disko.devices;
boot.loader.grub.devices = lib.mkForce cleanedConfig.boot.loader.grub.devices; boot.loader.grub.devices = lib.mkForce cleanedConfig.boot.loader.grub.devices;
}]; }
];
}; };
dependencies = with pkgs; [ dependencies = with pkgs; [
bash bash
@ -49,6 +56,7 @@ let
rootPaths = [ systemToInstall.config.system.build.toplevel ]; rootPaths = [ systemToInstall.config.system.build.toplevel ];
}; };
partitioner = '' partitioner = ''
set -efux
# running udev, stolen from stage-1.sh # running udev, stolen from stage-1.sh
echo "running udev..." echo "running udev..."
ln -sfn /proc/self/fd /dev/fd ln -sfn /proc/self/fd /dev/fd
@ -64,10 +72,13 @@ let
udevadm trigger --action=add udevadm trigger --action=add
udevadm settle udevadm settle
${lib.optionalString testMode ''
export IN_DISKO_TEST=1
''}
${systemToInstall.config.system.build.diskoScript} ${systemToInstall.config.system.build.diskoScript}
''; '';
installer = '' installer = lib.optionalString 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"

View file

@ -45,7 +45,8 @@ in
}; };
priority = lib.mkOption { priority = lib.mkOption {
type = lib.types.int; type = lib.types.int;
default = if partition.config.size or "" == "100%" then default =
if partition.config.size or "" == "100%" then
9001 9001
else if partition.config.type == "EF02" then else if partition.config.type == "EF02" then
# Boot partition should be created first, because some BIOS implementations require it. # Boot partition should be created first, because some BIOS implementations require it.

View file

@ -114,19 +114,23 @@ in
default = '' default = ''
if ! blkid "${config.device}" >/dev/null || ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then if ! blkid "${config.device}" >/dev/null || ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then
${lib.optionalString config.askPassword '' ${lib.optionalString config.askPassword ''
set +x
askPassword() { askPassword() {
if [ -z ''${IN_DISKO_TEST+x} ]; then
set +x
echo "Enter password for ${config.device}: " echo "Enter password for ${config.device}: "
IFS= read -r -s password IFS= read -r -s password
echo "Enter password for ${config.device} again to be safe: " echo "Enter password for ${config.device} again to be safe: "
IFS= read -r -s password_check IFS= read -r -s password_check
export password export password
[ "$password" = "$password_check" ] [ "$password" = "$password_check" ]
set -x
else
export password=disko
fi
} }
until askPassword; do until askPassword; do
echo "Passwords did not match, please try again." echo "Passwords did not match, please try again."
done done
set -x
''} ''}
cryptsetup -q luksFormat ${config.device} ${toString config.extraFormatArgs} ${keyFileArgs} cryptsetup -q luksFormat ${config.device} ${toString config.extraFormatArgs} ${keyFileArgs}
${cryptsetupOpen} --persistent ${cryptsetupOpen} --persistent
@ -147,11 +151,15 @@ in
dev = '' dev = ''
if ! cryptsetup status ${config.name} >/dev/null 2>/dev/null; then if ! cryptsetup status ${config.name} >/dev/null 2>/dev/null; then
${lib.optionalString config.askPassword '' ${lib.optionalString config.askPassword ''
if [ -z ''${IN_DISKO_TEST+x} ]; then
set +x set +x
echo "Enter password for ${config.device}" echo "Enter password for ${config.device}"
IFS= read -r -s password IFS= read -r -s password
export password export password
set -x set -x
else
export password=disko
fi
''} ''}
${cryptsetupOpen} ${cryptsetupOpen}
fi fi

View file

@ -125,7 +125,7 @@ in
_config = lib.mkOption { _config = lib.mkOption {
internal = true; internal = true;
readOnly = true; readOnly = true;
default = [ { boot.initrd.kernelModules = kernelModules; } ] ++ default = [{ boot.initrd.kernelModules = kernelModules; }] ++
map map
(lv: [ (lv: [
(lib.optional (lv.content != null) lv.content._config) (lib.optional (lv.content != null) lv.content._config)

View file

@ -59,7 +59,8 @@
# important to prevent accidental shadowing of mount points # important to prevent accidental shadowing of mount points
# since (create order != mount order) # since (create order != mount order)
# -p creates parents automatically # -p creates parents automatically
default = let default =
let
createOptions = (lib.optionalAttrs (config.mountpoint != null) { mountpoint = config.mountpoint; }) // config.options; createOptions = (lib.optionalAttrs (config.mountpoint != null) { mountpoint = config.mountpoint; }) // config.options;
# All options defined as PROP_ONETIME or PROP_ONETIME_DEFAULT in https://github.com/openzfs/zfs/blob/master/module/zcommon/zfs_prop.c # All options defined as PROP_ONETIME or PROP_ONETIME_DEFAULT in https://github.com/openzfs/zfs/blob/master/module/zcommon/zfs_prop.c
onetimeProperties = [ onetimeProperties = [
@ -74,7 +75,8 @@
]; ];
updateOptions = builtins.removeAttrs config.options onetimeProperties; updateOptions = builtins.removeAttrs config.options onetimeProperties;
mountpoint = config.options.mountpoint or config.mountpoint; mountpoint = config.options.mountpoint or config.mountpoint;
in '' in
''
if ! zfs get type ${config._name} >/dev/null 2>&1; then if ! zfs get type ${config._name} >/dev/null 2>&1; then
zfs create -up ${config._name} \ zfs create -up ${config._name} \
${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-o ${n}=${v}") (createOptions))} ${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-o ${n}=${v}") (createOptions))}

View file

@ -99,6 +99,15 @@ in
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
}; };
testMode = lib.mkOption {
internal = true;
description = ''
this is true if the system is being run in test mode.
like a vm test or an interactive vm
'';
type = lib.types.bool;
default = false;
};
tests = { tests = {
efi = lib.mkOption { efi = lib.mkOption {
description = '' description = ''
@ -151,6 +160,11 @@ in
extraSystemConfig = cfg.tests.extraConfig; extraSystemConfig = cfg.tests.extraConfig;
extraTestScript = cfg.tests.extraChecks; extraTestScript = cfg.tests.extraChecks;
}; };
vmWithDisko = diskoLib.makeVMRunner {
inherit pkgs;
nixosConfig = args;
};
}; };