diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix index b8a7526120aa..1b3724bfc170 100644 --- a/nixos/maintainers/scripts/ec2/amazon-image.nix +++ b/nixos/maintainers/scripts/ec2/amazon-image.nix @@ -1,50 +1,24 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: let - inherit (lib) - mkOption - optionalString - types - versionAtLeast - ; + inherit (lib) mkOption optionalString types versionAtLeast; inherit (lib.options) literalExpression; cfg = config.amazonImage; amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios"; - virtualisationOptions = import ../../../modules/virtualisation/virtualisation-options.nix; -in -{ - imports = [ - ../../../modules/virtualisation/amazon-image.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "amazonImage" - "sizeMB" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; +in { + + imports = [ ../../../modules/virtualisation/amazon-image.nix ]; # Amazon recommends setting this to the highest possible value for a good EBS # experience, which prior to 4.15 was 255. # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes config.boot.kernelParams = - let - timeout = - if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" then "4294967295" else "255"; - in - [ "nvme_core.io_timeout=${timeout}" ]; + let timeout = + if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" + then "4294967295" + else "255"; + in [ "nvme_core.io_timeout=${timeout}" ]; options.amazonImage = { name = mkOption { @@ -60,30 +34,30 @@ in } ] ''; - default = [ ]; + default = []; description = '' This option lists files to be copied to fixed locations in the generated image. Glob patterns work. ''; }; + sizeMB = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = 3072; + example = 8192; + description = "The size in MB of the image"; + }; + format = mkOption { - type = types.enum [ - "raw" - "qcow2" - "vpc" - ]; + type = types.enum [ "raw" "qcow2" "vpc" ]; default = "vpc"; description = "The image format to output"; }; }; - config.virtualisation.diskSize = lib.mkDefault (3 * 1024); - config.virtualisation.diskSizeAutoSupported = !config.ec2.zfs.enable; - - config.system.build.amazonImage = - let - configFile = pkgs.writeText "configuration.nix" '' + config.system.build.amazonImage = let + configFile = pkgs.writeText "configuration.nix" + '' { modulesPath, ... }: { imports = [ "''${modulesPath}/virtualisation/amazon-image.nix" ]; ${optionalString config.ec2.efi '' @@ -96,102 +70,91 @@ in } ''; - zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { - inherit - lib - config - configFile - pkgs - ; - inherit (cfg) contents format name; + zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { + inherit lib config configFile pkgs; + inherit (cfg) contents format name; - includeChannel = true; + includeChannel = true; - bootSize = 1000; # 1G is the minimum EBS volume + bootSize = 1000; # 1G is the minimum EBS volume - rootSize = config.virtualisation.diskSize; - rootPoolProperties = { - ashift = 12; - autoexpand = "on"; - }; - - datasets = config.ec2.zfs.datasets; - - postVM = '' - extension=''${rootDiskImage##*.} - friendlyName=$out/${cfg.name} - rootDisk="$friendlyName.root.$extension" - bootDisk="$friendlyName.boot.$extension" - mv "$rootDiskImage" "$rootDisk" - mv "$bootDiskImage" "$bootDisk" - - mkdir -p $out/nix-support - echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products - echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products - - ${pkgs.jq}/bin/jq -n \ - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ - --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_mode "${amiBootMode}" \ - --arg root "$rootDisk" \ - --arg boot "$bootDisk" \ - '{} - | .label = $system_label - | .boot_mode = $boot_mode - | .system = $system - | .disks.boot.logical_bytes = $boot_logical_bytes - | .disks.boot.file = $boot - | .disks.root.logical_bytes = $root_logical_bytes - | .disks.root.file = $root - ' > $out/nix-support/image-info.json - ''; + rootSize = cfg.sizeMB; + rootPoolProperties = { + ashift = 12; + autoexpand = "on"; }; - extBuilder = import ../../../lib/make-disk-image.nix { - inherit - lib - config - configFile - pkgs - ; + datasets = config.ec2.zfs.datasets; - inherit (cfg) contents format name; + postVM = '' + extension=''${rootDiskImage##*.} + friendlyName=$out/${cfg.name} + rootDisk="$friendlyName.root.$extension" + bootDisk="$friendlyName.boot.$extension" + mv "$rootDiskImage" "$rootDisk" + mv "$bootDiskImage" "$bootDisk" - fsType = "ext4"; - partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; + mkdir -p $out/nix-support + echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products + echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products - inherit (config.virtualisation) diskSize; + ${pkgs.jq}/bin/jq -n \ + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ + --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_mode "${amiBootMode}" \ + --arg root "$rootDisk" \ + --arg boot "$bootDisk" \ + '{} + | .label = $system_label + | .boot_mode = $boot_mode + | .system = $system + | .disks.boot.logical_bytes = $boot_logical_bytes + | .disks.boot.file = $boot + | .disks.root.logical_bytes = $root_logical_bytes + | .disks.root.file = $root + ' > $out/nix-support/image-info.json + ''; + }; - postVM = '' - extension=''${diskImage##*.} - friendlyName=$out/${cfg.name}.$extension - mv "$diskImage" "$friendlyName" - diskImage=$friendlyName + extBuilder = import ../../../lib/make-disk-image.nix { + inherit lib config configFile pkgs; - mkdir -p $out/nix-support - echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products + inherit (cfg) contents format name; - ${pkgs.jq}/bin/jq -n \ - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ - --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ - --arg boot_mode "${amiBootMode}" \ - --arg file "$diskImage" \ - '{} - | .label = $system_label - | .boot_mode = $boot_mode - | .system = $system - | .logical_bytes = $logical_bytes - | .file = $file - | .disks.root.logical_bytes = $logical_bytes - | .disks.root.file = $file - ' > $out/nix-support/image-info.json - ''; - }; - in - if config.ec2.zfs.enable then zfsBuilder else extBuilder; + fsType = "ext4"; + partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; + + diskSize = cfg.sizeMB; + + postVM = '' + extension=''${diskImage##*.} + friendlyName=$out/${cfg.name}.$extension + mv "$diskImage" "$friendlyName" + diskImage=$friendlyName + + mkdir -p $out/nix-support + echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products + + ${pkgs.jq}/bin/jq -n \ + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ + --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ + --arg boot_mode "${amiBootMode}" \ + --arg file "$diskImage" \ + '{} + | .label = $system_label + | .boot_mode = $boot_mode + | .system = $system + | .logical_bytes = $logical_bytes + | .file = $file + | .disks.root.logical_bytes = $logical_bytes + | .disks.root.file = $file + ' > $out/nix-support/image-info.json + ''; + }; + in if config.ec2.zfs.enable then zfsBuilder else extBuilder; meta.maintainers = with lib.maintainers; [ arianvp ]; } diff --git a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix index 03895b9aa833..9799f333aec0 100644 --- a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix +++ b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix @@ -1,37 +1,18 @@ # nix-build '' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }" -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: let inherit (lib) mkOption types; copyChannel = true; cfg = config.openstackImage; imageBootMode = if config.openstack.efi then "uefi" else "legacy-bios"; - virtualisationOptions = import ../../../modules/virtualisation/virtualisation-options.nix; in { imports = [ ../../../modules/virtualisation/openstack-config.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "openstackImage" - "sizeMB" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ] ++ (lib.optional copyChannel ../../../modules/installer/cd-dvd/channel.nix); + options.openstackImage = { name = mkOption { type = types.str; @@ -41,15 +22,18 @@ in ramMB = mkOption { type = types.int; - default = (3 * 1024); + default = 1024; description = "RAM allocation for build VM"; }; + sizeMB = mkOption { + type = types.int; + default = 8192; + description = "The size in MB of the image"; + }; + format = mkOption { - type = types.enum [ - "raw" - "qcow2" - ]; + type = types.enum [ "raw" "qcow2" ]; default = "qcow2"; description = "The image format to output"; }; @@ -70,26 +54,24 @@ in }; }; - virtualisation.diskSize = lib.mkDefault (8 * 1024); - virtualisation.diskSizeAutoSupported = false; - system.build.openstackImage = import ../../../lib/make-single-disk-zfs-image.nix { inherit lib config; inherit (cfg) contents format name; pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package - configFile = pkgs.writeText "configuration.nix" '' - { modulesPath, ... }: { - imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; - openstack.zfs.enable = true; - } - ''; + configFile = pkgs.writeText "configuration.nix" + '' + { modulesPath, ... }: { + imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; + openstack.zfs.enable = true; + } + ''; includeChannel = copyChannel; bootSize = 1000; memSize = cfg.ramMB; - rootSize = config.virtualisation.diskSize; + rootSize = cfg.sizeMB; rootPoolProperties = { ashift = 12; autoexpand = "on"; diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index 28832d2b7157..bf8414e1e108 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -1,9 +1,4 @@ -{ - config, - lib, - options, - ... -}: +{ config, lib, options, ... }: let keysDirectory = "/var/keys"; @@ -20,19 +15,6 @@ in imports = [ ../virtualisation/qemu-vm.nix - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "darwin-builder" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - # Avoid a dependency on stateVersion { disabledModules = [ @@ -41,16 +23,17 @@ in ]; # swraid's default depends on stateVersion config.boot.swraid.enable = false; - options.boot.isContainer = lib.mkOption { - default = false; - internal = true; - }; + options.boot.isContainer = lib.mkOption { default = false; internal = true; }; } ]; - options.virtualisation.description = "The maximum disk space allocated to the runner in megabytes"; - options.virtualisation.darwin-builder = with lib; { + diskSize = mkOption { + default = 20 * 1024; + type = types.int; + example = 30720; + description = "The maximum disk space allocated to the runner in MB"; + }; memorySize = mkOption { default = 3 * 1024; type = types.int; @@ -76,13 +59,13 @@ in ''; }; workingDirectory = mkOption { - default = "."; - type = types.str; - example = "/var/lib/darwin-builder"; - description = '' - The working directory to use to run the script. When running - as part of a flake will need to be set to a non read-only filesystem. - ''; + default = "."; + type = types.str; + example = "/var/lib/darwin-builder"; + description = '' + The working directory to use to run the script. When running + as part of a flake will need to be set to a non read-only filesystem. + ''; }; hostPort = mkOption { default = 31022; @@ -175,34 +158,26 @@ in script = hostPkgs.writeShellScriptBin "create-builder" ( '' set -euo pipefail - '' - + - # When running as non-interactively as part of a DarwinConfiguration the working directory - # must be set to a writeable directory. - ( - if cfg.workingDirectory != "." then - '' - ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" - cd "${cfg.workingDirectory}" - '' - else - "" - ) - + '' - KEYS="''${KEYS:-./keys}" - ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" - PRIVATE_KEY="''${KEYS}/${user}_${keyType}" - PUBLIC_KEY="''${PRIVATE_KEY}.pub" - if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then - ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" - ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' - fi - if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then - (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") - fi - KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} - '' - ); + '' + + # When running as non-interactively as part of a DarwinConfiguration the working directory + # must be set to a writeable directory. + (if cfg.workingDirectory != "." then '' + ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" + cd "${cfg.workingDirectory}" + '' else "") + '' + KEYS="''${KEYS:-./keys}" + ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" + PRIVATE_KEY="''${KEYS}/${user}_${keyType}" + PUBLIC_KEY="''${PRIVATE_KEY}.pub" + if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then + ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" + ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' + fi + if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then + (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") + fi + KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} + ''); in script.overrideAttrs (old: { @@ -248,16 +223,12 @@ in ''; virtualisation = { - diskSize = lib.mkDefault (20 * 1024); + diskSize = cfg.diskSize; memorySize = cfg.memorySize; forwardPorts = [ - { - from = "host"; - guest.port = 22; - host.port = cfg.hostPort; - } + { from = "host"; guest.port = 22; host.port = cfg.hostPort; } ]; # Disable graphics for the builder since users will likely want to run it diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix index 4c321ad95fc8..ecb57483cce9 100644 --- a/nixos/modules/virtualisation/azure-image.nix +++ b/nixos/modules/virtualisation/azure-image.nix @@ -1,34 +1,22 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: with lib; let cfg = config.virtualisation.azureImage; - virtualisationOptions = import ./virtualisation-options.nix; in { - imports = [ - ./azure-common.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "azureImage" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; + imports = [ ./azure-common.nix ]; options.virtualisation.azureImage = { + diskSize = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = "auto"; + example = 2048; + description = '' + Size of disk image. Unit is MB. + ''; + }; + bootSize = mkOption { type = types.int; default = 256; @@ -47,12 +35,7 @@ in }; vmGeneration = mkOption { - type = - with types; - enum [ - "v1" - "v2" - ]; + type = with types; enum [ "v1" "v2" ]; default = "v1"; description = '' VM Generation to use. @@ -74,8 +57,7 @@ in bootSize = "${toString cfg.bootSize}M"; partitionTableType = if cfg.vmGeneration == "v2" then "efi" else "legacy"; - inherit (cfg) contents; - inherit (config.virtualisation) diskSize; + inherit (cfg) diskSize contents; inherit config lib pkgs; }; }; diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix index 597f2c2f7a3f..53791e911406 100644 --- a/nixos/modules/virtualisation/digital-ocean-image.nix +++ b/nixos/modules/virtualisation/digital-ocean-image.nix @@ -1,35 +1,23 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: with lib; let cfg = config.virtualisation.digitalOceanImage; - virtualisationOptions = import ./virtualisation-options.nix; in { - imports = [ - ./digital-ocean-config.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "digitialOceanImage" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; + imports = [ ./digital-ocean-config.nix ]; options = { + virtualisation.digitalOceanImage.diskSize = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = "auto"; + example = 4096; + description = '' + Size of disk image. Unit is MB. + ''; + }; + virtualisation.digitalOceanImage.configFile = mkOption { type = with types; nullOr path; default = null; @@ -43,10 +31,7 @@ in }; virtualisation.digitalOceanImage.compressionMethod = mkOption { - type = types.enum [ - "gzip" - "bzip2" - ]; + type = types.enum [ "gzip" "bzip2" ]; default = "gzip"; example = "bzip2"; description = '' @@ -59,35 +44,27 @@ in #### implementation config = { + system.build.digitalOceanImage = import ../../lib/make-disk-image.nix { name = "digital-ocean-image"; format = "qcow2"; - postVM = - let - compress = - { - "gzip" = "${pkgs.gzip}/bin/gzip"; - "bzip2" = "${pkgs.bzip2}/bin/bzip2"; - } - .${cfg.compressionMethod}; - in - '' - ${compress} $diskImage - ''; - configFile = - if cfg.configFile == null then - config.virtualisation.digitalOcean.defaultConfigFile - else - cfg.configFile; - inherit (config.virtualisation) diskSize; + postVM = let + compress = { + "gzip" = "${pkgs.gzip}/bin/gzip"; + "bzip2" = "${pkgs.bzip2}/bin/bzip2"; + }.${cfg.compressionMethod}; + in '' + ${compress} $diskImage + ''; + configFile = if cfg.configFile == null + then config.virtualisation.digitalOcean.defaultConfigFile + else cfg.configFile; + inherit (cfg) diskSize; inherit config lib pkgs; }; }; - meta.maintainers = with maintainers; [ - arianvp - eamsden - ]; + meta.maintainers = with maintainers; [ arianvp eamsden ]; } diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix index 200c512ecf66..8e7b31b439bf 100644 --- a/nixos/modules/virtualisation/google-compute-image.nix +++ b/nixos/modules/virtualisation/google-compute-image.nix @@ -1,9 +1,4 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: with lib; let @@ -16,28 +11,21 @@ let ]; } ''; - virtualisationOptions = import ./virtualisation-options.nix; in { - imports = [ - ./google-compute-config.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "googleComputeImage" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; + imports = [ ./google-compute-config.nix ]; options = { + virtualisation.googleComputeImage.diskSize = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = "auto"; + example = 1536; + description = '' + Size of disk image. Unit is MB. + ''; + }; + virtualisation.googleComputeImage.configFile = mkOption { type = with types; nullOr str; default = null; @@ -76,13 +64,7 @@ in system.build.googleComputeImage = import ../../lib/make-disk-image.nix { name = "google-compute-image"; postVM = '' - PATH=$PATH:${ - with pkgs; - lib.makeBinPath [ - gnutar - gzip - ] - } + PATH=$PATH:${with pkgs; lib.makeBinPath [ gnutar gzip ]} pushd $out mv $diskImage disk.raw tar -Sc disk.raw | gzip -${toString cfg.compressionLevel} > \ @@ -93,7 +75,7 @@ in format = "raw"; configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; partitionTableType = if cfg.efi then "efi" else "legacy"; - inherit (config.virtualisation) diskSize; + inherit (cfg) diskSize; inherit config lib pkgs; }; diff --git a/nixos/modules/virtualisation/hyperv-image.nix b/nixos/modules/virtualisation/hyperv-image.nix index fe48ce632dcf..eb1bbe9f3a58 100644 --- a/nixos/modules/virtualisation/hyperv-image.nix +++ b/nixos/modules/virtualisation/hyperv-image.nix @@ -1,37 +1,21 @@ -{ - config, - pkgs, - lib, - ... -}: +{ config, pkgs, lib, ... }: with lib; let cfg = config.hyperv; - virtualisationOptions = import ./virtualisation-options.nix; - -in -{ - - imports = [ - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "hyperv" - "baseImageSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; +in { options = { hyperv = { + baseImageSize = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = "auto"; + example = 2048; + description = '' + The size of the hyper-v base image in MiB. + ''; + }; vmDerivationName = mkOption { type = types.str; default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}"; @@ -50,8 +34,6 @@ in }; config = { - virtualisation.diskSize = lib.mkDefault (4 * 1024); - system.build.hypervImage = import ../../lib/make-disk-image.nix { name = cfg.vmDerivationName; postVM = '' @@ -59,7 +41,7 @@ in rm $diskImage ''; format = "raw"; - inherit (config.virtualisation) diskSize; + diskSize = cfg.baseImageSize; partitionTableType = "efi"; inherit config lib pkgs; }; diff --git a/nixos/modules/virtualisation/linode-image.nix b/nixos/modules/virtualisation/linode-image.nix index 4725f0a2f9b7..51f793ac011d 100644 --- a/nixos/modules/virtualisation/linode-image.nix +++ b/nixos/modules/virtualisation/linode-image.nix @@ -1,9 +1,4 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: with lib; let @@ -15,27 +10,19 @@ let ]; } ''; - virtualisationOptions = import ./virtualisation-options.nix; in { - imports = [ - ./linode-config.nix - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "linodeImage" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; + imports = [ ./linode-config.nix ]; options = { + virtualisation.linodeImage.diskSize = mkOption { + type = with types; either (enum (singleton "auto")) ints.positive; + default = "auto"; + example = 1536; + description = '' + Size of disk image in MB. + ''; + }; virtualisation.linodeImage.configFile = mkOption { type = with types; nullOr str; @@ -70,7 +57,7 @@ in format = "raw"; partitionTableType = "none"; configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; - inherit (config.virtualisation) diskSize; + inherit (cfg) diskSize; inherit config lib pkgs; }; }; diff --git a/nixos/modules/virtualisation/oci-image.nix b/nixos/modules/virtualisation/oci-image.nix index 44a6ef7ef6f6..1e2b90bfd46e 100644 --- a/nixos/modules/virtualisation/oci-image.nix +++ b/nixos/modules/virtualisation/oci-image.nix @@ -1,9 +1,4 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: let cfg = config.oci; @@ -12,12 +7,9 @@ in imports = [ ./oci-common.nix ]; config = { - virtualisation.diskSize = lib.mkDefault (8 * 1024); - virtualisation.diskSizeAutoSupported = false; - system.build.OCIImage = import ../../lib/make-disk-image.nix { inherit config lib pkgs; - inherit (config.virtualisation) diskSize; + inherit (cfg) diskSize; name = "oci-image"; configFile = ./oci-config-user.nix; format = "qcow2"; @@ -33,10 +25,7 @@ in after = [ "network-online.target" ]; wants = [ "network-online.target" ]; - path = [ - pkgs.coreutils - pkgs.curl - ]; + path = [ pkgs.coreutils pkgs.curl ]; script = '' mkdir -m 0700 -p /root/.ssh if [ -f /root/.ssh/authorized_keys ]; then diff --git a/nixos/modules/virtualisation/oci-options.nix b/nixos/modules/virtualisation/oci-options.nix index 8a1ac4e03446..76f3475a4281 100644 --- a/nixos/modules/virtualisation/oci-options.nix +++ b/nixos/modules/virtualisation/oci-options.nix @@ -1,27 +1,5 @@ +{ config, lib, pkgs, ... }: { - lib, - ... -}: -let - virtualisationOptions = import ./virtualisation-options.nix; -in -{ - imports = [ - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "oci" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; - options = { oci = { efi = lib.mkOption { @@ -31,6 +9,12 @@ in Whether the OCI instance is using EFI. ''; }; + diskSize = lib.mkOption { + type = lib.types.int; + default = 8192; + description = "Size of the disk image created in MB."; + example = "diskSize = 12 * 1024; # 12GiB"; + }; }; }; } diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix index 2cfc7fa1b530..d390c78432ae 100644 --- a/nixos/modules/virtualisation/proxmox-image.nix +++ b/nixos/modules/virtualisation/proxmox-image.nix @@ -1,31 +1,8 @@ -{ - config, - pkgs, - lib, - ... -}: +{ config, pkgs, lib, ... }: with lib; -let - virtualisationOptions = import ./virtualisation-options.nix; -in -{ - imports = [ - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "proxmoxImage" - "diskSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; +{ options.proxmox = { qemuConf = { # essential configs @@ -77,10 +54,7 @@ in ''; }; bios = mkOption { - type = types.enum [ - "seabios" - "ovmf" - ]; + type = types.enum [ "seabios" "ovmf" ]; default = "seabios"; description = '' Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI). @@ -113,6 +87,16 @@ in either "efi" or "hybrid". ''; }; + diskSize = mkOption { + type = types.str; + default = "auto"; + example = "20480"; + description = '' + The size of the disk, in megabytes. + if "auto" size is calculated based on the contents copied to it and + additionalSpace is taken into account. + ''; + }; net0 = mkOption { type = types.commas; default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1"; @@ -140,13 +124,8 @@ in }; }; qemuExtraConf = mkOption { - type = - with types; - attrsOf (oneOf [ - str - int - ]); - default = { }; + type = with types; attrsOf (oneOf [ str int ]); + default = {}; example = literalExpression '' { cpu = "host"; @@ -158,12 +137,7 @@ in ''; }; partitionTableType = mkOption { - type = types.enum [ - "efi" - "hybrid" - "legacy" - "legacy+gpt" - ]; + type = types.enum [ "efi" "hybrid" "legacy" "legacy+gpt" ]; description = '' Partition table type to use. See make-disk-image.nix partitionTableType for details. Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'. @@ -211,163 +185,142 @@ in }; }; - config = - let - cfg = config.proxmox; - cfgLine = name: value: '' - ${name}: ${builtins.toString value} - ''; - virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); - cfgFile = - fileName: properties: - pkgs.writeTextDir fileName '' - # generated by NixOS - ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} - #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: - ''; + config = let + cfg = config.proxmox; + cfgLine = name: value: '' + ${name}: ${builtins.toString value} + ''; + virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); + cfgFile = fileName: properties: pkgs.writeTextDir fileName '' + # generated by NixOS + ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} + #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: + ''; + inherit (cfg) partitionTableType; + supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; + supportBios = partitionTableType == "legacy" || partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; + hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; + hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; + in { + assertions = [ + { + assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; + message = "systemd-boot requires 'ovmf' bios"; + } + { + assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; + message = "'efi' disk partitioning requires 'ovmf' bios"; + } + { + assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; + message = "'legacy' disk partitioning requires 'seabios' bios"; + } + { + assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; + message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; + } + ]; + system.build.VMA = import ../../lib/make-disk-image.nix { + name = "proxmox-${cfg.filenameSuffix}"; inherit (cfg) partitionTableType; - supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; - supportBios = - partitionTableType == "legacy" - || partitionTableType == "hybrid" - || partitionTableType == "legacy+gpt"; - hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; - hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; - in - { - assertions = [ - { - assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; - message = "systemd-boot requires 'ovmf' bios"; - } - { - assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; - message = "'efi' disk partitioning requires 'ovmf' bios"; - } - { - assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; - message = "'legacy' disk partitioning requires 'seabios' bios"; - } - { - assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; - message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; - } - ]; - system.build.VMA = import ../../lib/make-disk-image.nix { - name = "proxmox-${cfg.filenameSuffix}"; - inherit (cfg) partitionTableType; - postVM = - let - # Build qemu with PVE's patch that adds support for the VMA format - vma = - (pkgs.qemu_kvm.override { - alsaSupport = false; - pulseSupport = false; - sdlSupport = false; - jackSupport = false; - gtkSupport = false; - vncSupport = false; - smartcardSupport = false; - spiceSupport = false; - ncursesSupport = false; - libiscsiSupport = false; - tpmSupport = false; - numaSupport = false; - seccompSupport = false; - guestAgentSupport = false; - }).overrideAttrs - (super: rec { - # Check https://github.com/proxmox/pve-qemu/tree/master for the version - # of qemu and patch to use - version = "9.0.0"; - src = pkgs.fetchurl { - url = "https://download.qemu.org/qemu-${version}.tar.xz"; - hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; - }; - patches = [ - # Proxmox' VMA tool is published as a particular patch upon QEMU - "${ - pkgs.fetchFromGitHub { - owner = "proxmox"; - repo = "pve-qemu"; - rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; - hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; - } - }/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" - ]; + postVM = let + # Build qemu with PVE's patch that adds support for the VMA format + vma = (pkgs.qemu_kvm.override { + alsaSupport = false; + pulseSupport = false; + sdlSupport = false; + jackSupport = false; + gtkSupport = false; + vncSupport = false; + smartcardSupport = false; + spiceSupport = false; + ncursesSupport = false; + libiscsiSupport = false; + tpmSupport = false; + numaSupport = false; + seccompSupport = false; + guestAgentSupport = false; + }).overrideAttrs ( super: rec { + # Check https://github.com/proxmox/pve-qemu/tree/master for the version + # of qemu and patch to use + version = "9.0.0"; + src = pkgs.fetchurl { + url = "https://download.qemu.org/qemu-${version}.tar.xz"; + hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; + }; + patches = [ + # Proxmox' VMA tool is published as a particular patch upon QEMU + "${pkgs.fetchFromGitHub { + owner = "proxmox"; + repo = "pve-qemu"; + rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; + hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; + }}/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" + ]; - buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; - nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; + buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; + nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; - }); - in - '' - ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ - -c ${ - cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf) - }/qemu-server.conf drive-virtio0=$diskImage - rm $diskImage - ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" - mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ + }); + in + '' + ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ + -c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage + rm $diskImage + ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" + mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ - mkdir -p $out/nix-support - echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products - ''; - inherit (cfg.qemuConf) additionalSpace bootSize; - inherit (config.virtualisation) diskSize; - format = "raw"; - inherit config lib pkgs; - }; - - boot = { - growPartition = true; - kernelParams = [ "console=ttyS0" ]; - loader.grub = { - device = lib.mkDefault ( - if (hasNoFsPartition || supportBios) then - # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), - # which will be used the bootloader, do not set it as loader.grub.device. - # GRUB installation fails, unless the whole disk is selected. - "/dev/vda" - else - "nodev" - ); - efiSupport = lib.mkDefault supportEfi; - efiInstallAsRemovable = lib.mkDefault supportEfi; - }; - - loader.timeout = 0; - initrd.availableKernelModules = [ - "uas" - "virtio_blk" - "virtio_pci" - ]; - }; - - fileSystems."/" = { - device = "/dev/disk/by-label/nixos"; - autoResize = true; - fsType = "ext4"; - }; - fileSystems."/boot" = lib.mkIf hasBootPartition { - device = "/dev/disk/by-label/ESP"; - fsType = "vfat"; - }; - - networking = mkIf cfg.cloudInit.enable { - hostName = mkForce ""; - useDHCP = false; - }; - - services = { - cloud-init = mkIf cfg.cloudInit.enable { - enable = true; - network.enable = true; - }; - sshd.enable = mkDefault true; - qemuGuest.enable = true; - }; - - proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; + mkdir -p $out/nix-support + echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products + ''; + inherit (cfg.qemuConf) additionalSpace diskSize bootSize; + format = "raw"; + inherit config lib pkgs; }; + + boot = { + growPartition = true; + kernelParams = [ "console=ttyS0" ]; + loader.grub = { + device = lib.mkDefault (if (hasNoFsPartition || supportBios) then + # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), + # which will be used the bootloader, do not set it as loader.grub.device. + # GRUB installation fails, unless the whole disk is selected. + "/dev/vda" + else + "nodev"); + efiSupport = lib.mkDefault supportEfi; + efiInstallAsRemovable = lib.mkDefault supportEfi; + }; + + loader.timeout = 0; + initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ]; + }; + + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + fileSystems."/boot" = lib.mkIf hasBootPartition { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + + networking = mkIf cfg.cloudInit.enable { + hostName = mkForce ""; + useDHCP = false; + }; + + services = { + cloud-init = mkIf cfg.cloudInit.enable { + enable = true; + network.enable = true; + }; + sshd.enable = mkDefault true; + qemuGuest.enable = true; + }; + + proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; + }; } diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 7b9b5086dbb9..153dc877352a 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -4,13 +4,7 @@ # `config'. By default, the Nix store is shared read-only with the # host, which makes (re)building VMs very efficient. -{ - config, - lib, - pkgs, - options, - ... -}: +{ config, lib, pkgs, options, ... }: with lib; @@ -28,262 +22,235 @@ let consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; - driveOpts = - { ... }: - { + driveOpts = { ... }: { - options = { + options = { - file = mkOption { - type = types.str; - description = "The file image used for this drive."; - }; + file = mkOption { + type = types.str; + description = "The file image used for this drive."; + }; - driveExtraOpts = mkOption { - type = types.attrsOf types.str; - default = { }; - description = "Extra options passed to drive flag."; - }; + driveExtraOpts = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Extra options passed to drive flag."; + }; - deviceExtraOpts = mkOption { - type = types.attrsOf types.str; - default = { }; - description = "Extra options passed to device flag."; - }; - - name = mkOption { - type = types.nullOr types.str; - default = null; - description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; - }; + deviceExtraOpts = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Extra options passed to device flag."; + }; + name = mkOption { + type = types.nullOr types.str; + default = null; + description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; }; }; - selectPartitionTableLayout = - { useEFIBoot, useDefaultFilesystems }: - if useDefaultFilesystems then if useEFIBoot then "efi" else "legacy" else "none"; + }; - driveCmdline = - idx: - { - file, - driveExtraOpts, - deviceExtraOpts, - ... - }: + selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }: + if useDefaultFilesystems then + if useEFIBoot then "efi" else "legacy" + else "none"; + + driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: let drvId = "drive${toString idx}"; - mkKeyValue = generators.mkKeyValueDefault { } "="; + mkKeyValue = generators.mkKeyValueDefault {} "="; mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); - driveOpts = mkOpts ( - driveExtraOpts - // { - index = idx; - id = drvId; - "if" = "none"; - inherit file; - } - ); - deviceOpts = mkOpts ( - deviceExtraOpts - // { - drive = drvId; - } - ); + driveOpts = mkOpts (driveExtraOpts // { + index = idx; + id = drvId; + "if" = "none"; + inherit file; + }); + deviceOpts = mkOpts (deviceExtraOpts // { + drive = drvId; + }); device = if cfg.qemu.diskInterface == "scsi" then "-device lsi53c895a -device scsi-hd,${deviceOpts}" else "-device virtio-blk-pci,${deviceOpts}"; in - "-drive ${driveOpts} ${device}"; + "-drive ${driveOpts} ${device}"; drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); # Shell script to start the VM. - startVM = '' - #! ${hostPkgs.runtimeShell} + startVM = + '' + #! ${hostPkgs.runtimeShell} - export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH + export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH - set -e + set -e - # Create an empty ext4 filesystem image. A filesystem image does not - # contain a partition table but just a filesystem. - createEmptyFilesystemImage() { - local name=$1 - local size=$2 - local temp=$(mktemp) - ${qemu}/bin/qemu-img create -f raw "$temp" "$size" - ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" - ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" - rm "$temp" - } + # Create an empty ext4 filesystem image. A filesystem image does not + # contain a partition table but just a filesystem. + createEmptyFilesystemImage() { + local name=$1 + local size=$2 + local temp=$(mktemp) + ${qemu}/bin/qemu-img create -f raw "$temp" "$size" + ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" + ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" + rm "$temp" + } - NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" + NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" - if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then - echo "Disk image do not exist, creating the virtualisation disk image..." + if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then + echo "Disk image do not exist, creating the virtualisation disk image..." - ${ - if (cfg.useBootLoader && cfg.useDefaultFilesystems) then - '' - # Create a writable qcow2 image using the systemImage as a backing - # image. + ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then '' + # Create a writable qcow2 image using the systemImage as a backing + # image. - # CoW prevent size to be attributed to an image. - # FIXME: raise this issue to upstream. - ${qemu}/bin/qemu-img create \ - -f qcow2 \ - -b ${systemImage}/nixos.qcow2 \ - -F qcow2 \ - "$NIX_DISK_IMAGE" - '' - else if cfg.useDefaultFilesystems then - '' - createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" - '' - else - '' - # Create an empty disk image without a filesystem. - ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" - '' - } - echo "Virtualisation disk image created." - fi - - # Create a directory for storing temporary data of the running VM. - if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then - TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) - fi - - ${lib.optionalString (cfg.useNixStoreImage) '' - echo "Creating Nix store image..." - - ${hostPkgs.gnutar}/bin/tar --create \ - --absolute-names \ - --verbatim-files-from \ - --transform 'flags=rSh;s|/nix/store/||' \ - --files-from ${ - hostPkgs.closureInfo { - rootPaths = [ - config.system.build.toplevel - regInfo - ]; + # CoW prevent size to be attributed to an image. + # FIXME: raise this issue to upstream. + ${qemu}/bin/qemu-img create \ + -f qcow2 \ + -b ${systemImage}/nixos.qcow2 \ + -F qcow2 \ + "$NIX_DISK_IMAGE" + '' else if cfg.useDefaultFilesystems then '' + createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" + '' else '' + # Create an empty disk image without a filesystem. + ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" + '' } - }/store-paths \ - | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ - --quiet \ - --force-uid=0 \ - --force-gid=0 \ - -L ${nixStoreFilesystemLabel} \ - -U eb176051-bd15-49b7-9e6b-462e0b467019 \ - -T 0 \ - --tar=f \ - "$TMPDIR"/store.img - - echo "Created Nix store image." - ''} - - # Create a directory for exchanging data with the VM. - mkdir -p "$TMPDIR/xchg" - - ${lib.optionalString cfg.useHostCerts '' - mkdir -p "$TMPDIR/certs" - if [ -e "$NIX_SSL_CERT_FILE" ]; then - cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt - else - echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. + echo "Virtualisation disk image created." fi - ''} - ${lib.optionalString cfg.useEFIBoot '' - # Expose EFI variables, it's useful even when we are not using a bootloader (!). - # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence - # no guard against `useBootLoader`. Examples: - # - testing PXE boot or other EFI applications - # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows - NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") - # VM needs writable EFI vars - if ! test -e "$NIX_EFI_VARS"; then - ${ - if cfg.efi.keepVariables then - # We still need the EFI var from the make-disk-image derivation - # because our "switch-to-configuration" process might - # write into it and we want to keep this data. - ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' + # Create a directory for storing temporary data of the running VM. + if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then + TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) + fi + + ${lib.optionalString (cfg.useNixStoreImage) '' + echo "Creating Nix store image..." + + ${hostPkgs.gnutar}/bin/tar --create \ + --absolute-names \ + --verbatim-files-from \ + --transform 'flags=rSh;s|/nix/store/||' \ + --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \ + | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ + --quiet \ + --force-uid=0 \ + --force-gid=0 \ + -L ${nixStoreFilesystemLabel} \ + -U eb176051-bd15-49b7-9e6b-462e0b467019 \ + -T 0 \ + --tar=f \ + "$TMPDIR"/store.img + + echo "Created Nix store image." + '' + } + + # Create a directory for exchanging data with the VM. + mkdir -p "$TMPDIR/xchg" + + ${lib.optionalString cfg.useHostCerts + '' + mkdir -p "$TMPDIR/certs" + if [ -e "$NIX_SSL_CERT_FILE" ]; then + cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt else - ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' - } - chmod 0644 "$NIX_EFI_VARS" - fi - ''} + echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. + fi + ''} - ${lib.optionalString cfg.tpm.enable '' - NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") - mkdir -p "$NIX_SWTPM_DIR" - ${lib.getExe cfg.tpm.package} \ - socket \ - --tpmstate dir="$NIX_SWTPM_DIR" \ - --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ - --pid file="$NIX_SWTPM_DIR"/pid --daemon \ - --tpm2 \ - --log file="$NIX_SWTPM_DIR"/stdout,level=6 + ${lib.optionalString cfg.useEFIBoot + '' + # Expose EFI variables, it's useful even when we are not using a bootloader (!). + # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence + # no guard against `useBootLoader`. Examples: + # - testing PXE boot or other EFI applications + # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows + NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") + # VM needs writable EFI vars + if ! test -e "$NIX_EFI_VARS"; then + ${if cfg.efi.keepVariables then + # We still need the EFI var from the make-disk-image derivation + # because our "switch-to-configuration" process might + # write into it and we want to keep this data. + ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' + else + ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' + } + chmod 0644 "$NIX_EFI_VARS" + fi + ''} - # Enable `fdflags` builtin in Bash - # We will need it to perform surgical modification of the file descriptor - # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor - # on exec. - # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` - # at the end of this script. To work around that, we will just clear - # the `FD_CLOEXEC` bits as a first step. - enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags - # leave a dangling subprocess because the swtpm ctrl socket has - # "terminate" when the last connection disconnects, it stops swtpm. - # When qemu stops, or if the main shell process ends, the coproc will - # get signaled by virtue of the pipe between main and coproc ending. - # Which in turns triggers a socat connect-disconnect to swtpm which - # will stop it. - coproc waitingswtpm { - read || : - echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket - } - # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. - fdflags -s-cloexec ''${waitingswtpm[1]} - ''} + ${lib.optionalString cfg.tpm.enable '' + NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") + mkdir -p "$NIX_SWTPM_DIR" + ${lib.getExe cfg.tpm.package} \ + socket \ + --tpmstate dir="$NIX_SWTPM_DIR" \ + --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ + --pid file="$NIX_SWTPM_DIR"/pid --daemon \ + --tpm2 \ + --log file="$NIX_SWTPM_DIR"/stdout,level=6 - cd "$TMPDIR" + # Enable `fdflags` builtin in Bash + # We will need it to perform surgical modification of the file descriptor + # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor + # on exec. + # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` + # at the end of this script. To work around that, we will just clear + # the `FD_CLOEXEC` bits as a first step. + enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags + # leave a dangling subprocess because the swtpm ctrl socket has + # "terminate" when the last connection disconnects, it stops swtpm. + # When qemu stops, or if the main shell process ends, the coproc will + # get signaled by virtue of the pipe between main and coproc ending. + # Which in turns triggers a socat connect-disconnect to swtpm which + # will stop it. + coproc waitingswtpm { + read || : + echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket + } + # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. + fdflags -s-cloexec ''${waitingswtpm[1]} + ''} - ${lib.optionalString (cfg.emptyDiskImages != [ ]) "idx=0"} - ${flip concatMapStrings cfg.emptyDiskImages (size: '' - if ! test -e "empty$idx.qcow2"; then - ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" - fi - idx=$((idx + 1)) - '')} + cd "$TMPDIR" + + ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} + ${flip concatMapStrings cfg.emptyDiskImages (size: '' + if ! test -e "empty$idx.qcow2"; then + ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" + fi + idx=$((idx + 1)) + '')} + + # Start QEMU. + exec ${qemu-common.qemuBinary qemu} \ + -name ${config.system.name} \ + -m ${toString config.virtualisation.memorySize} \ + -smp ${toString config.virtualisation.cores} \ + -device virtio-rng-pci \ + ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ + ${concatStringsSep " \\\n " + (mapAttrsToList + (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}") + config.virtualisation.sharedDirectories)} \ + ${drivesCmdLine config.virtualisation.qemu.drives} \ + ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ + $QEMU_OPTS \ + "$@" + ''; - # Start QEMU. - exec ${qemu-common.qemuBinary qemu} \ - -name ${config.system.name} \ - -m ${toString config.virtualisation.memorySize} \ - -smp ${toString config.virtualisation.cores} \ - -device virtio-rng-pci \ - ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ - ${ - concatStringsSep " \\\n " ( - mapAttrsToList ( - tag: share: - "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}" - ) config.virtualisation.sharedDirectories - ) - } \ - ${drivesCmdLine config.virtualisation.qemu.drives} \ - ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ - $QEMU_OPTS \ - "$@" - ''; regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; }; @@ -325,243 +292,215 @@ let OVMF = cfg.efi.OVMF; }; - virtualisationOptions = import ./virtualisation-options.nix; - in { imports = [ - ./virtualisation-options.nix ../profiles/qemu-guest.nix - virtualisationOptions.diskSize - (mkRenamedOptionModule - [ - "virtualisation" - "pathsInNixDB" - ] - [ - "virtualisation" - "additionalPaths" - ] - ) - (mkRemovedOptionModule - [ - "virtualisation" - "bootDevice" - ] - "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues." - ) - (mkRemovedOptionModule - [ - "virtualisation" - "efiVars" - ] - "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue" - ) - (mkRemovedOptionModule - [ - "virtualisation" - "persistBootDevice" - ] - "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`" - ) + (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ]) + (mkRemovedOptionModule [ "virtualisation" "bootDevice" ] "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues.") + (mkRemovedOptionModule [ "virtualisation" "efiVars" ] "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue") + (mkRemovedOptionModule [ "virtualisation" "persistBootDevice" ] "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`") ]; options = { virtualisation.fileSystems = options.fileSystems; - virtualisation.memorySize = mkOption { - type = types.ints.positive; - default = 1024; - description = '' - The memory size in megabytes of the virtual machine. - ''; - }; - - virtualisation.msize = mkOption { - type = types.ints.positive; - default = 16384; - description = '' - The msize (maximum packet size) option passed to 9p file systems, in - bytes. Increasing this should increase performance significantly, - at the cost of higher RAM usage. - ''; - }; - - virtualisation.diskImage = mkOption { - type = types.nullOr types.str; - default = "./${config.system.name}.qcow2"; - defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; - description = '' - Path to the disk image containing the root filesystem. - The image will be created on startup if it does not - exist. - - If null, a tmpfs will be used as the root filesystem and - the VM's state will not be persistent. - ''; - }; - - virtualisation.bootLoaderDevice = mkOption { - type = types.path; - default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; - defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; - example = "/dev/disk/by-id/virtio-boot-loader-device"; - description = '' - The path (inside th VM) to the device to boot from when legacy booting. - ''; - }; - - virtualisation.bootPartition = mkOption { - type = types.nullOr types.path; - default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; - defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; - example = "/dev/disk/by-label/esp"; - description = '' - The path (inside the VM) to the device containing the EFI System Partition (ESP). - - If you are *not* booting from a UEFI firmware, this value is, by - default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. - ''; - }; - - virtualisation.rootDevice = mkOption { - type = types.nullOr types.path; - default = "/dev/disk/by-label/${rootFilesystemLabel}"; - defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; - example = "/dev/disk/by-label/nixos"; - description = '' - The path (inside the VM) to the device containing the root filesystem. - ''; - }; - - virtualisation.emptyDiskImages = mkOption { - type = types.listOf types.ints.positive; - default = [ ]; - description = '' - Additional disk images to provide to the VM. The value is - a list of size in megabytes of each disk. These disks are - writeable by the VM. - ''; - }; - - virtualisation.graphics = mkOption { - type = types.bool; - default = true; - description = '' - Whether to run QEMU with a graphics window, or in nographic mode. - Serial console will be enabled on both settings, but this will - change the preferred console. - ''; - }; - - virtualisation.resolution = mkOption { - type = options.services.xserver.resolutions.type.nestedTypes.elemType; - default = { - x = 1024; - y = 768; + virtualisation.memorySize = + mkOption { + type = types.ints.positive; + default = 1024; + description = '' + The memory size in megabytes of the virtual machine. + ''; }; - description = '' - The resolution of the virtual machine display. - ''; - }; - virtualisation.cores = mkOption { - type = types.ints.positive; - default = 1; - description = '' - Specify the number of cores the guest is permitted to use. - The number can be higher than the available cores on the - host system. - ''; - }; + virtualisation.msize = + mkOption { + type = types.ints.positive; + default = 16384; + description = '' + The msize (maximum packet size) option passed to 9p file systems, in + bytes. Increasing this should increase performance significantly, + at the cost of higher RAM usage. + ''; + }; - virtualisation.sharedDirectories = mkOption { - type = types.attrsOf ( - types.submodule { - options.source = mkOption { - type = types.str; - description = "The path of the directory to share, can be a shell variable"; - }; - options.target = mkOption { - type = types.path; - description = "The mount point of the directory inside the virtual machine"; - }; - options.securityModel = mkOption { - type = types.enum [ - "passthrough" - "mapped-xattr" - "mapped-file" - "none" - ]; - default = "mapped-xattr"; - description = '' - The security model to use for this share: + virtualisation.diskSize = + mkOption { + type = types.ints.positive; + default = 1024; + description = '' + The disk size in megabytes of the virtual machine. + ''; + }; - - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) - - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes - - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools - - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership - ''; - }; - } - ); - default = { }; - example = { - my-share = { - source = "/path/to/be/shared"; - target = "/mnt/shared"; + virtualisation.diskImage = + mkOption { + type = types.nullOr types.str; + default = "./${config.system.name}.qcow2"; + defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; + description = '' + Path to the disk image containing the root filesystem. + The image will be created on startup if it does not + exist. + + If null, a tmpfs will be used as the root filesystem and + the VM's state will not be persistent. + ''; + }; + + virtualisation.bootLoaderDevice = + mkOption { + type = types.path; + default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; + defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; + example = "/dev/disk/by-id/virtio-boot-loader-device"; + description = '' + The path (inside th VM) to the device to boot from when legacy booting. + ''; }; + + virtualisation.bootPartition = + mkOption { + type = types.nullOr types.path; + default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; + defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; + example = "/dev/disk/by-label/esp"; + description = '' + The path (inside the VM) to the device containing the EFI System Partition (ESP). + + If you are *not* booting from a UEFI firmware, this value is, by + default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. + ''; }; - description = '' - An attributes set of directories that will be shared with the - virtual machine using VirtFS (9P filesystem over VirtIO). - The attribute name will be used as the 9P mount tag. - ''; - }; - virtualisation.additionalPaths = mkOption { - type = types.listOf types.path; - default = [ ]; - description = '' - A list of paths whose closure should be made available to - the VM. + virtualisation.rootDevice = + mkOption { + type = types.nullOr types.path; + default = "/dev/disk/by-label/${rootFilesystemLabel}"; + defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; + example = "/dev/disk/by-label/nixos"; + description = '' + The path (inside the VM) to the device containing the root filesystem. + ''; + }; - When 9p is used, the closure is registered in the Nix - database in the VM. All other paths in the host Nix store - appear in the guest Nix store as well, but are considered - garbage (because they are not registered in the Nix - database of the guest). + virtualisation.emptyDiskImages = + mkOption { + type = types.listOf types.ints.positive; + default = []; + description = '' + Additional disk images to provide to the VM. The value is + a list of size in megabytes of each disk. These disks are + writeable by the VM. + ''; + }; - When {option}`virtualisation.useNixStoreImage` is - set, the closure is copied to the Nix store image. - ''; - }; + virtualisation.graphics = + mkOption { + type = types.bool; + default = true; + description = '' + Whether to run QEMU with a graphics window, or in nographic mode. + Serial console will be enabled on both settings, but this will + change the preferred console. + ''; + }; + + virtualisation.resolution = + mkOption { + type = options.services.xserver.resolutions.type.nestedTypes.elemType; + default = { x = 1024; y = 768; }; + description = '' + The resolution of the virtual machine display. + ''; + }; + + virtualisation.cores = + mkOption { + type = types.ints.positive; + default = 1; + description = '' + Specify the number of cores the guest is permitted to use. + The number can be higher than the available cores on the + host system. + ''; + }; + + virtualisation.sharedDirectories = + mkOption { + type = types.attrsOf + (types.submodule { + options.source = mkOption { + type = types.str; + description = "The path of the directory to share, can be a shell variable"; + }; + options.target = mkOption { + type = types.path; + description = "The mount point of the directory inside the virtual machine"; + }; + options.securityModel = mkOption { + type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ]; + default = "mapped-xattr"; + description = '' + The security model to use for this share: + + - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) + - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes + - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools + - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership + ''; + }; + }); + default = { }; + example = { + my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; + }; + description = '' + An attributes set of directories that will be shared with the + virtual machine using VirtFS (9P filesystem over VirtIO). + The attribute name will be used as the 9P mount tag. + ''; + }; + + virtualisation.additionalPaths = + mkOption { + type = types.listOf types.path; + default = []; + description = '' + A list of paths whose closure should be made available to + the VM. + + When 9p is used, the closure is registered in the Nix + database in the VM. All other paths in the host Nix store + appear in the guest Nix store as well, but are considered + garbage (because they are not registered in the Nix + database of the guest). + + When {option}`virtualisation.useNixStoreImage` is + set, the closure is copied to the Nix store image. + ''; + }; virtualisation.forwardPorts = mkOption { - type = types.listOf ( - types.submodule { + type = types.listOf + (types.submodule { options.from = mkOption { - type = types.enum [ - "host" - "guest" - ]; + type = types.enum [ "host" "guest" ]; default = "host"; description = '' - Controls the direction in which the ports are mapped: + Controls the direction in which the ports are mapped: - - `"host"` means traffic from the host ports - is forwarded to the given guest port. - - `"guest"` means traffic from the guest ports - is forwarded to the given host port. - ''; + - `"host"` means traffic from the host ports + is forwarded to the given guest port. + - `"guest"` means traffic from the guest ports + is forwarded to the given host port. + ''; }; options.proto = mkOption { - type = types.enum [ - "tcp" - "udp" - ]; + type = types.enum [ "tcp" "udp" ]; default = "tcp"; description = "The protocol to forward."; }; @@ -583,10 +522,10 @@ in type = types.port; description = "The guest port to be mapped."; }; - } - ); - default = [ ]; - example = lib.literalExpression '' + }); + default = []; + example = lib.literalExpression + '' [ # forward local port 2222 -> 22, to ssh into the VM { from = "host"; host.port = 2222; guest.port = 22; } @@ -596,121 +535,122 @@ in host.address = "127.0.0.1"; host.port = 80; } ] - ''; + ''; description = '' - When using the SLiRP user networking (default), this option allows to - forward ports to/from the host/guest. + When using the SLiRP user networking (default), this option allows to + forward ports to/from the host/guest. - ::: {.warning} - If the NixOS firewall on the virtual machine is enabled, you also - have to open the guest ports to enable the traffic between host and - guest. - ::: + ::: {.warning} + If the NixOS firewall on the virtual machine is enabled, you also + have to open the guest ports to enable the traffic between host and + guest. + ::: - ::: {.note} - Currently QEMU supports only IPv4 forwarding. - ::: - ''; + ::: {.note} + Currently QEMU supports only IPv4 forwarding. + ::: + ''; }; - virtualisation.restrictNetwork = mkOption { - type = types.bool; - default = false; - example = true; - description = '' - If this option is enabled, the guest will be isolated, i.e. it will - not be able to contact the host and no guest IP packets will be - routed over the host to the outside. This option does not affect - any explicitly set forwarding rules. - ''; - }; + virtualisation.restrictNetwork = + mkOption { + type = types.bool; + default = false; + example = true; + description = '' + If this option is enabled, the guest will be isolated, i.e. it will + not be able to contact the host and no guest IP packets will be + routed over the host to the outside. This option does not affect + any explicitly set forwarding rules. + ''; + }; - virtualisation.vlans = mkOption { - type = types.listOf types.ints.unsigned; - default = if config.virtualisation.interfaces == { } then [ 1 ] else [ ]; - defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; - example = [ - 1 - 2 - ]; - description = '' - Virtual networks to which the VM is connected. Each - number «N» in this list causes - the VM to have a virtual Ethernet interface attached to a - separate virtual network on which it will be assigned IP - address - `192.168.«N».«M»`, - where «M» is the index of this VM - in the list of VMs. - ''; - }; + virtualisation.vlans = + mkOption { + type = types.listOf types.ints.unsigned; + default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; + defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; + example = [ 1 2 ]; + description = '' + Virtual networks to which the VM is connected. Each + number «N» in this list causes + the VM to have a virtual Ethernet interface attached to a + separate virtual network on which it will be assigned IP + address + `192.168.«N».«M»`, + where «M» is the index of this VM + in the list of VMs. + ''; + }; virtualisation.interfaces = mkOption { - default = { }; + default = {}; example = { enp1s0.vlan = 1; }; description = '' Network interfaces to add to the VM. ''; - type = - with types; - attrsOf (submodule { - options = { - vlan = mkOption { - type = types.ints.unsigned; - description = '' - VLAN to which the network interface is connected. - ''; - }; - - assignIP = mkOption { - type = types.bool; - default = false; - description = '' - Automatically assign an IP address to the network interface using the same scheme as - virtualisation.vlans. - ''; - }; + type = with types; attrsOf (submodule { + options = { + vlan = mkOption { + type = types.ints.unsigned; + description = '' + VLAN to which the network interface is connected. + ''; }; - }); + + assignIP = mkOption { + type = types.bool; + default = false; + description = '' + Automatically assign an IP address to the network interface using the same scheme as + virtualisation.vlans. + ''; + }; + }; + }); }; - virtualisation.writableStore = mkOption { - type = types.bool; - default = cfg.mountHostNixStore; - defaultText = literalExpression "cfg.mountHostNixStore"; - description = '' - If enabled, the Nix store in the VM is made writable by - layering an overlay filesystem on top of the host's Nix - store. + virtualisation.writableStore = + mkOption { + type = types.bool; + default = cfg.mountHostNixStore; + defaultText = literalExpression "cfg.mountHostNixStore"; + description = '' + If enabled, the Nix store in the VM is made writable by + layering an overlay filesystem on top of the host's Nix + store. - By default, this is enabled if you mount a host Nix store. - ''; - }; + By default, this is enabled if you mount a host Nix store. + ''; + }; - virtualisation.writableStoreUseTmpfs = mkOption { - type = types.bool; - default = true; - description = '' - Use a tmpfs for the writable store instead of writing to the VM's - own filesystem. - ''; - }; + virtualisation.writableStoreUseTmpfs = + mkOption { + type = types.bool; + default = true; + description = '' + Use a tmpfs for the writable store instead of writing to the VM's + own filesystem. + ''; + }; - networking.primaryIPAddress = mkOption { - type = types.str; - default = ""; - internal = true; - description = "Primary IP address used in /etc/hosts."; - }; + networking.primaryIPAddress = + mkOption { + type = types.str; + default = ""; + internal = true; + description = "Primary IP address used in /etc/hosts."; + }; - networking.primaryIPv6Address = mkOption { - type = types.str; - default = ""; - internal = true; - description = "Primary IPv6 address used in /etc/hosts."; - }; + networking.primaryIPv6Address = + mkOption { + type = types.str; + default = ""; + internal = true; + description = "Primary IPv6 address used in /etc/hosts."; + }; virtualisation.host.pkgs = mkOption { type = options.nixpkgs.pkgs.type; @@ -726,38 +666,31 @@ in }; virtualisation.qemu = { - package = mkOption { - type = types.package; - default = - if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then - hostPkgs.qemu_kvm - else - hostPkgs.qemu; - defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; - example = literalExpression "pkgs.qemu_test"; - description = "QEMU package to use."; - }; + package = + mkOption { + type = types.package; + default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu; + defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; + example = literalExpression "pkgs.qemu_test"; + description = "QEMU package to use."; + }; - options = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "-vga std" ]; - description = '' - Options passed to QEMU. - See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. - ''; - }; + options = + mkOption { + type = types.listOf types.str; + default = []; + example = [ "-vga std" ]; + description = '' + Options passed to QEMU. + See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. + ''; + }; consoles = mkOption { type = types.listOf types.str; - default = - let - consoles = [ - "${qemu-common.qemuSerialDevice},115200n8" - "tty0" - ]; - in - if cfg.graphics then consoles else reverseList consoles; + default = let + consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; + in if cfg.graphics then consoles else reverseList consoles; example = [ "console=tty1" ]; description = '' The output console devices to pass to the kernel command line via the @@ -770,158 +703,163 @@ in ''; }; - networkingOptions = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ - "-net nic,netdev=user.0,model=virtio" - "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" - ]; - description = '' - Networking-related command-line options that should be passed to qemu. - The default is to use userspace networking (SLiRP). - See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. + networkingOptions = + mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "-net nic,netdev=user.0,model=virtio" + "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" + ]; + description = '' + Networking-related command-line options that should be passed to qemu. + The default is to use userspace networking (SLiRP). + See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. - If you override this option, be advised to keep - `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) - to keep the default runtime behaviour. - ''; - }; + If you override this option, be advised to keep + `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) + to keep the default runtime behaviour. + ''; + }; - drives = mkOption { - type = types.listOf (types.submodule driveOpts); - description = "Drives passed to qemu."; - }; + drives = + mkOption { + type = types.listOf (types.submodule driveOpts); + description = "Drives passed to qemu."; + }; - diskInterface = mkOption { - type = types.enum [ - "virtio" - "scsi" - "ide" - ]; - default = "virtio"; - example = "scsi"; - description = "The interface used for the virtual hard disks."; - }; + diskInterface = + mkOption { + type = types.enum [ "virtio" "scsi" "ide" ]; + default = "virtio"; + example = "scsi"; + description = "The interface used for the virtual hard disks."; + }; - guestAgent.enable = mkOption { + guestAgent.enable = + mkOption { + type = types.bool; + default = true; + description = '' + Enable the Qemu guest agent. + ''; + }; + + virtioKeyboard = + mkOption { + type = types.bool; + default = true; + description = '' + Enable the virtio-keyboard device. + ''; + }; + }; + + virtualisation.useNixStoreImage = + mkOption { type = types.bool; - default = true; + default = false; description = '' - Enable the Qemu guest agent. + Build and use a disk image for the Nix store, instead of + accessing the host's one through 9p. + + For applications which do a lot of reads from the store, + this can drastically improve performance, but at the cost of + disk space and image build time. + + The Nix store image is built just-in-time right before the VM is + started. Because it does not produce another derivation, the image is + not cached between invocations and never lands in the store or binary + cache. + + If you want a full disk image with a partition table and a root + filesystem instead of only a store image, enable + {option}`virtualisation.useBootLoader` instead. ''; }; - virtioKeyboard = mkOption { + virtualisation.mountHostNixStore = + mkOption { type = types.bool; - default = true; + default = !cfg.useNixStoreImage && !cfg.useBootLoader; + defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; description = '' - Enable the virtio-keyboard device. + Mount the host Nix store as a 9p mount. ''; }; - }; - - virtualisation.useNixStoreImage = mkOption { - type = types.bool; - default = false; - description = '' - Build and use a disk image for the Nix store, instead of - accessing the host's one through 9p. - - For applications which do a lot of reads from the store, - this can drastically improve performance, but at the cost of - disk space and image build time. - - The Nix store image is built just-in-time right before the VM is - started. Because it does not produce another derivation, the image is - not cached between invocations and never lands in the store or binary - cache. - - If you want a full disk image with a partition table and a root - filesystem instead of only a store image, enable - {option}`virtualisation.useBootLoader` instead. - ''; - }; - - virtualisation.mountHostNixStore = mkOption { - type = types.bool; - default = !cfg.useNixStoreImage && !cfg.useBootLoader; - defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; - description = '' - Mount the host Nix store as a 9p mount. - ''; - }; virtualisation.directBoot = { - enable = mkOption { + enable = + mkOption { + type = types.bool; + default = !cfg.useBootLoader; + defaultText = "!cfg.useBootLoader"; + description = '' + If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. + Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) + + This is enabled by default. + If you want to test netboot, consider disabling this option. + Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. + + Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. + Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. + They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details + For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. + + This will not (re-)boot correctly into a system that has switched to a different configuration on disk. + ''; + }; + initrd = + mkOption { + type = types.str; + default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; + description = '' + In direct boot situations, you may want to influence the initrd to load + to use your own customized payload. + + This is useful if you want to test the netboot image without + testing the firmware or the loading part. + ''; + }; + }; + + virtualisation.useBootLoader = + mkOption { type = types.bool; - default = !cfg.useBootLoader; - defaultText = "!cfg.useBootLoader"; + default = false; description = '' - If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. - Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) + Use a boot loader to boot the system. + This allows, among other things, testing the boot loader. - This is enabled by default. - If you want to test netboot, consider disabling this option. - Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. + If disabled, the kernel and initrd are directly booted, + forgoing any bootloader. - Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. - Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. - They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details - For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. - - This will not (re-)boot correctly into a system that has switched to a different configuration on disk. - ''; + Check the documentation on {option}`virtualisation.directBoot.enable` for details. + ''; }; - initrd = mkOption { - type = types.str; - default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; - defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; + + virtualisation.useEFIBoot = + mkOption { + type = types.bool; + default = false; description = '' - In direct boot situations, you may want to influence the initrd to load - to use your own customized payload. - - This is useful if you want to test the netboot image without - testing the firmware or the loading part. - ''; - }; - }; - - virtualisation.useBootLoader = mkOption { - type = types.bool; - default = false; - description = '' - Use a boot loader to boot the system. - This allows, among other things, testing the boot loader. - - If disabled, the kernel and initrd are directly booted, - forgoing any bootloader. - - Check the documentation on {option}`virtualisation.directBoot.enable` for details. - ''; - }; - - virtualisation.useEFIBoot = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, the virtual machine will provide a EFI boot - manager. - useEFIBoot is ignored if useBootLoader == false. - ''; - }; + If enabled, the virtual machine will provide a EFI boot + manager. + useEFIBoot is ignored if useBootLoader == false. + ''; + }; virtualisation.efi = { OVMF = mkOption { type = types.package; - default = - (pkgs.OVMF.override { - secureBoot = cfg.useSecureBoot; - }).fd; - defaultText = '' - (pkgs.OVMF.override { - secureBoot = cfg.useSecureBoot; - }).fd''; + default = (pkgs.OVMF.override { + secureBoot = cfg.useSecureBoot; + }).fd; + defaultText = ''(pkgs.OVMF.override { + secureBoot = cfg.useSecureBoot; + }).fd''; description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; }; @@ -930,8 +868,8 @@ in default = cfg.efi.OVMF.firmware; defaultText = literalExpression "cfg.efi.OVMF.firmware"; description = '' - Firmware binary for EFI implementation, defaults to OVMF. - ''; + Firmware binary for EFI implementation, defaults to OVMF. + ''; }; variables = mkOption { @@ -939,9 +877,9 @@ in default = cfg.efi.OVMF.variables; defaultText = literalExpression "cfg.efi.OVMF.variables"; description = '' - Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. - Defaults to OVMF. - ''; + Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. + Defaults to OVMF. + ''; }; keepVariables = mkOption { @@ -959,16 +897,13 @@ in deviceModel = mkOption { type = types.str; - default = ( - { - "i686-linux" = "tpm-tis"; - "x86_64-linux" = "tpm-tis"; - "ppc64-linux" = "tpm-spapr"; - "armv7-linux" = "tpm-tis-device"; - "aarch64-linux" = "tpm-tis-device"; - } - .${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU") - ); + default = ({ + "i686-linux" = "tpm-tis"; + "x86_64-linux" = "tpm-tis"; + "ppc64-linux" = "tpm-spapr"; + "armv7-linux" = "tpm-tis-device"; + "aarch64-linux" = "tpm-tis-device"; + }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")); defaultText = '' Based on the guest platform Linux system: @@ -981,97 +916,97 @@ in }; }; - virtualisation.useDefaultFilesystems = mkOption { - type = types.bool; - default = true; - description = '' - If enabled, the boot disk of the virtual machine will be - formatted and mounted with the default filesystems for - testing. Swap devices and LUKS will be disabled. + virtualisation.useDefaultFilesystems = + mkOption { + type = types.bool; + default = true; + description = '' + If enabled, the boot disk of the virtual machine will be + formatted and mounted with the default filesystems for + testing. Swap devices and LUKS will be disabled. - If disabled, a root filesystem has to be specified and - formatted (for example in the initial ramdisk). - ''; - }; + If disabled, a root filesystem has to be specified and + formatted (for example in the initial ramdisk). + ''; + }; - virtualisation.useSecureBoot = mkOption { - type = types.bool; - default = false; - description = '' - Enable Secure Boot support in the EFI firmware. - ''; - }; + virtualisation.useSecureBoot = + mkOption { + type = types.bool; + default = false; + description = '' + Enable Secure Boot support in the EFI firmware. + ''; + }; - virtualisation.bios = mkOption { - type = types.nullOr types.package; - default = null; - description = '' - An alternate BIOS (such as `qboot`) with which to start the VM. - Should contain a file named `bios.bin`. - If `null`, QEMU's builtin SeaBIOS will be used. - ''; - }; + virtualisation.bios = + mkOption { + type = types.nullOr types.package; + default = null; + description = '' + An alternate BIOS (such as `qboot`) with which to start the VM. + Should contain a file named `bios.bin`. + If `null`, QEMU's builtin SeaBIOS will be used. + ''; + }; - virtualisation.useHostCerts = mkOption { - type = types.bool; - default = false; - description = '' - If enabled, when `NIX_SSL_CERT_FILE` is set on the host, - pass the CA certificates from the host to the VM. - ''; - }; + virtualisation.useHostCerts = + mkOption { + type = types.bool; + default = false; + description = '' + If enabled, when `NIX_SSL_CERT_FILE` is set on the host, + pass the CA certificates from the host to the VM. + ''; + }; }; config = { assertions = - lib.concatLists ( - lib.flip lib.imap cfg.forwardPorts ( - i: rule: [ - { - assertion = rule.from == "guest" -> rule.proto == "tcp"; - message = '' + lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: + [ + { assertion = rule.from == "guest" -> rule.proto == "tcp"; + message = + '' Invalid virtualisation.forwardPorts..proto: Guest forwarding supports only TCP connections. ''; - } - { - assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; - message = '' + } + { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; + message = + '' Invalid virtualisation.forwardPorts..guest.address: The address must be in the default VLAN (10.0.2.0/24). ''; - } - ] - ) - ) - ++ [ - { - assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; - message = '' - virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. - ''; - } - { - assertion = - cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; - message = '' - You changed the default of `virtualisation.directBoot.initrd` but you are not - using QEMU direct boot. This initrd will not be used in your current - boot configuration. + } + ])) ++ [ + { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; + message = '' + virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. + ''; + } + { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; + message = + '' + You changed the default of `virtualisation.directBoot.initrd` but you are not + using QEMU direct boot. This initrd will not be used in your current + boot configuration. - Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. + Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. - If you have a more advanced usecase, please open an issue or a pull request. - ''; - } - ]; + If you have a more advanced usecase, please open an issue or a pull request. + ''; + } + ]; - warnings = optional (cfg.directBoot.enable && cfg.useBootLoader) '' - You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering - `useBootLoader` useless. You might want to disable one of those options. - ''; + warnings = + optional (cfg.directBoot.enable && cfg.useBootLoader) + '' + You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering + `useBootLoader` useless. You might want to disable one of those options. + ''; # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs. @@ -1089,11 +1024,12 @@ in # allow `system.build.toplevel' to be included. (If we had a direct # reference to ${regInfo} here, then we would get a cyclic # dependency.) - boot.postBootCommands = lib.mkIf config.nix.enable '' - if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then - ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} - fi - ''; + boot.postBootCommands = lib.mkIf config.nix.enable + '' + if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then + ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} + fi + ''; boot.initrd.availableKernelModules = optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" @@ -1130,20 +1066,14 @@ in virtualisation.qemu.networkingOptions = let - forwardingOptions = flip concatMapStrings cfg.forwardPorts ( - { - proto, - from, - host, - guest, - }: - if from == "host" then - "hostfwd=${proto}:${host.address}:${toString host.port}-" - + "${guest.address}:${toString guest.port}," - else - "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" - + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," - ); + forwardingOptions = flip concatMapStrings cfg.forwardPorts + ({ proto, from, host, guest }: + if from == "host" + then "hostfwd=${proto}:${host.address}:${toString host.port}-" + + "${guest.address}:${toString guest.port}," + else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," + ); restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,"; in [ @@ -1156,29 +1086,20 @@ in "-device virtio-keyboard" ]) (mkIf pkgs.stdenv.hostPlatform.isx86 [ - "-usb" - "-device usb-tablet,bus=usb-bus.0" + "-usb" "-device usb-tablet,bus=usb-bus.0" ]) (mkIf pkgs.stdenv.hostPlatform.isAarch [ - "-device virtio-gpu-pci" - "-device usb-ehci,id=usb0" - "-device usb-kbd" - "-device usb-tablet" + "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" + ]) + (let + alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); + # Replace all non-alphanumeric characters with underscores + sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); + in mkIf cfg.directBoot.enable [ + "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" + "-initrd ${cfg.directBoot.initrd}" + ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' ]) - ( - let - alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); - # Replace all non-alphanumeric characters with underscores - sanitizeShellIdent = - s: - concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); - in - mkIf cfg.directBoot.enable [ - "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" - "-initrd ${cfg.directBoot.initrd}" - ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' - ] - ) (mkIf cfg.useEFIBoot [ "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}" "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS" @@ -1195,32 +1116,26 @@ in "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" ]) (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [ - "-machine" - "q35,smm=on" - "-global" - "driver=cfi.pflash01,property=secure,value=on" + "-machine" "q35,smm=on" + "-global" "driver=cfi.pflash01,property=secure,value=on" ]) ]; virtualisation.qemu.drives = mkMerge [ - (mkIf (cfg.diskImage != null) [ - { - name = "root"; - file = ''"$NIX_DISK_IMAGE"''; - driveExtraOpts.cache = "writeback"; - driveExtraOpts.werror = "report"; - deviceExtraOpts.bootindex = "1"; - deviceExtraOpts.serial = rootDriveSerialAttr; - } - ]) - (mkIf cfg.useNixStoreImage [ - { - name = "nix-store"; - file = ''"$TMPDIR"/store.img''; - deviceExtraOpts.bootindex = "2"; - driveExtraOpts.format = "raw"; - } - ]) + (mkIf (cfg.diskImage != null) [{ + name = "root"; + file = ''"$NIX_DISK_IMAGE"''; + driveExtraOpts.cache = "writeback"; + driveExtraOpts.werror = "report"; + deviceExtraOpts.bootindex = "1"; + deviceExtraOpts.serial = rootDriveSerialAttr; + }]) + (mkIf cfg.useNixStoreImage [{ + name = "nix-store"; + file = ''"$TMPDIR"/store.img''; + deviceExtraOpts.bootindex = "2"; + driveExtraOpts.format = "raw"; + }]) (imap0 (idx: _: { file = "$(pwd)/empty${toString idx}.qcow2"; driveExtraOpts.werror = "report"; @@ -1234,115 +1149,91 @@ in # override by setting `virtualisation.fileSystems = lib.mkForce { };`. fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems); - virtualisation.diskSizeAutoSupported = false; - - virtualisation.fileSystems = - let - mkSharedDir = tag: share: { + virtualisation.fileSystems = let + mkSharedDir = tag: share: + { name = share.target; value.device = tag; value.fsType = "9p"; value.neededForBoot = true; - value.options = [ - "trans=virtio" - "version=9p2000.L" - "msize=${toString cfg.msize}" - ] ++ lib.optional (tag == "nix-store") "cache=loose"; + value.options = + [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] + ++ lib.optional (tag == "nix-store") "cache=loose"; }; - in - lib.mkMerge [ - (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) - { - "/" = lib.mkIf cfg.useDefaultFilesystems ( - if cfg.diskImage == null then - { - device = "tmpfs"; - fsType = "tmpfs"; - } - else - { - device = cfg.rootDevice; - fsType = "ext4"; - } - ); - "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { - device = "tmpfs"; - fsType = "tmpfs"; - neededForBoot = true; - # Sync with systemd's tmp.mount; - options = [ - "mode=1777" - "strictatime" - "nosuid" - "nodev" - "size=${toString config.boot.tmp.tmpfsSize}" - ]; + in lib.mkMerge [ + (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) + { + "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then { + device = "tmpfs"; + fsType = "tmpfs"; + } else { + device = cfg.rootDevice; + fsType = "ext4"; + }); + "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { + device = "tmpfs"; + fsType = "tmpfs"; + neededForBoot = true; + # Sync with systemd's tmp.mount; + options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; + }; + "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then { + overlay = { + lowerdir = [ "/nix/.ro-store" ]; + upperdir = "/nix/.rw-store/upper"; + workdir = "/nix/.rw-store/work"; }; - "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) ( - if cfg.writableStore then - { - overlay = { - lowerdir = [ "/nix/.ro-store" ]; - upperdir = "/nix/.rw-store/upper"; - workdir = "/nix/.rw-store/work"; - }; - } - else - { - device = "/nix/.ro-store"; - options = [ "bind" ]; - } - ); - "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { - device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; - fsType = "erofs"; - neededForBoot = true; - options = [ "ro" ]; - }; - "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { - fsType = "tmpfs"; - options = [ "mode=0755" ]; - neededForBoot = true; - }; - "${config.boot.loader.efi.efiSysMountPoint}" = - lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) - { - device = cfg.bootPartition; - fsType = "vfat"; - }; - } - ]; + } else { + device = "/nix/.ro-store"; + options = [ "bind" ]; + }); + "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { + device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; + fsType = "erofs"; + neededForBoot = true; + options = [ "ro" ]; + }; + "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { + fsType = "tmpfs"; + options = [ "mode=0755" ]; + neededForBoot = true; + }; + "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) { + device = cfg.bootPartition; + fsType = "vfat"; + }; + } + ]; swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; - boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) { }; + boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; # Don't run ntpd in the guest. It should get the correct time from KVM. services.timesyncd.enable = false; services.qemuGuest.enable = cfg.qemu.guestAgent.enable; - system.build.vm = - hostPkgs.runCommand "nixos-vm" - { - preferLocalBuild = true; - meta.mainProgram = "run-${config.system.name}-vm"; - } - '' - mkdir -p $out/bin - ln -s ${config.system.build.toplevel} $out/system - ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm - ''; + system.build.vm = hostPkgs.runCommand "nixos-vm" { + preferLocalBuild = true; + meta.mainProgram = "run-${config.system.name}-vm"; + } + '' + mkdir -p $out/bin + ln -s ${config.system.build.toplevel} $out/system + ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm + ''; # When building a regular system configuration, override whatever # video driver the host uses. services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; services.xserver.defaultDepth = mkVMOverride 0; services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; - services.xserver.monitorSection = '' - # Set a higher refresh rate so that resolutions > 800x600 work. - HorizSync 30-140 - VertRefresh 50-160 - ''; + services.xserver.monitorSection = + '' + # Set a higher refresh rate so that resolutions > 800x600 work. + HorizSync 30-140 + VertRefresh 50-160 + ''; # Wireless won't work in the VM. networking.wireless.enable = mkVMOverride false; @@ -1353,10 +1244,8 @@ in networking.usePredictableInterfaceNames = false; - system.requiredKernelConfig = - with config.lib.kernelConfig; - [ - (isEnabled "VIRTIO_BLK") + system.requiredKernelConfig = with config.lib.kernelConfig; + [ (isEnabled "VIRTIO_BLK") (isEnabled "VIRTIO_PCI") (isEnabled "VIRTIO_NET") (isEnabled "EXT4_FS") @@ -1368,12 +1257,10 @@ in (isYes "NET_CORE") (isYes "INET") (isYes "NETWORK_FILESYSTEMS") - ] - ++ optionals (!cfg.graphics) [ + ] ++ optionals (!cfg.graphics) [ (isYes "SERIAL_8250_CONSOLE") (isYes "SERIAL_8250") - ] - ++ optionals (cfg.writableStore) [ + ] ++ optionals (cfg.writableStore) [ (isEnabled "OVERLAY_FS") ]; diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix index de9be384a7fe..1c8b9b99c01c 100644 --- a/nixos/modules/virtualisation/virtualbox-image.nix +++ b/nixos/modules/virtualisation/virtualbox-image.nix @@ -1,37 +1,23 @@ -{ - config, - lib, - pkgs, - ... -}: +{ config, lib, pkgs, ... }: with lib; let cfg = config.virtualbox; - virtualisationOptions = import ./virtualisation-options.nix; -in -{ - imports = [ - virtualisationOptions.diskSize - (lib.mkRenamedOptionModuleWith { - sinceRelease = 2411; - from = [ - "virtualisation" - "virtualbox" - "baseImageSize" - ]; - to = [ - "virtualisation" - "diskSize" - ]; - }) - ]; +in { options = { virtualbox = { + baseImageSize = mkOption { + type = with types; either (enum [ "auto" ]) int; + default = "auto"; + example = 50 * 1024; + description = '' + The size of the VirtualBox base image in MiB. + ''; + }; baseImageFreeSpace = mkOption { type = with types; int; default = 30 * 1024; @@ -68,14 +54,7 @@ in ''; }; params = mkOption { - type = - with types; - attrsOf (oneOf [ - str - int - bool - (listOf str) - ]); + type = with types; attrsOf (oneOf [ str int bool (listOf str) ]); example = { audio = "alsa"; rtcuseutc = "on"; @@ -88,21 +67,11 @@ in ''; }; exportParams = mkOption { - type = - with types; - listOf (oneOf [ - str - int - bool - (listOf str) - ]); + type = with types; listOf (oneOf [ str int bool (listOf str) ]); example = [ - "--vsys" - "0" - "--vendor" - "ACME Inc." + "--vsys" "0" "--vendor" "ACME Inc." ]; - default = [ ]; + default = []; description = '' Parameters passed to the Virtualbox export command. @@ -120,25 +89,23 @@ in mountPoint = "/home/demo/storage"; size = 100 * 1024; }; - type = types.nullOr ( - types.submodule { - options = { - size = mkOption { - type = types.int; - description = "Size in MiB"; - }; - label = mkOption { - type = types.str; - default = "vm-extra-storage"; - description = "Label for the disk partition"; - }; - mountPoint = mkOption { - type = types.str; - description = "Path where to mount this disk."; - }; + type = types.nullOr (types.submodule { + options = { + size = mkOption { + type = types.int; + description = "Size in MiB"; }; - } - ); + label = mkOption { + type = types.str; + default = "vm-extra-storage"; + description = "Label for the disk partition"; + }; + mountPoint = mkOption { + type = types.str; + description = "Path where to mount this disk."; + }; + }; + }); }; postExportCommands = mkOption { type = types.lines; @@ -158,14 +125,7 @@ in ''; }; storageController = mkOption { - type = - with types; - attrsOf (oneOf [ - str - int - bool - (listOf str) - ]); + type = with types; attrsOf (oneOf [ str int bool (listOf str) ]); example = { name = "SCSI"; add = "scsi"; @@ -192,8 +152,6 @@ in config = { - virtualisation.diskSize = lib.mkDefault (50 * 1024); - virtualbox.params = mkMerge [ (mapAttrs (name: mkDefault) { acpi = "on"; @@ -217,83 +175,80 @@ in inherit pkgs lib config; partitionTableType = "legacy"; - inherit (config.virtualisation) diskSize; + diskSize = cfg.baseImageSize; additionalSpace = "${toString cfg.baseImageFreeSpace}M"; - postVM = '' - export HOME=$PWD - export PATH=${pkgs.virtualbox}/bin:$PATH + postVM = + '' + export HOME=$PWD + export PATH=${pkgs.virtualbox}/bin:$PATH - echo "converting image to VirtualBox format..." - VBoxManage convertfromraw $diskImage disk.vdi + echo "converting image to VirtualBox format..." + VBoxManage convertfromraw $diskImage disk.vdi - ${optionalString (cfg.extraDisk != null) '' - echo "creating extra disk: data-disk.raw" - dataDiskImage=data-disk.raw - truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage + ${optionalString (cfg.extraDisk != null) '' + echo "creating extra disk: data-disk.raw" + dataDiskImage=data-disk.raw + truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage - parted --script $dataDiskImage -- \ - mklabel msdos \ - mkpart primary ext4 1MiB -1 - eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) - mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K - echo "creating extra disk: data-disk.vdi" - VBoxManage convertfromraw $dataDiskImage data-disk.vdi - ''} + parted --script $dataDiskImage -- \ + mklabel msdos \ + mkpart primary ext4 1MiB -1 + eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) + mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K + echo "creating extra disk: data-disk.vdi" + VBoxManage convertfromraw $dataDiskImage data-disk.vdi + ''} - echo "creating VirtualBox VM..." - vmName="${cfg.vmName}"; - VBoxManage createvm --name "$vmName" --register \ - --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} - VBoxManage modifyvm "$vmName" \ - --memory ${toString cfg.memorySize} \ - ${lib.cli.toGNUCommandLineShell { } cfg.params} - VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ - --medium disk.vdi - ${optionalString (cfg.extraDisk != null) '' - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ - --medium data-disk.vdi - ''} + echo "creating VirtualBox VM..." + vmName="${cfg.vmName}"; + VBoxManage createvm --name "$vmName" --register \ + --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} + VBoxManage modifyvm "$vmName" \ + --memory ${toString cfg.memorySize} \ + ${lib.cli.toGNUCommandLineShell { } cfg.params} + VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ + --medium disk.vdi + ${optionalString (cfg.extraDisk != null) '' + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ + --medium data-disk.vdi + ''} - echo "exporting VirtualBox VM..." - mkdir -p $out - fn="$out/${cfg.vmFileName}" - VBoxManage export "$vmName" --output "$fn" --options manifest ${escapeShellArgs cfg.exportParams} - ${cfg.postExportCommands} + echo "exporting VirtualBox VM..." + mkdir -p $out + fn="$out/${cfg.vmFileName}" + VBoxManage export "$vmName" --output "$fn" --options manifest ${escapeShellArgs cfg.exportParams} + ${cfg.postExportCommands} - rm -v $diskImage + rm -v $diskImage - mkdir -p $out/nix-support - echo "file ova $fn" >> $out/nix-support/hydra-build-products - ''; + mkdir -p $out/nix-support + echo "file ova $fn" >> $out/nix-support/hydra-build-products + ''; }; - fileSystems = - { - "/" = { - device = "/dev/disk/by-label/nixos"; - autoResize = true; - fsType = "ext4"; - }; - } - // (lib.optionalAttrs (cfg.extraDisk != null) { - ${cfg.extraDisk.mountPoint} = { - device = "/dev/disk/by-label/" + cfg.extraDisk.label; - autoResize = true; - fsType = "ext4"; - }; - }); + fileSystems = { + "/" = { + device = "/dev/disk/by-label/nixos"; + autoResize = true; + fsType = "ext4"; + }; + } // (lib.optionalAttrs (cfg.extraDisk != null) { + ${cfg.extraDisk.mountPoint} = { + device = "/dev/disk/by-label/" + cfg.extraDisk.label; + autoResize = true; + fsType = "ext4"; + }; + }); boot.growPartition = true; boot.loader.grub.device = "/dev/sda"; - swapDevices = [ - { - device = "/var/swap"; - size = 2048; - } - ]; + swapDevices = [{ + device = "/var/swap"; + size = 2048; + }]; virtualisation.virtualbox.guest.enable = true; diff --git a/nixos/modules/virtualisation/virtualisation-options.nix b/nixos/modules/virtualisation/virtualisation-options.nix deleted file mode 100644 index 5040a7916d84..000000000000 --- a/nixos/modules/virtualisation/virtualisation-options.nix +++ /dev/null @@ -1,60 +0,0 @@ -# This modules declares shared options for virtual machines, -# containers and anything else in `virtualisation`. -# -# This is useful to declare e.g. defaults for -# `virtualisation.diskSize` once, while building multiple -# different image formats of a NixOS configuration. -# -# Additional options can be migrated over time from -# `modules/virtualisation/qemu-vm.nix` and others. -# Please keep defaults and descriptions here generic -# and independent of i.e. hypervisor-specific notes -# and defaults where. -# Those can be added in the consuming modules where needed. -# needed. -let - _file = ./virtualisation-options.nix; - key = _file; -in -{ - diskSize = - { lib, config, ... }: - let - t = lib.types; - in - { - inherit _file key; - - options = { - virtualisation.diskSizeAutoSupported = lib.mkOption { - type = t.bool; - default = true; - description = '' - Whether the current image builder or vm runner supports `virtualisation.diskSize = "auto".` - ''; - internal = true; - }; - - virtualisation.diskSize = lib.mkOption { - type = t.either (t.enum [ "auto" ]) t.ints.positive; - default = "auto"; - description = '' - The disk size in megabytes of the virtual machine. - ''; - }; - }; - - config = - let - inherit (config.virtualisation) diskSize diskSizeAutoSupported; - in - { - assertions = [ - { - assertion = diskSize != "auto" || diskSizeAutoSupported; - message = "Setting virtualisation.diskSize to `auto` is not supported by the current image build or vm runner; use an explicit size."; - } - ]; - }; - }; -} diff --git a/nixos/release.nix b/nixos/release.nix index dc131dcba52c..eeca73ea4c55 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -312,7 +312,7 @@ in rec { [ configuration versionModule ./maintainers/scripts/ec2/amazon-image.nix - ({ ... }: { amazonImage.virtualisation.diskSize = "auto"; }) + ({ ... }: { amazonImage.sizeMB = "auto"; }) ]; }).config.system.build.amazonImage)