Merge pull request #206839 from lheckemann/stateless-vms

nixos/qemu-vm: allow use without a disk image
This commit is contained in:
Linus Heckemann 2023-03-04 02:02:45 +01:00 committed by GitHub
commit 48269da315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -108,9 +108,9 @@ let
set -e set -e
NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}") NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
if ! test -e "$NIX_DISK_IMAGE"; then if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \ ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
${toString config.virtualisation.diskSize}M ${toString config.virtualisation.diskSize}M
fi fi
@ -346,7 +346,7 @@ in
virtualisation.diskImage = virtualisation.diskImage =
mkOption { mkOption {
type = types.str; type = types.nullOr types.str;
default = "./${config.system.name}.qcow2"; default = "./${config.system.name}.qcow2";
defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
description = description =
@ -354,6 +354,9 @@ in
Path to the disk image containing the root filesystem. Path to the disk image containing the root filesystem.
The image will be created on startup if it does not The image will be created on startup if it does not
exist. exist.
If null, a tmpfs will be used as the root filesystem and
the VM's state will not be persistent.
''; '';
}; };
@ -990,12 +993,12 @@ in
]; ];
virtualisation.qemu.drives = mkMerge [ virtualisation.qemu.drives = mkMerge [
[{ (mkIf (cfg.diskImage != null) [{
name = "root"; name = "root";
file = ''"$NIX_DISK_IMAGE"''; file = ''"$NIX_DISK_IMAGE"'';
driveExtraOpts.cache = "writeback"; driveExtraOpts.cache = "writeback";
driveExtraOpts.werror = "report"; driveExtraOpts.werror = "report";
}] }])
(mkIf cfg.useNixStoreImage [{ (mkIf cfg.useNixStoreImage [{
name = "nix-store"; name = "nix-store";
file = ''"$TMPDIR"/store.img''; file = ''"$TMPDIR"/store.img'';
@ -1018,20 +1021,21 @@ in
}) cfg.emptyDiskImages) }) cfg.emptyDiskImages)
]; ];
fileSystems = mkVMOverride cfg.fileSystems;
# Mount the host filesystem via 9P, and bind-mount the Nix store # Mount the host filesystem via 9P, and bind-mount the Nix store
# of the host into our own filesystem. We use mkVMOverride to # of the host into our own filesystem. We use mkVMOverride to
# allow this module to be applied to "normal" NixOS system # allow this module to be applied to "normal" NixOS system
# configuration, where the regular value for the `fileSystems' # configuration, where the regular value for the `fileSystems'
# attribute should be disregarded for the purpose of building a VM # attribute should be disregarded for the purpose of building a VM
# test image (since those filesystems don't exist in the VM). # test image (since those filesystems don't exist in the VM).
fileSystems = virtualisation.fileSystems = let
let
mkSharedDir = tag: share: mkSharedDir = tag: share:
{ {
name = name =
if tag == "nix-store" && cfg.writableStore if tag == "nix-store" && cfg.writableStore
then "/nix/.ro-store" then "/nix/.ro-store"
else share.target; else share.target;
value.device = tag; value.device = tag;
value.fsType = "9p"; value.fsType = "9p";
value.neededForBoot = true; value.neededForBoot = true;
@ -1039,44 +1043,42 @@ in
[ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ]
++ lib.optional (tag == "nix-store") "cache=loose"; ++ lib.optional (tag == "nix-store") "cache=loose";
}; };
in in lib.mkMerge [
mkVMOverride (cfg.fileSystems // (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
optionalAttrs cfg.useDefaultFilesystems { {
"/".device = cfg.bootDevice; "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then {
"/".fsType = "ext4"; device = "tmpfs";
"/".autoFormat = true; fsType = "tmpfs";
} // } else {
optionalAttrs config.boot.tmpOnTmpfs { device = cfg.bootDevice;
"/tmp" = { fsType = "ext4";
autoFormat = true;
});
"/tmp" = lib.mkIf config.boot.tmpOnTmpfs {
device = "tmpfs"; device = "tmpfs";
fsType = "tmpfs"; fsType = "tmpfs";
neededForBoot = true; neededForBoot = true;
# Sync with systemd's tmp.mount; # Sync with systemd's tmp.mount;
options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ]; options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
}; };
} // "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage {
optionalAttrs cfg.useNixStoreImage {
"/nix/${if cfg.writableStore then ".ro-store" else "store"}" = {
device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}"; device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
neededForBoot = true; neededForBoot = true;
options = [ "ro" ]; options = [ "ro" ];
}; };
} // "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) {
"/nix/.rw-store" = {
fsType = "tmpfs"; fsType = "tmpfs";
options = [ "mode=0755" ]; options = [ "mode=0755" ];
neededForBoot = true; neededForBoot = true;
}; };
} //
optionalAttrs cfg.useBootLoader {
# see note [Disk layout with `useBootLoader`] # see note [Disk layout with `useBootLoader`]
"/boot" = { "/boot" = lib.mkIf cfg.useBootLoader {
device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk` device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
fsType = "vfat"; fsType = "vfat";
noCheck = true; # fsck fails on a r/o filesystem noCheck = true; # fsck fails on a r/o filesystem
}; };
} // lib.mapAttrs' mkSharedDir cfg.sharedDirectories); }
];
boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) { boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
mounts = [{ mounts = [{