lol

modules/virtualisation: add shared options, merge various diskSize options (#341058)

authored by

Robert Hensing and committed by
GitHub
2ba15d4f 6385db14

+1649 -1250
+3
.git-blame-ignore-revs
··· 192 192 193 193 # percona: apply nixfmt 194 194 8d14fa2886fec877690c6d28cfcdba4503dbbcea 195 + 196 + # nixos/virtualisation: format image-related files 197 + 88b285c01d84de82c0b2b052fd28eaf6709c2d26
+130 -94
nixos/maintainers/scripts/ec2/amazon-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 let 4 - inherit (lib) mkOption optionalString types versionAtLeast; 9 + inherit (lib) 10 + mkOption 11 + optionalString 12 + types 13 + versionAtLeast 14 + ; 5 15 inherit (lib.options) literalExpression; 6 16 cfg = config.amazonImage; 7 17 amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios"; 8 - 9 - in { 10 - 11 - imports = [ ../../../modules/virtualisation/amazon-image.nix ]; 18 + in 19 + { 20 + imports = [ 21 + ../../../modules/virtualisation/amazon-image.nix 22 + ../../../modules/virtualisation/disk-size-option.nix 23 + (lib.mkRenamedOptionModuleWith { 24 + sinceRelease = 2411; 25 + from = [ 26 + "amazonImage" 27 + "sizeMB" 28 + ]; 29 + to = [ 30 + "virtualisation" 31 + "diskSize" 32 + ]; 33 + }) 34 + ]; 12 35 13 36 # Amazon recommends setting this to the highest possible value for a good EBS 14 37 # experience, which prior to 4.15 was 255. 15 38 # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes 16 39 config.boot.kernelParams = 17 - let timeout = 18 - if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" 19 - then "4294967295" 20 - else "255"; 21 - in [ "nvme_core.io_timeout=${timeout}" ]; 40 + let 41 + timeout = 42 + if versionAtLeast config.boot.kernelPackages.kernel.version "4.15" then "4294967295" else "255"; 43 + in 44 + [ "nvme_core.io_timeout=${timeout}" ]; 22 45 23 46 options.amazonImage = { 24 47 name = mkOption { ··· 34 57 } 35 58 ] 36 59 ''; 37 - default = []; 60 + default = [ ]; 38 61 description = '' 39 62 This option lists files to be copied to fixed locations in the 40 63 generated image. Glob patterns work. 41 64 ''; 42 65 }; 43 66 44 - sizeMB = mkOption { 45 - type = with types; either (enum [ "auto" ]) int; 46 - default = 3072; 47 - example = 8192; 48 - description = "The size in MB of the image"; 49 - }; 50 - 51 67 format = mkOption { 52 - type = types.enum [ "raw" "qcow2" "vpc" ]; 68 + type = types.enum [ 69 + "raw" 70 + "qcow2" 71 + "vpc" 72 + ]; 53 73 default = "vpc"; 54 74 description = "The image format to output"; 55 75 }; 56 76 }; 57 77 58 - config.system.build.amazonImage = let 59 - configFile = pkgs.writeText "configuration.nix" 60 - '' 78 + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault 79 + # to avoid breaking existing configs using that. 80 + config.virtualisation.diskSize = lib.mkOverride 1490 (3 * 1024); 81 + config.virtualisation.diskSizeAutoSupported = !config.ec2.zfs.enable; 82 + 83 + config.system.build.amazonImage = 84 + let 85 + configFile = pkgs.writeText "configuration.nix" '' 61 86 { modulesPath, ... }: { 62 87 imports = [ "''${modulesPath}/virtualisation/amazon-image.nix" ]; 63 88 ${optionalString config.ec2.efi '' ··· 70 95 } 71 96 ''; 72 97 73 - zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { 74 - inherit lib config configFile pkgs; 75 - inherit (cfg) contents format name; 98 + zfsBuilder = import ../../../lib/make-multi-disk-zfs-image.nix { 99 + inherit 100 + lib 101 + config 102 + configFile 103 + pkgs 104 + ; 105 + inherit (cfg) contents format name; 76 106 77 - includeChannel = true; 107 + includeChannel = true; 78 108 79 - bootSize = 1000; # 1G is the minimum EBS volume 109 + bootSize = 1000; # 1G is the minimum EBS volume 80 110 81 - rootSize = cfg.sizeMB; 82 - rootPoolProperties = { 83 - ashift = 12; 84 - autoexpand = "on"; 85 - }; 111 + rootSize = config.virtualisation.diskSize; 112 + rootPoolProperties = { 113 + ashift = 12; 114 + autoexpand = "on"; 115 + }; 86 116 87 - datasets = config.ec2.zfs.datasets; 117 + datasets = config.ec2.zfs.datasets; 88 118 89 - postVM = '' 90 - extension=''${rootDiskImage##*.} 91 - friendlyName=$out/${cfg.name} 92 - rootDisk="$friendlyName.root.$extension" 93 - bootDisk="$friendlyName.boot.$extension" 94 - mv "$rootDiskImage" "$rootDisk" 95 - mv "$bootDiskImage" "$bootDisk" 119 + postVM = '' 120 + extension=''${rootDiskImage##*.} 121 + friendlyName=$out/${cfg.name} 122 + rootDisk="$friendlyName.root.$extension" 123 + bootDisk="$friendlyName.boot.$extension" 124 + mv "$rootDiskImage" "$rootDisk" 125 + mv "$bootDiskImage" "$bootDisk" 96 126 97 - mkdir -p $out/nix-support 98 - echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products 99 - echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products 127 + mkdir -p $out/nix-support 128 + echo "file ${cfg.format} $bootDisk" >> $out/nix-support/hydra-build-products 129 + echo "file ${cfg.format} $rootDisk" >> $out/nix-support/hydra-build-products 100 130 101 - ${pkgs.jq}/bin/jq -n \ 102 - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ 103 - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ 104 - --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 105 - --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 106 - --arg boot_mode "${amiBootMode}" \ 107 - --arg root "$rootDisk" \ 108 - --arg boot "$bootDisk" \ 109 - '{} 110 - | .label = $system_label 111 - | .boot_mode = $boot_mode 112 - | .system = $system 113 - | .disks.boot.logical_bytes = $boot_logical_bytes 114 - | .disks.boot.file = $boot 115 - | .disks.root.logical_bytes = $root_logical_bytes 116 - | .disks.root.file = $root 117 - ' > $out/nix-support/image-info.json 118 - ''; 119 - }; 131 + ${pkgs.jq}/bin/jq -n \ 132 + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ 133 + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ 134 + --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 135 + --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 136 + --arg boot_mode "${amiBootMode}" \ 137 + --arg root "$rootDisk" \ 138 + --arg boot "$bootDisk" \ 139 + '{} 140 + | .label = $system_label 141 + | .boot_mode = $boot_mode 142 + | .system = $system 143 + | .disks.boot.logical_bytes = $boot_logical_bytes 144 + | .disks.boot.file = $boot 145 + | .disks.root.logical_bytes = $root_logical_bytes 146 + | .disks.root.file = $root 147 + ' > $out/nix-support/image-info.json 148 + ''; 149 + }; 120 150 121 - extBuilder = import ../../../lib/make-disk-image.nix { 122 - inherit lib config configFile pkgs; 151 + extBuilder = import ../../../lib/make-disk-image.nix { 152 + inherit 153 + lib 154 + config 155 + configFile 156 + pkgs 157 + ; 123 158 124 - inherit (cfg) contents format name; 159 + inherit (cfg) contents format name; 125 160 126 - fsType = "ext4"; 127 - partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; 161 + fsType = "ext4"; 162 + partitionTableType = if config.ec2.efi then "efi" else "legacy+gpt"; 128 163 129 - diskSize = cfg.sizeMB; 164 + inherit (config.virtualisation) diskSize; 130 165 131 - postVM = '' 132 - extension=''${diskImage##*.} 133 - friendlyName=$out/${cfg.name}.$extension 134 - mv "$diskImage" "$friendlyName" 135 - diskImage=$friendlyName 166 + postVM = '' 167 + extension=''${diskImage##*.} 168 + friendlyName=$out/${cfg.name}.$extension 169 + mv "$diskImage" "$friendlyName" 170 + diskImage=$friendlyName 136 171 137 - mkdir -p $out/nix-support 138 - echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products 172 + mkdir -p $out/nix-support 173 + echo "file ${cfg.format} $diskImage" >> $out/nix-support/hydra-build-products 139 174 140 - ${pkgs.jq}/bin/jq -n \ 141 - --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ 142 - --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ 143 - --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 144 - --arg boot_mode "${amiBootMode}" \ 145 - --arg file "$diskImage" \ 146 - '{} 147 - | .label = $system_label 148 - | .boot_mode = $boot_mode 149 - | .system = $system 150 - | .logical_bytes = $logical_bytes 151 - | .file = $file 152 - | .disks.root.logical_bytes = $logical_bytes 153 - | .disks.root.file = $file 154 - ' > $out/nix-support/image-info.json 155 - ''; 156 - }; 157 - in if config.ec2.zfs.enable then zfsBuilder else extBuilder; 175 + ${pkgs.jq}/bin/jq -n \ 176 + --arg system_label ${lib.escapeShellArg config.system.nixos.label} \ 177 + --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \ 178 + --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \ 179 + --arg boot_mode "${amiBootMode}" \ 180 + --arg file "$diskImage" \ 181 + '{} 182 + | .label = $system_label 183 + | .boot_mode = $boot_mode 184 + | .system = $system 185 + | .logical_bytes = $logical_bytes 186 + | .file = $file 187 + | .disks.root.logical_bytes = $logical_bytes 188 + | .disks.root.file = $file 189 + ' > $out/nix-support/image-info.json 190 + ''; 191 + }; 192 + in 193 + if config.ec2.zfs.enable then zfsBuilder else extBuilder; 158 194 159 195 meta.maintainers = with lib.maintainers; [ arianvp ]; 160 196 }
+35 -18
nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
··· 1 1 # nix-build '<nixpkgs/nixos>' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }" 2 2 3 - { config, lib, pkgs, ... }: 3 + { 4 + config, 5 + lib, 6 + pkgs, 7 + ... 8 + }: 4 9 let 5 10 inherit (lib) mkOption types; 6 11 copyChannel = true; ··· 10 15 { 11 16 imports = [ 12 17 ../../../modules/virtualisation/openstack-config.nix 18 + ../../../modules/virtualisation/disk-size-option.nix 19 + (lib.mkRenamedOptionModuleWith { 20 + sinceRelease = 2411; 21 + from = [ 22 + "openstackImage" 23 + "sizeMB" 24 + ]; 25 + to = [ 26 + "virtualisation" 27 + "diskSize" 28 + ]; 29 + }) 13 30 ] ++ (lib.optional copyChannel ../../../modules/installer/cd-dvd/channel.nix); 14 - 15 31 16 32 options.openstackImage = { 17 33 name = mkOption { ··· 22 38 23 39 ramMB = mkOption { 24 40 type = types.int; 25 - default = 1024; 41 + default = (3 * 1024); 26 42 description = "RAM allocation for build VM"; 27 43 }; 28 44 29 - sizeMB = mkOption { 30 - type = types.int; 31 - default = 8192; 32 - description = "The size in MB of the image"; 33 - }; 34 - 35 45 format = mkOption { 36 - type = types.enum [ "raw" "qcow2" ]; 46 + type = types.enum [ 47 + "raw" 48 + "qcow2" 49 + ]; 37 50 default = "qcow2"; 38 51 description = "The image format to output"; 39 52 }; ··· 54 67 }; 55 68 }; 56 69 70 + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault 71 + # to avoid breaking existing configs using that. 72 + virtualisation.diskSize = lib.mkOverride 1490 (8 * 1024); 73 + virtualisation.diskSizeAutoSupported = false; 74 + 57 75 system.build.openstackImage = import ../../../lib/make-single-disk-zfs-image.nix { 58 76 inherit lib config; 59 77 inherit (cfg) contents format name; 60 78 pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package 61 79 62 - configFile = pkgs.writeText "configuration.nix" 63 - '' 64 - { modulesPath, ... }: { 65 - imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; 66 - openstack.zfs.enable = true; 67 - } 68 - ''; 80 + configFile = pkgs.writeText "configuration.nix" '' 81 + { modulesPath, ... }: { 82 + imports = [ "''${modulesPath}/virtualisation/openstack-config.nix" ]; 83 + openstack.zfs.enable = true; 84 + } 85 + ''; 69 86 70 87 includeChannel = copyChannel; 71 88 72 89 bootSize = 1000; 73 90 memSize = cfg.ramMB; 74 - rootSize = cfg.sizeMB; 91 + rootSize = config.virtualisation.diskSize; 75 92 rootPoolProperties = { 76 93 ashift = 12; 77 94 autoexpand = "on";
+30 -13
nixos/modules/virtualisation/azure-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 with lib; 4 9 let 5 10 cfg = config.virtualisation.azureImage; 6 11 in 7 12 { 8 - imports = [ ./azure-common.nix ]; 13 + imports = [ 14 + ./azure-common.nix 15 + ./disk-size-option.nix 16 + (lib.mkRenamedOptionModuleWith { 17 + sinceRelease = 2411; 18 + from = [ 19 + "virtualisation" 20 + "azureImage" 21 + "diskSize" 22 + ]; 23 + to = [ 24 + "virtualisation" 25 + "diskSize" 26 + ]; 27 + }) 28 + ]; 9 29 10 30 options.virtualisation.azureImage = { 11 - diskSize = mkOption { 12 - type = with types; either (enum [ "auto" ]) int; 13 - default = "auto"; 14 - example = 2048; 15 - description = '' 16 - Size of disk image. Unit is MB. 17 - ''; 18 - }; 19 - 20 31 bootSize = mkOption { 21 32 type = types.int; 22 33 default = 256; ··· 35 46 }; 36 47 37 48 vmGeneration = mkOption { 38 - type = with types; enum [ "v1" "v2" ]; 49 + type = 50 + with types; 51 + enum [ 52 + "v1" 53 + "v2" 54 + ]; 39 55 default = "v1"; 40 56 description = '' 41 57 VM Generation to use. ··· 57 73 bootSize = "${toString cfg.bootSize}M"; 58 74 partitionTableType = if cfg.vmGeneration == "v2" then "efi" else "legacy"; 59 75 60 - inherit (cfg) diskSize contents; 76 + inherit (cfg) contents; 77 + inherit (config.virtualisation) diskSize; 61 78 inherit config lib pkgs; 62 79 }; 63 80 };
+48 -26
nixos/modules/virtualisation/digital-ocean-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 with lib; 4 9 let ··· 6 11 in 7 12 { 8 13 9 - imports = [ ./digital-ocean-config.nix ]; 14 + imports = [ 15 + ./digital-ocean-config.nix 16 + ./disk-size-option.nix 17 + (lib.mkRenamedOptionModuleWith { 18 + sinceRelease = 2411; 19 + from = [ 20 + "virtualisation" 21 + "digitalOceanImage" 22 + "diskSize" 23 + ]; 24 + to = [ 25 + "virtualisation" 26 + "diskSize" 27 + ]; 28 + }) 29 + ]; 10 30 11 31 options = { 12 - virtualisation.digitalOceanImage.diskSize = mkOption { 13 - type = with types; either (enum [ "auto" ]) int; 14 - default = "auto"; 15 - example = 4096; 16 - description = '' 17 - Size of disk image. Unit is MB. 18 - ''; 19 - }; 20 - 21 32 virtualisation.digitalOceanImage.configFile = mkOption { 22 33 type = with types; nullOr path; 23 34 default = null; ··· 31 42 }; 32 43 33 44 virtualisation.digitalOceanImage.compressionMethod = mkOption { 34 - type = types.enum [ "gzip" "bzip2" ]; 45 + type = types.enum [ 46 + "gzip" 47 + "bzip2" 48 + ]; 35 49 default = "gzip"; 36 50 example = "bzip2"; 37 51 description = '' ··· 44 58 45 59 #### implementation 46 60 config = { 47 - 48 61 system.build.digitalOceanImage = import ../../lib/make-disk-image.nix { 49 62 name = "digital-ocean-image"; 50 63 format = "qcow2"; 51 - postVM = let 52 - compress = { 53 - "gzip" = "${pkgs.gzip}/bin/gzip"; 54 - "bzip2" = "${pkgs.bzip2}/bin/bzip2"; 55 - }.${cfg.compressionMethod}; 56 - in '' 57 - ${compress} $diskImage 58 - ''; 59 - configFile = if cfg.configFile == null 60 - then config.virtualisation.digitalOcean.defaultConfigFile 61 - else cfg.configFile; 62 - inherit (cfg) diskSize; 64 + postVM = 65 + let 66 + compress = 67 + { 68 + "gzip" = "${pkgs.gzip}/bin/gzip"; 69 + "bzip2" = "${pkgs.bzip2}/bin/bzip2"; 70 + } 71 + .${cfg.compressionMethod}; 72 + in 73 + '' 74 + ${compress} $diskImage 75 + ''; 76 + configFile = 77 + if cfg.configFile == null then 78 + config.virtualisation.digitalOcean.defaultConfigFile 79 + else 80 + cfg.configFile; 81 + inherit (config.virtualisation) diskSize; 63 82 inherit config lib pkgs; 64 83 }; 65 84 66 85 }; 67 86 68 - meta.maintainers = with maintainers; [ arianvp eamsden ]; 87 + meta.maintainers = with maintainers; [ 88 + arianvp 89 + eamsden 90 + ]; 69 91 70 92 }
+38
nixos/modules/virtualisation/disk-size-option.nix
··· 1 + { lib, config, ... }: 2 + let 3 + t = lib.types; 4 + in 5 + { 6 + options = { 7 + virtualisation.diskSizeAutoSupported = lib.mkOption { 8 + type = t.bool; 9 + default = true; 10 + description = '' 11 + Whether the current image builder or vm runner supports `virtualisation.diskSize = "auto".` 12 + ''; 13 + internal = true; 14 + }; 15 + 16 + virtualisation.diskSize = lib.mkOption { 17 + type = t.either (t.enum [ "auto" ]) t.ints.positive; 18 + default = if config.virtualisation.diskSizeAutoSupported then "auto" else 1024; 19 + defaultText = "\"auto\" if diskSizeAutoSupported, else 1024"; 20 + description = '' 21 + The disk size in megabytes of the virtual machine. 22 + ''; 23 + }; 24 + }; 25 + 26 + config = 27 + let 28 + inherit (config.virtualisation) diskSize diskSizeAutoSupported; 29 + in 30 + { 31 + assertions = [ 32 + { 33 + assertion = diskSize != "auto" || diskSizeAutoSupported; 34 + message = "Setting virtualisation.diskSize to `auto` is not supported by the current image build or vm runner; use an explicit size."; 35 + } 36 + ]; 37 + }; 38 + }
+30 -13
nixos/modules/virtualisation/google-compute-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 with lib; 4 9 let ··· 14 19 in 15 20 { 16 21 17 - imports = [ ./google-compute-config.nix ]; 22 + imports = [ 23 + ./google-compute-config.nix 24 + ./disk-size-option.nix 25 + (lib.mkRenamedOptionModuleWith { 26 + sinceRelease = 2411; 27 + from = [ 28 + "virtualisation" 29 + "googleComputeImage" 30 + "diskSize" 31 + ]; 32 + to = [ 33 + "virtualisation" 34 + "diskSize" 35 + ]; 36 + }) 37 + ]; 18 38 19 39 options = { 20 - virtualisation.googleComputeImage.diskSize = mkOption { 21 - type = with types; either (enum [ "auto" ]) int; 22 - default = "auto"; 23 - example = 1536; 24 - description = '' 25 - Size of disk image. Unit is MB. 26 - ''; 27 - }; 28 - 29 40 virtualisation.googleComputeImage.configFile = mkOption { 30 41 type = with types; nullOr str; 31 42 default = null; ··· 64 75 system.build.googleComputeImage = import ../../lib/make-disk-image.nix { 65 76 name = "google-compute-image"; 66 77 postVM = '' 67 - PATH=$PATH:${with pkgs; lib.makeBinPath [ gnutar gzip ]} 78 + PATH=$PATH:${ 79 + with pkgs; 80 + lib.makeBinPath [ 81 + gnutar 82 + gzip 83 + ] 84 + } 68 85 pushd $out 69 86 mv $diskImage disk.raw 70 87 tar -Sc disk.raw | gzip -${toString cfg.compressionLevel} > \ ··· 75 92 format = "raw"; 76 93 configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; 77 94 partitionTableType = if cfg.efi then "efi" else "legacy"; 78 - inherit (cfg) diskSize; 95 + inherit (config.virtualisation) diskSize; 79 96 inherit config lib pkgs; 80 97 }; 81 98
+28 -11
nixos/modules/virtualisation/hyperv-image.nix
··· 1 - { config, pkgs, lib, ... }: 1 + { 2 + config, 3 + pkgs, 4 + lib, 5 + ... 6 + }: 2 7 3 8 with lib; 4 9 5 10 let 6 11 cfg = config.hyperv; 12 + in 13 + { 7 14 8 - in { 15 + imports = [ 16 + ./disk-size-option.nix 17 + (lib.mkRenamedOptionModuleWith { 18 + sinceRelease = 2411; 19 + from = [ 20 + "hyperv" 21 + "baseImageSize" 22 + ]; 23 + to = [ 24 + "virtualisation" 25 + "diskSize" 26 + ]; 27 + }) 28 + ]; 29 + 9 30 options = { 10 31 hyperv = { 11 - baseImageSize = mkOption { 12 - type = with types; either (enum [ "auto" ]) int; 13 - default = "auto"; 14 - example = 2048; 15 - description = '' 16 - The size of the hyper-v base image in MiB. 17 - ''; 18 - }; 19 32 vmDerivationName = mkOption { 20 33 type = types.str; 21 34 default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}"; ··· 34 47 }; 35 48 36 49 config = { 50 + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault 51 + # to avoid breaking existing configs using that. 52 + virtualisation.diskSize = lib.mkOverride 1490 (4 * 1024); 53 + 37 54 system.build.hypervImage = import ../../lib/make-disk-image.nix { 38 55 name = cfg.vmDerivationName; 39 56 postVM = '' ··· 41 58 rm $diskImage 42 59 ''; 43 60 format = "raw"; 44 - diskSize = cfg.baseImageSize; 61 + inherit (config.virtualisation) diskSize; 45 62 partitionTableType = "efi"; 46 63 inherit config lib pkgs; 47 64 };
+23 -11
nixos/modules/virtualisation/linode-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 with lib; 4 9 let ··· 12 17 ''; 13 18 in 14 19 { 15 - imports = [ ./linode-config.nix ]; 20 + imports = [ 21 + ./linode-config.nix 22 + ./disk-size-option.nix 23 + (lib.mkRenamedOptionModuleWith { 24 + sinceRelease = 2411; 25 + from = [ 26 + "virtualisation" 27 + "linodeImage" 28 + "diskSize" 29 + ]; 30 + to = [ 31 + "virtualisation" 32 + "diskSize" 33 + ]; 34 + }) 35 + ]; 16 36 17 37 options = { 18 - virtualisation.linodeImage.diskSize = mkOption { 19 - type = with types; either (enum (singleton "auto")) ints.positive; 20 - default = "auto"; 21 - example = 1536; 22 - description = '' 23 - Size of disk image in MB. 24 - ''; 25 - }; 26 38 27 39 virtualisation.linodeImage.configFile = mkOption { 28 40 type = with types; nullOr str; ··· 57 69 format = "raw"; 58 70 partitionTableType = "none"; 59 71 configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile; 60 - inherit (cfg) diskSize; 72 + inherit (config.virtualisation) diskSize; 61 73 inherit config lib pkgs; 62 74 }; 63 75 };
+16 -3
nixos/modules/virtualisation/oci-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 3 8 let 4 9 cfg = config.oci; ··· 7 12 imports = [ ./oci-common.nix ]; 8 13 9 14 config = { 15 + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault 16 + # to avoid breaking existing configs using that. 17 + virtualisation.diskSize = lib.mkOverride 1490 (8 * 1024); 18 + virtualisation.diskSizeAutoSupported = false; 19 + 10 20 system.build.OCIImage = import ../../lib/make-disk-image.nix { 11 21 inherit config lib pkgs; 12 - inherit (cfg) diskSize; 22 + inherit (config.virtualisation) diskSize; 13 23 name = "oci-image"; 14 24 configFile = ./oci-config-user.nix; 15 25 format = "qcow2"; ··· 25 35 after = [ "network-online.target" ]; 26 36 wants = [ "network-online.target" ]; 27 37 28 - path = [ pkgs.coreutils pkgs.curl ]; 38 + path = [ 39 + pkgs.coreutils 40 + pkgs.curl 41 + ]; 29 42 script = '' 30 43 mkdir -m 0700 -p /root/.ssh 31 44 if [ -f /root/.ssh/authorized_keys ]; then
+19 -7
nixos/modules/virtualisation/oci-options.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + lib, 3 + ... 4 + }: 2 5 { 6 + imports = [ 7 + ./disk-size-option.nix 8 + (lib.mkRenamedOptionModuleWith { 9 + sinceRelease = 2411; 10 + from = [ 11 + "oci" 12 + "diskSize" 13 + ]; 14 + to = [ 15 + "virtualisation" 16 + "diskSize" 17 + ]; 18 + }) 19 + ]; 20 + 3 21 options = { 4 22 oci = { 5 23 efi = lib.mkOption { ··· 8 26 description = '' 9 27 Whether the OCI instance is using EFI. 10 28 ''; 11 - }; 12 - diskSize = lib.mkOption { 13 - type = lib.types.int; 14 - default = 8192; 15 - description = "Size of the disk image created in MB."; 16 - example = "diskSize = 12 * 1024; # 12GiB"; 17 29 }; 18 30 }; 19 31 };
+187 -143
nixos/modules/virtualisation/proxmox-image.nix
··· 1 - { config, pkgs, lib, ... }: 1 + { 2 + config, 3 + pkgs, 4 + lib, 5 + ... 6 + }: 2 7 3 8 with lib; 4 - 5 9 { 10 + imports = [ 11 + ./disk-size-option.nix 12 + (lib.mkRenamedOptionModuleWith { 13 + sinceRelease = 2411; 14 + from = [ 15 + "proxmox" 16 + "qemuConf" 17 + "diskSize" 18 + ]; 19 + to = [ 20 + "virtualisation" 21 + "diskSize" 22 + ]; 23 + }) 24 + ]; 25 + 6 26 options.proxmox = { 7 27 qemuConf = { 8 28 # essential configs ··· 54 74 ''; 55 75 }; 56 76 bios = mkOption { 57 - type = types.enum [ "seabios" "ovmf" ]; 77 + type = types.enum [ 78 + "seabios" 79 + "ovmf" 80 + ]; 58 81 default = "seabios"; 59 82 description = '' 60 83 Select BIOS implementation (seabios = Legacy BIOS, ovmf = UEFI). ··· 87 110 either "efi" or "hybrid". 88 111 ''; 89 112 }; 90 - diskSize = mkOption { 91 - type = types.str; 92 - default = "auto"; 93 - example = "20480"; 94 - description = '' 95 - The size of the disk, in megabytes. 96 - if "auto" size is calculated based on the contents copied to it and 97 - additionalSpace is taken into account. 98 - ''; 99 - }; 100 113 net0 = mkOption { 101 114 type = types.commas; 102 115 default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1"; ··· 124 137 }; 125 138 }; 126 139 qemuExtraConf = mkOption { 127 - type = with types; attrsOf (oneOf [ str int ]); 128 - default = {}; 140 + type = 141 + with types; 142 + attrsOf (oneOf [ 143 + str 144 + int 145 + ]); 146 + default = { }; 129 147 example = literalExpression '' 130 148 { 131 149 cpu = "host"; ··· 137 155 ''; 138 156 }; 139 157 partitionTableType = mkOption { 140 - type = types.enum [ "efi" "hybrid" "legacy" "legacy+gpt" ]; 158 + type = types.enum [ 159 + "efi" 160 + "hybrid" 161 + "legacy" 162 + "legacy+gpt" 163 + ]; 141 164 description = '' 142 165 Partition table type to use. See make-disk-image.nix partitionTableType for details. 143 166 Defaults to 'legacy' for 'proxmox.qemuConf.bios="seabios"' (default), other bios values defaults to 'efi'. ··· 185 208 }; 186 209 }; 187 210 188 - config = let 189 - cfg = config.proxmox; 190 - cfgLine = name: value: '' 191 - ${name}: ${builtins.toString value} 192 - ''; 193 - virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); 194 - cfgFile = fileName: properties: pkgs.writeTextDir fileName '' 195 - # generated by NixOS 196 - ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} 197 - #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: 198 - ''; 199 - inherit (cfg) partitionTableType; 200 - supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; 201 - supportBios = partitionTableType == "legacy" || partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; 202 - hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; 203 - hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; 204 - in { 205 - assertions = [ 206 - { 207 - assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; 208 - message = "systemd-boot requires 'ovmf' bios"; 209 - } 210 - { 211 - assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; 212 - message = "'efi' disk partitioning requires 'ovmf' bios"; 213 - } 214 - { 215 - assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; 216 - message = "'legacy' disk partitioning requires 'seabios' bios"; 217 - } 218 - { 219 - assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; 220 - message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; 221 - } 222 - ]; 223 - system.build.VMA = import ../../lib/make-disk-image.nix { 224 - name = "proxmox-${cfg.filenameSuffix}"; 211 + config = 212 + let 213 + cfg = config.proxmox; 214 + cfgLine = name: value: '' 215 + ${name}: ${builtins.toString value} 216 + ''; 217 + virtio0Storage = builtins.head (builtins.split ":" cfg.qemuConf.virtio0); 218 + cfgFile = 219 + fileName: properties: 220 + pkgs.writeTextDir fileName '' 221 + # generated by NixOS 222 + ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)} 223 + #qmdump#map:virtio0:drive-virtio0:${virtio0Storage}:raw: 224 + ''; 225 225 inherit (cfg) partitionTableType; 226 - postVM = let 227 - # Build qemu with PVE's patch that adds support for the VMA format 228 - vma = (pkgs.qemu_kvm.override { 229 - alsaSupport = false; 230 - pulseSupport = false; 231 - sdlSupport = false; 232 - jackSupport = false; 233 - gtkSupport = false; 234 - vncSupport = false; 235 - smartcardSupport = false; 236 - spiceSupport = false; 237 - ncursesSupport = false; 238 - libiscsiSupport = false; 239 - tpmSupport = false; 240 - numaSupport = false; 241 - seccompSupport = false; 242 - guestAgentSupport = false; 243 - }).overrideAttrs ( super: rec { 244 - # Check https://github.com/proxmox/pve-qemu/tree/master for the version 245 - # of qemu and patch to use 246 - version = "9.0.0"; 247 - src = pkgs.fetchurl { 248 - url = "https://download.qemu.org/qemu-${version}.tar.xz"; 249 - hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; 250 - }; 251 - patches = [ 252 - # Proxmox' VMA tool is published as a particular patch upon QEMU 253 - "${pkgs.fetchFromGitHub { 254 - owner = "proxmox"; 255 - repo = "pve-qemu"; 256 - rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; 257 - hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; 258 - }}/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" 259 - ]; 260 - 261 - buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; 262 - nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; 226 + supportEfi = partitionTableType == "efi" || partitionTableType == "hybrid"; 227 + supportBios = 228 + partitionTableType == "legacy" 229 + || partitionTableType == "hybrid" 230 + || partitionTableType == "legacy+gpt"; 231 + hasBootPartition = partitionTableType == "efi" || partitionTableType == "hybrid"; 232 + hasNoFsPartition = partitionTableType == "hybrid" || partitionTableType == "legacy+gpt"; 233 + in 234 + { 235 + assertions = [ 236 + { 237 + assertion = config.boot.loader.systemd-boot.enable -> config.proxmox.qemuConf.bios == "ovmf"; 238 + message = "systemd-boot requires 'ovmf' bios"; 239 + } 240 + { 241 + assertion = partitionTableType == "efi" -> config.proxmox.qemuConf.bios == "ovmf"; 242 + message = "'efi' disk partitioning requires 'ovmf' bios"; 243 + } 244 + { 245 + assertion = partitionTableType == "legacy" -> config.proxmox.qemuConf.bios == "seabios"; 246 + message = "'legacy' disk partitioning requires 'seabios' bios"; 247 + } 248 + { 249 + assertion = partitionTableType == "legacy+gpt" -> config.proxmox.qemuConf.bios == "seabios"; 250 + message = "'legacy+gpt' disk partitioning requires 'seabios' bios"; 251 + } 252 + ]; 253 + system.build.VMA = import ../../lib/make-disk-image.nix { 254 + name = "proxmox-${cfg.filenameSuffix}"; 255 + inherit (cfg) partitionTableType; 256 + postVM = 257 + let 258 + # Build qemu with PVE's patch that adds support for the VMA format 259 + vma = 260 + (pkgs.qemu_kvm.override { 261 + alsaSupport = false; 262 + pulseSupport = false; 263 + sdlSupport = false; 264 + jackSupport = false; 265 + gtkSupport = false; 266 + vncSupport = false; 267 + smartcardSupport = false; 268 + spiceSupport = false; 269 + ncursesSupport = false; 270 + libiscsiSupport = false; 271 + tpmSupport = false; 272 + numaSupport = false; 273 + seccompSupport = false; 274 + guestAgentSupport = false; 275 + }).overrideAttrs 276 + (super: rec { 277 + # Check https://github.com/proxmox/pve-qemu/tree/master for the version 278 + # of qemu and patch to use 279 + version = "9.0.0"; 280 + src = pkgs.fetchurl { 281 + url = "https://download.qemu.org/qemu-${version}.tar.xz"; 282 + hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk="; 283 + }; 284 + patches = [ 285 + # Proxmox' VMA tool is published as a particular patch upon QEMU 286 + "${ 287 + pkgs.fetchFromGitHub { 288 + owner = "proxmox"; 289 + repo = "pve-qemu"; 290 + rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8"; 291 + hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc="; 292 + } 293 + }/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch" 294 + ]; 263 295 264 - }); 265 - in 266 - '' 267 - ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ 268 - -c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage 269 - rm $diskImage 270 - ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" 271 - mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ 296 + buildInputs = super.buildInputs ++ [ pkgs.libuuid ]; 297 + nativeBuildInputs = super.nativeBuildInputs ++ [ pkgs.perl ]; 272 298 273 - mkdir -p $out/nix-support 274 - echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products 275 - ''; 276 - inherit (cfg.qemuConf) additionalSpace diskSize bootSize; 277 - format = "raw"; 278 - inherit config lib pkgs; 279 - }; 299 + }); 300 + in 301 + '' 302 + ${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \ 303 + -c ${ 304 + cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf) 305 + }/qemu-server.conf drive-virtio0=$diskImage 306 + rm $diskImage 307 + ${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma" 308 + mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/ 280 309 281 - boot = { 282 - growPartition = true; 283 - kernelParams = [ "console=ttyS0" ]; 284 - loader.grub = { 285 - device = lib.mkDefault (if (hasNoFsPartition || supportBios) then 286 - # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), 287 - # which will be used the bootloader, do not set it as loader.grub.device. 288 - # GRUB installation fails, unless the whole disk is selected. 289 - "/dev/vda" 290 - else 291 - "nodev"); 292 - efiSupport = lib.mkDefault supportEfi; 293 - efiInstallAsRemovable = lib.mkDefault supportEfi; 310 + mkdir -p $out/nix-support 311 + echo "file vma $out/vzdump-qemu-${cfg.filenameSuffix}.vma.zst" > $out/nix-support/hydra-build-products 312 + ''; 313 + inherit (cfg.qemuConf) additionalSpace bootSize; 314 + inherit (config.virtualisation) diskSize; 315 + format = "raw"; 316 + inherit config lib pkgs; 294 317 }; 295 318 296 - loader.timeout = 0; 297 - initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ]; 298 - }; 319 + boot = { 320 + growPartition = true; 321 + kernelParams = [ "console=ttyS0" ]; 322 + loader.grub = { 323 + device = lib.mkDefault ( 324 + if (hasNoFsPartition || supportBios) then 325 + # Even if there is a separate no-fs partition ("/dev/disk/by-partlabel/no-fs" i.e. "/dev/vda2"), 326 + # which will be used the bootloader, do not set it as loader.grub.device. 327 + # GRUB installation fails, unless the whole disk is selected. 328 + "/dev/vda" 329 + else 330 + "nodev" 331 + ); 332 + efiSupport = lib.mkDefault supportEfi; 333 + efiInstallAsRemovable = lib.mkDefault supportEfi; 334 + }; 299 335 300 - fileSystems."/" = { 301 - device = "/dev/disk/by-label/nixos"; 302 - autoResize = true; 303 - fsType = "ext4"; 304 - }; 305 - fileSystems."/boot" = lib.mkIf hasBootPartition { 306 - device = "/dev/disk/by-label/ESP"; 307 - fsType = "vfat"; 308 - }; 336 + loader.timeout = 0; 337 + initrd.availableKernelModules = [ 338 + "uas" 339 + "virtio_blk" 340 + "virtio_pci" 341 + ]; 342 + }; 309 343 310 - networking = mkIf cfg.cloudInit.enable { 311 - hostName = mkForce ""; 312 - useDHCP = false; 313 - }; 344 + fileSystems."/" = { 345 + device = "/dev/disk/by-label/nixos"; 346 + autoResize = true; 347 + fsType = "ext4"; 348 + }; 349 + fileSystems."/boot" = lib.mkIf hasBootPartition { 350 + device = "/dev/disk/by-label/ESP"; 351 + fsType = "vfat"; 352 + }; 353 + 354 + networking = mkIf cfg.cloudInit.enable { 355 + hostName = mkForce ""; 356 + useDHCP = false; 357 + }; 314 358 315 - services = { 316 - cloud-init = mkIf cfg.cloudInit.enable { 317 - enable = true; 318 - network.enable = true; 359 + services = { 360 + cloud-init = mkIf cfg.cloudInit.enable { 361 + enable = true; 362 + network.enable = true; 363 + }; 364 + sshd.enable = mkDefault true; 365 + qemuGuest.enable = true; 319 366 }; 320 - sshd.enable = mkDefault true; 321 - qemuGuest.enable = true; 367 + 368 + proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; 322 369 }; 323 - 324 - proxmox.qemuExtraConf.${cfg.cloudInit.device} = "${cfg.cloudInit.defaultStorage}:vm-9999-cloudinit,media=cdrom"; 325 - }; 326 370 }
+926 -818
nixos/modules/virtualisation/qemu-vm.nix
··· 4 4 # `config'. By default, the Nix store is shared read-only with the 5 5 # host, which makes (re)building VMs very efficient. 6 6 7 - { config, lib, pkgs, options, ... }: 7 + { 8 + config, 9 + lib, 10 + pkgs, 11 + options, 12 + ... 13 + }: 8 14 9 15 with lib; 10 16 ··· 22 28 23 29 consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; 24 30 25 - driveOpts = { ... }: { 31 + driveOpts = 32 + { ... }: 33 + { 26 34 27 - options = { 35 + options = { 28 36 29 - file = mkOption { 30 - type = types.str; 31 - description = "The file image used for this drive."; 32 - }; 37 + file = mkOption { 38 + type = types.str; 39 + description = "The file image used for this drive."; 40 + }; 41 + 42 + driveExtraOpts = mkOption { 43 + type = types.attrsOf types.str; 44 + default = { }; 45 + description = "Extra options passed to drive flag."; 46 + }; 33 47 34 - driveExtraOpts = mkOption { 35 - type = types.attrsOf types.str; 36 - default = {}; 37 - description = "Extra options passed to drive flag."; 38 - }; 48 + deviceExtraOpts = mkOption { 49 + type = types.attrsOf types.str; 50 + default = { }; 51 + description = "Extra options passed to device flag."; 52 + }; 39 53 40 - deviceExtraOpts = mkOption { 41 - type = types.attrsOf types.str; 42 - default = {}; 43 - description = "Extra options passed to device flag."; 44 - }; 54 + name = mkOption { 55 + type = types.nullOr types.str; 56 + default = null; 57 + description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; 58 + }; 45 59 46 - name = mkOption { 47 - type = types.nullOr types.str; 48 - default = null; 49 - description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; 50 60 }; 51 61 52 62 }; 53 63 54 - }; 64 + selectPartitionTableLayout = 65 + { useEFIBoot, useDefaultFilesystems }: 66 + if useDefaultFilesystems then if useEFIBoot then "efi" else "legacy" else "none"; 55 67 56 - selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }: 57 - if useDefaultFilesystems then 58 - if useEFIBoot then "efi" else "legacy" 59 - else "none"; 60 - 61 - driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: 68 + driveCmdline = 69 + idx: 70 + { 71 + file, 72 + driveExtraOpts, 73 + deviceExtraOpts, 74 + ... 75 + }: 62 76 let 63 77 drvId = "drive${toString idx}"; 64 - mkKeyValue = generators.mkKeyValueDefault {} "="; 78 + mkKeyValue = generators.mkKeyValueDefault { } "="; 65 79 mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); 66 - driveOpts = mkOpts (driveExtraOpts // { 67 - index = idx; 68 - id = drvId; 69 - "if" = "none"; 70 - inherit file; 71 - }); 72 - deviceOpts = mkOpts (deviceExtraOpts // { 73 - drive = drvId; 74 - }); 80 + driveOpts = mkOpts ( 81 + driveExtraOpts 82 + // { 83 + index = idx; 84 + id = drvId; 85 + "if" = "none"; 86 + inherit file; 87 + } 88 + ); 89 + deviceOpts = mkOpts ( 90 + deviceExtraOpts 91 + // { 92 + drive = drvId; 93 + } 94 + ); 75 95 device = 76 96 if cfg.qemu.diskInterface == "scsi" then 77 97 "-device lsi53c895a -device scsi-hd,${deviceOpts}" 78 98 else 79 99 "-device virtio-blk-pci,${deviceOpts}"; 80 100 in 81 - "-drive ${driveOpts} ${device}"; 101 + "-drive ${driveOpts} ${device}"; 82 102 83 103 drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); 84 104 85 105 # Shell script to start the VM. 86 - startVM = 87 - '' 88 - #! ${hostPkgs.runtimeShell} 106 + startVM = '' 107 + #! ${hostPkgs.runtimeShell} 89 108 90 - export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH 91 - 92 - set -e 109 + export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH 93 110 94 - # Create an empty ext4 filesystem image. A filesystem image does not 95 - # contain a partition table but just a filesystem. 96 - createEmptyFilesystemImage() { 97 - local name=$1 98 - local size=$2 99 - local temp=$(mktemp) 100 - ${qemu}/bin/qemu-img create -f raw "$temp" "$size" 101 - ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" 102 - ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" 103 - rm "$temp" 104 - } 111 + set -e 105 112 106 - NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" 113 + # Create an empty ext4 filesystem image. A filesystem image does not 114 + # contain a partition table but just a filesystem. 115 + createEmptyFilesystemImage() { 116 + local name=$1 117 + local size=$2 118 + local temp=$(mktemp) 119 + ${qemu}/bin/qemu-img create -f raw "$temp" "$size" 120 + ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" 121 + ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" 122 + rm "$temp" 123 + } 107 124 108 - if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then 109 - echo "Disk image do not exist, creating the virtualisation disk image..." 125 + NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" 110 126 111 - ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then '' 112 - # Create a writable qcow2 image using the systemImage as a backing 113 - # image. 127 + if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then 128 + echo "Disk image do not exist, creating the virtualisation disk image..." 114 129 115 - # CoW prevent size to be attributed to an image. 116 - # FIXME: raise this issue to upstream. 117 - ${qemu}/bin/qemu-img create \ 118 - -f qcow2 \ 119 - -b ${systemImage}/nixos.qcow2 \ 120 - -F qcow2 \ 121 - "$NIX_DISK_IMAGE" 122 - '' else if cfg.useDefaultFilesystems then '' 123 - createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 124 - '' else '' 125 - # Create an empty disk image without a filesystem. 126 - ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 127 - '' 128 - } 129 - echo "Virtualisation disk image created." 130 - fi 130 + ${ 131 + if (cfg.useBootLoader && cfg.useDefaultFilesystems) then 132 + '' 133 + # Create a writable qcow2 image using the systemImage as a backing 134 + # image. 131 135 132 - # Create a directory for storing temporary data of the running VM. 133 - if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then 134 - TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) 135 - fi 136 + # CoW prevent size to be attributed to an image. 137 + # FIXME: raise this issue to upstream. 138 + ${qemu}/bin/qemu-img create \ 139 + -f qcow2 \ 140 + -b ${systemImage}/nixos.qcow2 \ 141 + -F qcow2 \ 142 + "$NIX_DISK_IMAGE" 143 + '' 144 + else if cfg.useDefaultFilesystems then 145 + '' 146 + createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 147 + '' 148 + else 149 + '' 150 + # Create an empty disk image without a filesystem. 151 + ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 152 + '' 153 + } 154 + echo "Virtualisation disk image created." 155 + fi 136 156 137 - ${lib.optionalString (cfg.useNixStoreImage) '' 138 - echo "Creating Nix store image..." 157 + # Create a directory for storing temporary data of the running VM. 158 + if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then 159 + TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) 160 + fi 139 161 140 - ${hostPkgs.gnutar}/bin/tar --create \ 141 - --absolute-names \ 142 - --verbatim-files-from \ 143 - --transform 'flags=rSh;s|/nix/store/||' \ 144 - --transform 'flags=rSh;s|~nix~case~hack~[[:digit:]]\+||g' \ 145 - --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \ 146 - | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ 147 - --quiet \ 148 - --force-uid=0 \ 149 - --force-gid=0 \ 150 - -L ${nixStoreFilesystemLabel} \ 151 - -U eb176051-bd15-49b7-9e6b-462e0b467019 \ 152 - -T 0 \ 153 - --tar=f \ 154 - "$TMPDIR"/store.img 162 + ${lib.optionalString (cfg.useNixStoreImage) '' 163 + echo "Creating Nix store image..." 155 164 156 - echo "Created Nix store image." 157 - '' 158 - } 165 + ${hostPkgs.gnutar}/bin/tar --create \ 166 + --absolute-names \ 167 + --verbatim-files-from \ 168 + --transform 'flags=rSh;s|/nix/store/||' \ 169 + --transform 'flags=rSh;s|~nix~case~hack~[[:digit:]]\+||g' \ 170 + --files-from ${ 171 + hostPkgs.closureInfo { 172 + rootPaths = [ 173 + config.system.build.toplevel 174 + regInfo 175 + ]; 176 + } 177 + }/store-paths \ 178 + | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ 179 + --quiet \ 180 + --force-uid=0 \ 181 + --force-gid=0 \ 182 + -L ${nixStoreFilesystemLabel} \ 183 + -U eb176051-bd15-49b7-9e6b-462e0b467019 \ 184 + -T 0 \ 185 + --tar=f \ 186 + "$TMPDIR"/store.img 159 187 160 - # Create a directory for exchanging data with the VM. 161 - mkdir -p "$TMPDIR/xchg" 188 + echo "Created Nix store image." 189 + ''} 162 190 163 - ${lib.optionalString cfg.useHostCerts 164 - '' 165 - mkdir -p "$TMPDIR/certs" 166 - if [ -e "$NIX_SSL_CERT_FILE" ]; then 167 - cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt 168 - else 169 - echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. 170 - fi 171 - ''} 191 + # Create a directory for exchanging data with the VM. 192 + mkdir -p "$TMPDIR/xchg" 172 193 173 - ${lib.optionalString cfg.useEFIBoot 174 - '' 175 - # Expose EFI variables, it's useful even when we are not using a bootloader (!). 176 - # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence 177 - # no guard against `useBootLoader`. Examples: 178 - # - testing PXE boot or other EFI applications 179 - # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows 180 - NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") 181 - # VM needs writable EFI vars 182 - if ! test -e "$NIX_EFI_VARS"; then 183 - ${if cfg.efi.keepVariables then 184 - # We still need the EFI var from the make-disk-image derivation 185 - # because our "switch-to-configuration" process might 186 - # write into it and we want to keep this data. 187 - ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' 188 - else 189 - ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' 190 - } 191 - chmod 0644 "$NIX_EFI_VARS" 192 - fi 193 - ''} 194 + ${lib.optionalString cfg.useHostCerts '' 195 + mkdir -p "$TMPDIR/certs" 196 + if [ -e "$NIX_SSL_CERT_FILE" ]; then 197 + cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt 198 + else 199 + echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. 200 + fi 201 + ''} 194 202 195 - ${lib.optionalString cfg.tpm.enable '' 196 - NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") 197 - mkdir -p "$NIX_SWTPM_DIR" 198 - ${lib.getExe cfg.tpm.package} \ 199 - socket \ 200 - --tpmstate dir="$NIX_SWTPM_DIR" \ 201 - --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ 202 - --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 203 - --tpm2 \ 204 - --log file="$NIX_SWTPM_DIR"/stdout,level=6 203 + ${lib.optionalString cfg.useEFIBoot '' 204 + # Expose EFI variables, it's useful even when we are not using a bootloader (!). 205 + # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence 206 + # no guard against `useBootLoader`. Examples: 207 + # - testing PXE boot or other EFI applications 208 + # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows 209 + NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") 210 + # VM needs writable EFI vars 211 + if ! test -e "$NIX_EFI_VARS"; then 212 + ${ 213 + if cfg.efi.keepVariables then 214 + # We still need the EFI var from the make-disk-image derivation 215 + # because our "switch-to-configuration" process might 216 + # write into it and we want to keep this data. 217 + ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' 218 + else 219 + ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' 220 + } 221 + chmod 0644 "$NIX_EFI_VARS" 222 + fi 223 + ''} 205 224 206 - # Enable `fdflags` builtin in Bash 207 - # We will need it to perform surgical modification of the file descriptor 208 - # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor 209 - # on exec. 210 - # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` 211 - # at the end of this script. To work around that, we will just clear 212 - # the `FD_CLOEXEC` bits as a first step. 213 - enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags 214 - # leave a dangling subprocess because the swtpm ctrl socket has 215 - # "terminate" when the last connection disconnects, it stops swtpm. 216 - # When qemu stops, or if the main shell process ends, the coproc will 217 - # get signaled by virtue of the pipe between main and coproc ending. 218 - # Which in turns triggers a socat connect-disconnect to swtpm which 219 - # will stop it. 220 - coproc waitingswtpm { 221 - read || : 222 - echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket 223 - } 224 - # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 225 - fdflags -s-cloexec ''${waitingswtpm[1]} 226 - ''} 225 + ${lib.optionalString cfg.tpm.enable '' 226 + NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") 227 + mkdir -p "$NIX_SWTPM_DIR" 228 + ${lib.getExe cfg.tpm.package} \ 229 + socket \ 230 + --tpmstate dir="$NIX_SWTPM_DIR" \ 231 + --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ 232 + --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 233 + --tpm2 \ 234 + --log file="$NIX_SWTPM_DIR"/stdout,level=6 227 235 228 - cd "$TMPDIR" 236 + # Enable `fdflags` builtin in Bash 237 + # We will need it to perform surgical modification of the file descriptor 238 + # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor 239 + # on exec. 240 + # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` 241 + # at the end of this script. To work around that, we will just clear 242 + # the `FD_CLOEXEC` bits as a first step. 243 + enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags 244 + # leave a dangling subprocess because the swtpm ctrl socket has 245 + # "terminate" when the last connection disconnects, it stops swtpm. 246 + # When qemu stops, or if the main shell process ends, the coproc will 247 + # get signaled by virtue of the pipe between main and coproc ending. 248 + # Which in turns triggers a socat connect-disconnect to swtpm which 249 + # will stop it. 250 + coproc waitingswtpm { 251 + read || : 252 + echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket 253 + } 254 + # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 255 + fdflags -s-cloexec ''${waitingswtpm[1]} 256 + ''} 229 257 230 - ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} 231 - ${flip concatMapStrings cfg.emptyDiskImages (size: '' 232 - if ! test -e "empty$idx.qcow2"; then 233 - ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" 234 - fi 235 - idx=$((idx + 1)) 236 - '')} 258 + cd "$TMPDIR" 237 259 238 - # Start QEMU. 239 - exec ${qemu-common.qemuBinary qemu} \ 240 - -name ${config.system.name} \ 241 - -m ${toString config.virtualisation.memorySize} \ 242 - -smp ${toString config.virtualisation.cores} \ 243 - -device virtio-rng-pci \ 244 - ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ 245 - ${concatStringsSep " \\\n " 246 - (mapAttrsToList 247 - (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}") 248 - config.virtualisation.sharedDirectories)} \ 249 - ${drivesCmdLine config.virtualisation.qemu.drives} \ 250 - ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ 251 - $QEMU_OPTS \ 252 - "$@" 253 - ''; 260 + ${lib.optionalString (cfg.emptyDiskImages != [ ]) "idx=0"} 261 + ${flip concatMapStrings cfg.emptyDiskImages (size: '' 262 + if ! test -e "empty$idx.qcow2"; then 263 + ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" 264 + fi 265 + idx=$((idx + 1)) 266 + '')} 254 267 268 + # Start QEMU. 269 + exec ${qemu-common.qemuBinary qemu} \ 270 + -name ${config.system.name} \ 271 + -m ${toString config.virtualisation.memorySize} \ 272 + -smp ${toString config.virtualisation.cores} \ 273 + -device virtio-rng-pci \ 274 + ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ 275 + ${ 276 + concatStringsSep " \\\n " ( 277 + mapAttrsToList ( 278 + tag: share: 279 + "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}" 280 + ) config.virtualisation.sharedDirectories 281 + ) 282 + } \ 283 + ${drivesCmdLine config.virtualisation.qemu.drives} \ 284 + ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ 285 + $QEMU_OPTS \ 286 + "$@" 287 + ''; 255 288 256 289 regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; }; 257 290 ··· 284 317 copyChannel = false; 285 318 OVMF = cfg.efi.OVMF; 286 319 }; 287 - 288 320 in 289 - 290 321 { 291 322 imports = [ 292 323 ../profiles/qemu-guest.nix 293 - (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ]) 294 - (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.") 295 - (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") 296 - (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.`") 324 + ./disk-size-option.nix 325 + (mkRenamedOptionModule 326 + [ 327 + "virtualisation" 328 + "pathsInNixDB" 329 + ] 330 + [ 331 + "virtualisation" 332 + "additionalPaths" 333 + ] 334 + ) 335 + (mkRemovedOptionModule 336 + [ 337 + "virtualisation" 338 + "bootDevice" 339 + ] 340 + "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." 341 + ) 342 + (mkRemovedOptionModule 343 + [ 344 + "virtualisation" 345 + "efiVars" 346 + ] 347 + "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" 348 + ) 349 + (mkRemovedOptionModule 350 + [ 351 + "virtualisation" 352 + "persistBootDevice" 353 + ] 354 + "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.`" 355 + ) 297 356 ]; 298 357 299 358 options = { 300 359 301 360 virtualisation.fileSystems = options.fileSystems; 302 361 303 - virtualisation.memorySize = 304 - mkOption { 305 - type = types.ints.positive; 306 - default = 1024; 307 - description = '' 308 - The memory size in megabytes of the virtual machine. 309 - ''; 310 - }; 362 + virtualisation.memorySize = mkOption { 363 + type = types.ints.positive; 364 + default = 1024; 365 + description = '' 366 + The memory size in megabytes of the virtual machine. 367 + ''; 368 + }; 311 369 312 - virtualisation.msize = 313 - mkOption { 314 - type = types.ints.positive; 315 - default = 16384; 316 - description = '' 317 - The msize (maximum packet size) option passed to 9p file systems, in 318 - bytes. Increasing this should increase performance significantly, 319 - at the cost of higher RAM usage. 320 - ''; 321 - }; 370 + virtualisation.msize = mkOption { 371 + type = types.ints.positive; 372 + default = 16384; 373 + description = '' 374 + The msize (maximum packet size) option passed to 9p file systems, in 375 + bytes. Increasing this should increase performance significantly, 376 + at the cost of higher RAM usage. 377 + ''; 378 + }; 322 379 323 - virtualisation.diskSize = 324 - mkOption { 325 - type = types.ints.positive; 326 - default = 1024; 327 - description = '' 328 - The disk size in megabytes of the virtual machine. 329 - ''; 330 - }; 380 + virtualisation.diskImage = mkOption { 381 + type = types.nullOr types.str; 382 + default = "./${config.system.name}.qcow2"; 383 + defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; 384 + description = '' 385 + Path to the disk image containing the root filesystem. 386 + The image will be created on startup if it does not 387 + exist. 331 388 332 - virtualisation.diskImage = 333 - mkOption { 334 - type = types.nullOr types.str; 335 - default = "./${config.system.name}.qcow2"; 336 - defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; 337 - description = '' 338 - Path to the disk image containing the root filesystem. 339 - The image will be created on startup if it does not 340 - exist. 389 + If null, a tmpfs will be used as the root filesystem and 390 + the VM's state will not be persistent. 391 + ''; 392 + }; 341 393 342 - If null, a tmpfs will be used as the root filesystem and 343 - the VM's state will not be persistent. 344 - ''; 345 - }; 346 - 347 - virtualisation.bootLoaderDevice = 348 - mkOption { 349 - type = types.path; 350 - default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; 351 - defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; 352 - example = "/dev/disk/by-id/virtio-boot-loader-device"; 353 - description = '' 354 - The path (inside th VM) to the device to boot from when legacy booting. 355 - ''; 356 - }; 394 + virtualisation.bootLoaderDevice = mkOption { 395 + type = types.path; 396 + default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; 397 + defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; 398 + example = "/dev/disk/by-id/virtio-boot-loader-device"; 399 + description = '' 400 + The path (inside th VM) to the device to boot from when legacy booting. 401 + ''; 402 + }; 357 403 358 - virtualisation.bootPartition = 359 - mkOption { 360 - type = types.nullOr types.path; 361 - default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; 362 - defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; 363 - example = "/dev/disk/by-label/esp"; 364 - description = '' 365 - The path (inside the VM) to the device containing the EFI System Partition (ESP). 404 + virtualisation.bootPartition = mkOption { 405 + type = types.nullOr types.path; 406 + default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; 407 + defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; 408 + example = "/dev/disk/by-label/esp"; 409 + description = '' 410 + The path (inside the VM) to the device containing the EFI System Partition (ESP). 366 411 367 - If you are *not* booting from a UEFI firmware, this value is, by 368 - default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. 369 - ''; 370 - }; 412 + If you are *not* booting from a UEFI firmware, this value is, by 413 + default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. 414 + ''; 415 + }; 371 416 372 - virtualisation.rootDevice = 373 - mkOption { 374 - type = types.nullOr types.path; 375 - default = "/dev/disk/by-label/${rootFilesystemLabel}"; 376 - defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; 377 - example = "/dev/disk/by-label/nixos"; 378 - description = '' 379 - The path (inside the VM) to the device containing the root filesystem. 380 - ''; 381 - }; 417 + virtualisation.rootDevice = mkOption { 418 + type = types.nullOr types.path; 419 + default = "/dev/disk/by-label/${rootFilesystemLabel}"; 420 + defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; 421 + example = "/dev/disk/by-label/nixos"; 422 + description = '' 423 + The path (inside the VM) to the device containing the root filesystem. 424 + ''; 425 + }; 382 426 383 - virtualisation.emptyDiskImages = 384 - mkOption { 385 - type = types.listOf types.ints.positive; 386 - default = []; 387 - description = '' 388 - Additional disk images to provide to the VM. The value is 389 - a list of size in megabytes of each disk. These disks are 390 - writeable by the VM. 391 - ''; 392 - }; 427 + virtualisation.emptyDiskImages = mkOption { 428 + type = types.listOf types.ints.positive; 429 + default = [ ]; 430 + description = '' 431 + Additional disk images to provide to the VM. The value is 432 + a list of size in megabytes of each disk. These disks are 433 + writeable by the VM. 434 + ''; 435 + }; 393 436 394 - virtualisation.graphics = 395 - mkOption { 396 - type = types.bool; 397 - default = true; 398 - description = '' 399 - Whether to run QEMU with a graphics window, or in nographic mode. 400 - Serial console will be enabled on both settings, but this will 401 - change the preferred console. 402 - ''; 403 - }; 437 + virtualisation.graphics = mkOption { 438 + type = types.bool; 439 + default = true; 440 + description = '' 441 + Whether to run QEMU with a graphics window, or in nographic mode. 442 + Serial console will be enabled on both settings, but this will 443 + change the preferred console. 444 + ''; 445 + }; 404 446 405 - virtualisation.resolution = 406 - mkOption { 407 - type = options.services.xserver.resolutions.type.nestedTypes.elemType; 408 - default = { x = 1024; y = 768; }; 409 - description = '' 410 - The resolution of the virtual machine display. 411 - ''; 447 + virtualisation.resolution = mkOption { 448 + type = options.services.xserver.resolutions.type.nestedTypes.elemType; 449 + default = { 450 + x = 1024; 451 + y = 768; 412 452 }; 453 + description = '' 454 + The resolution of the virtual machine display. 455 + ''; 456 + }; 413 457 414 - virtualisation.cores = 415 - mkOption { 416 - type = types.ints.positive; 417 - default = 1; 418 - description = '' 419 - Specify the number of cores the guest is permitted to use. 420 - The number can be higher than the available cores on the 421 - host system. 422 - ''; 423 - }; 458 + virtualisation.cores = mkOption { 459 + type = types.ints.positive; 460 + default = 1; 461 + description = '' 462 + Specify the number of cores the guest is permitted to use. 463 + The number can be higher than the available cores on the 464 + host system. 465 + ''; 466 + }; 424 467 425 - virtualisation.sharedDirectories = 426 - mkOption { 427 - type = types.attrsOf 428 - (types.submodule { 429 - options.source = mkOption { 430 - type = types.str; 431 - description = "The path of the directory to share, can be a shell variable"; 432 - }; 433 - options.target = mkOption { 434 - type = types.path; 435 - description = "The mount point of the directory inside the virtual machine"; 436 - }; 437 - options.securityModel = mkOption { 438 - type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ]; 439 - default = "mapped-xattr"; 440 - description = '' 441 - The security model to use for this share: 468 + virtualisation.sharedDirectories = mkOption { 469 + type = types.attrsOf ( 470 + types.submodule { 471 + options.source = mkOption { 472 + type = types.str; 473 + description = "The path of the directory to share, can be a shell variable"; 474 + }; 475 + options.target = mkOption { 476 + type = types.path; 477 + description = "The mount point of the directory inside the virtual machine"; 478 + }; 479 + options.securityModel = mkOption { 480 + type = types.enum [ 481 + "passthrough" 482 + "mapped-xattr" 483 + "mapped-file" 484 + "none" 485 + ]; 486 + default = "mapped-xattr"; 487 + description = '' 488 + The security model to use for this share: 442 489 443 - - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) 444 - - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes 445 - - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools 446 - - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership 447 - ''; 448 - }; 449 - }); 450 - default = { }; 451 - example = { 452 - my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; 490 + - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) 491 + - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes 492 + - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools 493 + - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership 494 + ''; 495 + }; 496 + } 497 + ); 498 + default = { }; 499 + example = { 500 + my-share = { 501 + source = "/path/to/be/shared"; 502 + target = "/mnt/shared"; 453 503 }; 454 - description = '' 455 - An attributes set of directories that will be shared with the 456 - virtual machine using VirtFS (9P filesystem over VirtIO). 457 - The attribute name will be used as the 9P mount tag. 458 - ''; 459 504 }; 505 + description = '' 506 + An attributes set of directories that will be shared with the 507 + virtual machine using VirtFS (9P filesystem over VirtIO). 508 + The attribute name will be used as the 9P mount tag. 509 + ''; 510 + }; 460 511 461 - virtualisation.additionalPaths = 462 - mkOption { 463 - type = types.listOf types.path; 464 - default = []; 465 - description = '' 466 - A list of paths whose closure should be made available to 467 - the VM. 512 + virtualisation.additionalPaths = mkOption { 513 + type = types.listOf types.path; 514 + default = [ ]; 515 + description = '' 516 + A list of paths whose closure should be made available to 517 + the VM. 468 518 469 - When 9p is used, the closure is registered in the Nix 470 - database in the VM. All other paths in the host Nix store 471 - appear in the guest Nix store as well, but are considered 472 - garbage (because they are not registered in the Nix 473 - database of the guest). 519 + When 9p is used, the closure is registered in the Nix 520 + database in the VM. All other paths in the host Nix store 521 + appear in the guest Nix store as well, but are considered 522 + garbage (because they are not registered in the Nix 523 + database of the guest). 474 524 475 - When {option}`virtualisation.useNixStoreImage` is 476 - set, the closure is copied to the Nix store image. 477 - ''; 478 - }; 525 + When {option}`virtualisation.useNixStoreImage` is 526 + set, the closure is copied to the Nix store image. 527 + ''; 528 + }; 479 529 480 530 virtualisation.forwardPorts = mkOption { 481 - type = types.listOf 482 - (types.submodule { 531 + type = types.listOf ( 532 + types.submodule { 483 533 options.from = mkOption { 484 - type = types.enum [ "host" "guest" ]; 534 + type = types.enum [ 535 + "host" 536 + "guest" 537 + ]; 485 538 default = "host"; 486 539 description = '' 487 - Controls the direction in which the ports are mapped: 540 + Controls the direction in which the ports are mapped: 488 541 489 - - `"host"` means traffic from the host ports 490 - is forwarded to the given guest port. 491 - - `"guest"` means traffic from the guest ports 492 - is forwarded to the given host port. 493 - ''; 542 + - `"host"` means traffic from the host ports 543 + is forwarded to the given guest port. 544 + - `"guest"` means traffic from the guest ports 545 + is forwarded to the given host port. 546 + ''; 494 547 }; 495 548 options.proto = mkOption { 496 - type = types.enum [ "tcp" "udp" ]; 549 + type = types.enum [ 550 + "tcp" 551 + "udp" 552 + ]; 497 553 default = "tcp"; 498 554 description = "The protocol to forward."; 499 555 }; ··· 515 571 type = types.port; 516 572 description = "The guest port to be mapped."; 517 573 }; 518 - }); 519 - default = []; 520 - example = lib.literalExpression 521 - '' 574 + } 575 + ); 576 + default = [ ]; 577 + example = lib.literalExpression '' 522 578 [ # forward local port 2222 -> 22, to ssh into the VM 523 579 { from = "host"; host.port = 2222; guest.port = 22; } 524 580 ··· 528 584 host.address = "127.0.0.1"; host.port = 80; 529 585 } 530 586 ] 531 - ''; 587 + ''; 532 588 description = '' 533 - When using the SLiRP user networking (default), this option allows to 534 - forward ports to/from the host/guest. 589 + When using the SLiRP user networking (default), this option allows to 590 + forward ports to/from the host/guest. 535 591 536 - ::: {.warning} 537 - If the NixOS firewall on the virtual machine is enabled, you also 538 - have to open the guest ports to enable the traffic between host and 539 - guest. 540 - ::: 592 + ::: {.warning} 593 + If the NixOS firewall on the virtual machine is enabled, you also 594 + have to open the guest ports to enable the traffic between host and 595 + guest. 596 + ::: 541 597 542 - ::: {.note} 543 - Currently QEMU supports only IPv4 forwarding. 544 - ::: 545 - ''; 598 + ::: {.note} 599 + Currently QEMU supports only IPv4 forwarding. 600 + ::: 601 + ''; 546 602 }; 547 603 548 - virtualisation.restrictNetwork = 549 - mkOption { 550 - type = types.bool; 551 - default = false; 552 - example = true; 553 - description = '' 554 - If this option is enabled, the guest will be isolated, i.e. it will 555 - not be able to contact the host and no guest IP packets will be 556 - routed over the host to the outside. This option does not affect 557 - any explicitly set forwarding rules. 558 - ''; 559 - }; 604 + virtualisation.restrictNetwork = mkOption { 605 + type = types.bool; 606 + default = false; 607 + example = true; 608 + description = '' 609 + If this option is enabled, the guest will be isolated, i.e. it will 610 + not be able to contact the host and no guest IP packets will be 611 + routed over the host to the outside. This option does not affect 612 + any explicitly set forwarding rules. 613 + ''; 614 + }; 560 615 561 - virtualisation.vlans = 562 - mkOption { 563 - type = types.listOf types.ints.unsigned; 564 - default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; 565 - defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; 566 - example = [ 1 2 ]; 567 - description = '' 568 - Virtual networks to which the VM is connected. Each 569 - number «N» in this list causes 570 - the VM to have a virtual Ethernet interface attached to a 571 - separate virtual network on which it will be assigned IP 572 - address 573 - `192.168.«N».«M»`, 574 - where «M» is the index of this VM 575 - in the list of VMs. 576 - ''; 577 - }; 616 + virtualisation.vlans = mkOption { 617 + type = types.listOf types.ints.unsigned; 618 + default = if config.virtualisation.interfaces == { } then [ 1 ] else [ ]; 619 + defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; 620 + example = [ 621 + 1 622 + 2 623 + ]; 624 + description = '' 625 + Virtual networks to which the VM is connected. Each 626 + number «N» in this list causes 627 + the VM to have a virtual Ethernet interface attached to a 628 + separate virtual network on which it will be assigned IP 629 + address 630 + `192.168.«N».«M»`, 631 + where «M» is the index of this VM 632 + in the list of VMs. 633 + ''; 634 + }; 578 635 579 636 virtualisation.interfaces = mkOption { 580 - default = {}; 637 + default = { }; 581 638 example = { 582 639 enp1s0.vlan = 1; 583 640 }; 584 641 description = '' 585 642 Network interfaces to add to the VM. 586 643 ''; 587 - type = with types; attrsOf (submodule { 588 - options = { 589 - vlan = mkOption { 590 - type = types.ints.unsigned; 591 - description = '' 592 - VLAN to which the network interface is connected. 593 - ''; 594 - }; 644 + type = 645 + with types; 646 + attrsOf (submodule { 647 + options = { 648 + vlan = mkOption { 649 + type = types.ints.unsigned; 650 + description = '' 651 + VLAN to which the network interface is connected. 652 + ''; 653 + }; 595 654 596 - assignIP = mkOption { 597 - type = types.bool; 598 - default = false; 599 - description = '' 600 - Automatically assign an IP address to the network interface using the same scheme as 601 - virtualisation.vlans. 602 - ''; 655 + assignIP = mkOption { 656 + type = types.bool; 657 + default = false; 658 + description = '' 659 + Automatically assign an IP address to the network interface using the same scheme as 660 + virtualisation.vlans. 661 + ''; 662 + }; 603 663 }; 604 - }; 605 - }); 664 + }); 606 665 }; 607 666 608 - virtualisation.writableStore = 609 - mkOption { 610 - type = types.bool; 611 - default = cfg.mountHostNixStore; 612 - defaultText = literalExpression "cfg.mountHostNixStore"; 613 - description = '' 614 - If enabled, the Nix store in the VM is made writable by 615 - layering an overlay filesystem on top of the host's Nix 616 - store. 667 + virtualisation.writableStore = mkOption { 668 + type = types.bool; 669 + default = cfg.mountHostNixStore; 670 + defaultText = literalExpression "cfg.mountHostNixStore"; 671 + description = '' 672 + If enabled, the Nix store in the VM is made writable by 673 + layering an overlay filesystem on top of the host's Nix 674 + store. 617 675 618 - By default, this is enabled if you mount a host Nix store. 619 - ''; 620 - }; 676 + By default, this is enabled if you mount a host Nix store. 677 + ''; 678 + }; 621 679 622 - virtualisation.writableStoreUseTmpfs = 623 - mkOption { 624 - type = types.bool; 625 - default = true; 626 - description = '' 627 - Use a tmpfs for the writable store instead of writing to the VM's 628 - own filesystem. 629 - ''; 630 - }; 680 + virtualisation.writableStoreUseTmpfs = mkOption { 681 + type = types.bool; 682 + default = true; 683 + description = '' 684 + Use a tmpfs for the writable store instead of writing to the VM's 685 + own filesystem. 686 + ''; 687 + }; 631 688 632 - networking.primaryIPAddress = 633 - mkOption { 634 - type = types.str; 635 - default = ""; 636 - internal = true; 637 - description = "Primary IP address used in /etc/hosts."; 638 - }; 689 + networking.primaryIPAddress = mkOption { 690 + type = types.str; 691 + default = ""; 692 + internal = true; 693 + description = "Primary IP address used in /etc/hosts."; 694 + }; 639 695 640 - networking.primaryIPv6Address = 641 - mkOption { 642 - type = types.str; 643 - default = ""; 644 - internal = true; 645 - description = "Primary IPv6 address used in /etc/hosts."; 646 - }; 696 + networking.primaryIPv6Address = mkOption { 697 + type = types.str; 698 + default = ""; 699 + internal = true; 700 + description = "Primary IPv6 address used in /etc/hosts."; 701 + }; 647 702 648 703 virtualisation.host.pkgs = mkOption { 649 704 type = options.nixpkgs.pkgs.type; ··· 659 714 }; 660 715 661 716 virtualisation.qemu = { 662 - package = 663 - mkOption { 664 - type = types.package; 665 - default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu; 666 - defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; 667 - example = literalExpression "pkgs.qemu_test"; 668 - description = "QEMU package to use."; 669 - }; 717 + package = mkOption { 718 + type = types.package; 719 + default = 720 + if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then 721 + hostPkgs.qemu_kvm 722 + else 723 + hostPkgs.qemu; 724 + defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; 725 + example = literalExpression "pkgs.qemu_test"; 726 + description = "QEMU package to use."; 727 + }; 670 728 671 - options = 672 - mkOption { 673 - type = types.listOf types.str; 674 - default = []; 675 - example = [ "-vga std" ]; 676 - description = '' 677 - Options passed to QEMU. 678 - See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. 679 - ''; 680 - }; 729 + options = mkOption { 730 + type = types.listOf types.str; 731 + default = [ ]; 732 + example = [ "-vga std" ]; 733 + description = '' 734 + Options passed to QEMU. 735 + See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. 736 + ''; 737 + }; 681 738 682 739 consoles = mkOption { 683 740 type = types.listOf types.str; 684 - default = let 685 - consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; 686 - in if cfg.graphics then consoles else reverseList consoles; 741 + default = 742 + let 743 + consoles = [ 744 + "${qemu-common.qemuSerialDevice},115200n8" 745 + "tty0" 746 + ]; 747 + in 748 + if cfg.graphics then consoles else reverseList consoles; 687 749 example = [ "console=tty1" ]; 688 750 description = '' 689 751 The output console devices to pass to the kernel command line via the ··· 696 758 ''; 697 759 }; 698 760 699 - networkingOptions = 700 - mkOption { 701 - type = types.listOf types.str; 702 - default = [ ]; 703 - example = [ 704 - "-net nic,netdev=user.0,model=virtio" 705 - "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" 706 - ]; 707 - description = '' 708 - Networking-related command-line options that should be passed to qemu. 709 - The default is to use userspace networking (SLiRP). 710 - See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. 761 + networkingOptions = mkOption { 762 + type = types.listOf types.str; 763 + default = [ ]; 764 + example = [ 765 + "-net nic,netdev=user.0,model=virtio" 766 + "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" 767 + ]; 768 + description = '' 769 + Networking-related command-line options that should be passed to qemu. 770 + The default is to use userspace networking (SLiRP). 771 + See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. 711 772 712 - If you override this option, be advised to keep 713 - `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) 714 - to keep the default runtime behaviour. 715 - ''; 716 - }; 717 - 718 - drives = 719 - mkOption { 720 - type = types.listOf (types.submodule driveOpts); 721 - description = "Drives passed to qemu."; 722 - }; 723 - 724 - diskInterface = 725 - mkOption { 726 - type = types.enum [ "virtio" "scsi" "ide" ]; 727 - default = "virtio"; 728 - example = "scsi"; 729 - description = "The interface used for the virtual hard disks."; 730 - }; 773 + If you override this option, be advised to keep 774 + `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) 775 + to keep the default runtime behaviour. 776 + ''; 777 + }; 731 778 732 - guestAgent.enable = 733 - mkOption { 734 - type = types.bool; 735 - default = true; 736 - description = '' 737 - Enable the Qemu guest agent. 738 - ''; 739 - }; 779 + drives = mkOption { 780 + type = types.listOf (types.submodule driveOpts); 781 + description = "Drives passed to qemu."; 782 + }; 740 783 741 - virtioKeyboard = 742 - mkOption { 743 - type = types.bool; 744 - default = true; 745 - description = '' 746 - Enable the virtio-keyboard device. 747 - ''; 748 - }; 749 - }; 784 + diskInterface = mkOption { 785 + type = types.enum [ 786 + "virtio" 787 + "scsi" 788 + "ide" 789 + ]; 790 + default = "virtio"; 791 + example = "scsi"; 792 + description = "The interface used for the virtual hard disks."; 793 + }; 750 794 751 - virtualisation.useNixStoreImage = 752 - mkOption { 795 + guestAgent.enable = mkOption { 753 796 type = types.bool; 754 - default = false; 797 + default = true; 755 798 description = '' 756 - Build and use a disk image for the Nix store, instead of 757 - accessing the host's one through 9p. 758 - 759 - For applications which do a lot of reads from the store, 760 - this can drastically improve performance, but at the cost of 761 - disk space and image build time. 762 - 763 - The Nix store image is built just-in-time right before the VM is 764 - started. Because it does not produce another derivation, the image is 765 - not cached between invocations and never lands in the store or binary 766 - cache. 767 - 768 - If you want a full disk image with a partition table and a root 769 - filesystem instead of only a store image, enable 770 - {option}`virtualisation.useBootLoader` instead. 799 + Enable the Qemu guest agent. 771 800 ''; 772 801 }; 773 802 774 - virtualisation.mountHostNixStore = 775 - mkOption { 803 + virtioKeyboard = mkOption { 776 804 type = types.bool; 777 - default = !cfg.useNixStoreImage && !cfg.useBootLoader; 778 - defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; 805 + default = true; 779 806 description = '' 780 - Mount the host Nix store as a 9p mount. 807 + Enable the virtio-keyboard device. 781 808 ''; 782 809 }; 810 + }; 783 811 784 - virtualisation.directBoot = { 785 - enable = 786 - mkOption { 787 - type = types.bool; 788 - default = !cfg.useBootLoader; 789 - defaultText = "!cfg.useBootLoader"; 790 - description = '' 791 - If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. 792 - Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) 812 + virtualisation.useNixStoreImage = mkOption { 813 + type = types.bool; 814 + default = false; 815 + description = '' 816 + Build and use a disk image for the Nix store, instead of 817 + accessing the host's one through 9p. 793 818 794 - This is enabled by default. 795 - If you want to test netboot, consider disabling this option. 796 - Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. 819 + For applications which do a lot of reads from the store, 820 + this can drastically improve performance, but at the cost of 821 + disk space and image build time. 797 822 798 - Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. 799 - Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. 800 - They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details 801 - For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. 823 + The Nix store image is built just-in-time right before the VM is 824 + started. Because it does not produce another derivation, the image is 825 + not cached between invocations and never lands in the store or binary 826 + cache. 802 827 803 - This will not (re-)boot correctly into a system that has switched to a different configuration on disk. 804 - ''; 805 - }; 806 - initrd = 807 - mkOption { 808 - type = types.str; 809 - default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; 810 - defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; 811 - description = '' 812 - In direct boot situations, you may want to influence the initrd to load 813 - to use your own customized payload. 828 + If you want a full disk image with a partition table and a root 829 + filesystem instead of only a store image, enable 830 + {option}`virtualisation.useBootLoader` instead. 831 + ''; 832 + }; 814 833 815 - This is useful if you want to test the netboot image without 816 - testing the firmware or the loading part. 817 - ''; 818 - }; 834 + virtualisation.mountHostNixStore = mkOption { 835 + type = types.bool; 836 + default = !cfg.useNixStoreImage && !cfg.useBootLoader; 837 + defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; 838 + description = '' 839 + Mount the host Nix store as a 9p mount. 840 + ''; 819 841 }; 820 842 821 - virtualisation.useBootLoader = 822 - mkOption { 843 + virtualisation.directBoot = { 844 + enable = mkOption { 823 845 type = types.bool; 824 - default = false; 846 + default = !cfg.useBootLoader; 847 + defaultText = "!cfg.useBootLoader"; 825 848 description = '' 826 - Use a boot loader to boot the system. 827 - This allows, among other things, testing the boot loader. 849 + If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. 850 + Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) 828 851 829 - If disabled, the kernel and initrd are directly booted, 830 - forgoing any bootloader. 852 + This is enabled by default. 853 + If you want to test netboot, consider disabling this option. 854 + Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. 831 855 832 - Check the documentation on {option}`virtualisation.directBoot.enable` for details. 833 - ''; 834 - }; 856 + Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. 857 + Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. 858 + They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details 859 + For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. 835 860 836 - virtualisation.installBootLoader = 837 - mkOption { 838 - type = types.bool; 839 - default = cfg.useBootLoader && cfg.useDefaultFilesystems; 840 - defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems"; 861 + This will not (re-)boot correctly into a system that has switched to a different configuration on disk. 862 + ''; 863 + }; 864 + initrd = mkOption { 865 + type = types.str; 866 + default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; 867 + defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; 841 868 description = '' 842 - Install boot loader to target image. 869 + In direct boot situations, you may want to influence the initrd to load 870 + to use your own customized payload. 843 871 844 - This is best-effort and may break with unconventional partition setups. 845 - Use `virtualisation.useDefaultFilesystems` for a known-working configuration. 872 + This is useful if you want to test the netboot image without 873 + testing the firmware or the loading part. 846 874 ''; 847 875 }; 876 + }; 877 + 878 + virtualisation.useBootLoader = mkOption { 879 + type = types.bool; 880 + default = false; 881 + description = '' 882 + Use a boot loader to boot the system. 883 + This allows, among other things, testing the boot loader. 884 + 885 + If disabled, the kernel and initrd are directly booted, 886 + forgoing any bootloader. 848 887 849 - virtualisation.useEFIBoot = 850 - mkOption { 851 - type = types.bool; 852 - default = false; 853 - description = '' 854 - If enabled, the virtual machine will provide a EFI boot 855 - manager. 856 - useEFIBoot is ignored if useBootLoader == false. 857 - ''; 858 - }; 888 + Check the documentation on {option}`virtualisation.directBoot.enable` for details. 889 + ''; 890 + }; 891 + 892 + virtualisation.installBootLoader = mkOption { 893 + type = types.bool; 894 + default = cfg.useBootLoader && cfg.useDefaultFilesystems; 895 + defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems"; 896 + description = '' 897 + Install boot loader to target image. 898 + 899 + This is best-effort and may break with unconventional partition setups. 900 + Use `virtualisation.useDefaultFilesystems` for a known-working configuration. 901 + ''; 902 + }; 903 + 904 + virtualisation.useEFIBoot = mkOption { 905 + type = types.bool; 906 + default = false; 907 + description = '' 908 + If enabled, the virtual machine will provide a EFI boot 909 + manager. 910 + useEFIBoot is ignored if useBootLoader == false. 911 + ''; 912 + }; 859 913 860 914 virtualisation.efi = { 861 915 OVMF = mkOption { 862 916 type = types.package; 863 - default = (pkgs.OVMF.override { 864 - secureBoot = cfg.useSecureBoot; 865 - }).fd; 866 - defaultText = ''(pkgs.OVMF.override { 867 - secureBoot = cfg.useSecureBoot; 868 - }).fd''; 917 + default = 918 + (pkgs.OVMF.override { 919 + secureBoot = cfg.useSecureBoot; 920 + }).fd; 921 + defaultText = '' 922 + (pkgs.OVMF.override { 923 + secureBoot = cfg.useSecureBoot; 924 + }).fd''; 869 925 description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; 870 926 }; 871 927 ··· 874 930 default = cfg.efi.OVMF.firmware; 875 931 defaultText = literalExpression "cfg.efi.OVMF.firmware"; 876 932 description = '' 877 - Firmware binary for EFI implementation, defaults to OVMF. 878 - ''; 933 + Firmware binary for EFI implementation, defaults to OVMF. 934 + ''; 879 935 }; 880 936 881 937 variables = mkOption { ··· 883 939 default = cfg.efi.OVMF.variables; 884 940 defaultText = literalExpression "cfg.efi.OVMF.variables"; 885 941 description = '' 886 - Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. 887 - Defaults to OVMF. 888 - ''; 942 + Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. 943 + Defaults to OVMF. 944 + ''; 889 945 }; 890 946 891 947 keepVariables = mkOption { ··· 903 959 904 960 deviceModel = mkOption { 905 961 type = types.str; 906 - default = ({ 907 - "i686-linux" = "tpm-tis"; 908 - "x86_64-linux" = "tpm-tis"; 909 - "ppc64-linux" = "tpm-spapr"; 910 - "armv7-linux" = "tpm-tis-device"; 911 - "aarch64-linux" = "tpm-tis-device"; 912 - }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")); 962 + default = ( 963 + { 964 + "i686-linux" = "tpm-tis"; 965 + "x86_64-linux" = "tpm-tis"; 966 + "ppc64-linux" = "tpm-spapr"; 967 + "armv7-linux" = "tpm-tis-device"; 968 + "aarch64-linux" = "tpm-tis-device"; 969 + } 970 + .${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU") 971 + ); 913 972 defaultText = '' 914 973 Based on the guest platform Linux system: 915 974 ··· 922 981 }; 923 982 }; 924 983 925 - virtualisation.useDefaultFilesystems = 926 - mkOption { 927 - type = types.bool; 928 - default = true; 929 - description = '' 930 - If enabled, the boot disk of the virtual machine will be 931 - formatted and mounted with the default filesystems for 932 - testing. Swap devices and LUKS will be disabled. 984 + virtualisation.useDefaultFilesystems = mkOption { 985 + type = types.bool; 986 + default = true; 987 + description = '' 988 + If enabled, the boot disk of the virtual machine will be 989 + formatted and mounted with the default filesystems for 990 + testing. Swap devices and LUKS will be disabled. 933 991 934 - If disabled, a root filesystem has to be specified and 935 - formatted (for example in the initial ramdisk). 936 - ''; 937 - }; 992 + If disabled, a root filesystem has to be specified and 993 + formatted (for example in the initial ramdisk). 994 + ''; 995 + }; 938 996 939 - virtualisation.useSecureBoot = 940 - mkOption { 941 - type = types.bool; 942 - default = false; 943 - description = '' 944 - Enable Secure Boot support in the EFI firmware. 945 - ''; 946 - }; 997 + virtualisation.useSecureBoot = mkOption { 998 + type = types.bool; 999 + default = false; 1000 + description = '' 1001 + Enable Secure Boot support in the EFI firmware. 1002 + ''; 1003 + }; 947 1004 948 - virtualisation.bios = 949 - mkOption { 950 - type = types.nullOr types.package; 951 - default = null; 952 - description = '' 953 - An alternate BIOS (such as `qboot`) with which to start the VM. 954 - Should contain a file named `bios.bin`. 955 - If `null`, QEMU's builtin SeaBIOS will be used. 956 - ''; 957 - }; 1005 + virtualisation.bios = mkOption { 1006 + type = types.nullOr types.package; 1007 + default = null; 1008 + description = '' 1009 + An alternate BIOS (such as `qboot`) with which to start the VM. 1010 + Should contain a file named `bios.bin`. 1011 + If `null`, QEMU's builtin SeaBIOS will be used. 1012 + ''; 1013 + }; 958 1014 959 - virtualisation.useHostCerts = 960 - mkOption { 961 - type = types.bool; 962 - default = false; 963 - description = '' 964 - If enabled, when `NIX_SSL_CERT_FILE` is set on the host, 965 - pass the CA certificates from the host to the VM. 966 - ''; 967 - }; 1015 + virtualisation.useHostCerts = mkOption { 1016 + type = types.bool; 1017 + default = false; 1018 + description = '' 1019 + If enabled, when `NIX_SSL_CERT_FILE` is set on the host, 1020 + pass the CA certificates from the host to the VM. 1021 + ''; 1022 + }; 968 1023 969 1024 }; 970 1025 971 1026 config = { 972 1027 973 1028 assertions = 974 - lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: 975 - [ 976 - { assertion = rule.from == "guest" -> rule.proto == "tcp"; 977 - message = 978 - '' 1029 + lib.concatLists ( 1030 + lib.flip lib.imap cfg.forwardPorts ( 1031 + i: rule: [ 1032 + { 1033 + assertion = rule.from == "guest" -> rule.proto == "tcp"; 1034 + message = '' 979 1035 Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto: 980 1036 Guest forwarding supports only TCP connections. 981 1037 ''; 982 - } 983 - { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; 984 - message = 985 - '' 1038 + } 1039 + { 1040 + assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; 1041 + message = '' 986 1042 Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address: 987 1043 The address must be in the default VLAN (10.0.2.0/24). 988 1044 ''; 989 - } 990 - ])) ++ [ 991 - { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; 992 - message = '' 993 - virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. 994 - ''; 995 - } 996 - { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; 997 - message = 998 - '' 999 - You changed the default of `virtualisation.directBoot.initrd` but you are not 1000 - using QEMU direct boot. This initrd will not be used in your current 1001 - boot configuration. 1045 + } 1046 + ] 1047 + ) 1048 + ) 1049 + ++ [ 1050 + { 1051 + assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; 1052 + message = '' 1053 + virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. 1054 + ''; 1055 + } 1056 + { 1057 + assertion = 1058 + cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; 1059 + message = '' 1060 + You changed the default of `virtualisation.directBoot.initrd` but you are not 1061 + using QEMU direct boot. This initrd will not be used in your current 1062 + boot configuration. 1002 1063 1003 - Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. 1064 + Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. 1004 1065 1005 - If you have a more advanced usecase, please open an issue or a pull request. 1006 - ''; 1007 - } 1008 - { 1009 - assertion = cfg.installBootLoader -> config.system.switch.enable; 1010 - message = '' 1011 - `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work. 1012 - Please enable it in your configuration. 1013 - ''; 1014 - } 1015 - ]; 1066 + If you have a more advanced usecase, please open an issue or a pull request. 1067 + ''; 1068 + } 1069 + { 1070 + assertion = cfg.installBootLoader -> config.system.switch.enable; 1071 + message = '' 1072 + `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work. 1073 + Please enable it in your configuration. 1074 + ''; 1075 + } 1076 + ]; 1016 1077 1017 - warnings = 1018 - optional (cfg.directBoot.enable && cfg.useBootLoader) 1019 - '' 1020 - You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering 1021 - `useBootLoader` useless. You might want to disable one of those options. 1022 - ''; 1078 + warnings = optional (cfg.directBoot.enable && cfg.useBootLoader) '' 1079 + You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering 1080 + `useBootLoader` useless. You might want to disable one of those options. 1081 + ''; 1023 1082 1024 1083 # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install 1025 1084 # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs. ··· 1037 1096 # allow `system.build.toplevel' to be included. (If we had a direct 1038 1097 # reference to ${regInfo} here, then we would get a cyclic 1039 1098 # dependency.) 1040 - boot.postBootCommands = lib.mkIf config.nix.enable 1041 - '' 1042 - if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then 1043 - ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} 1044 - fi 1045 - ''; 1099 + boot.postBootCommands = lib.mkIf config.nix.enable '' 1100 + if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then 1101 + ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} 1102 + fi 1103 + ''; 1046 1104 1047 1105 boot.initrd.availableKernelModules = 1048 1106 optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" ··· 1079 1137 1080 1138 virtualisation.qemu.networkingOptions = 1081 1139 let 1082 - forwardingOptions = flip concatMapStrings cfg.forwardPorts 1083 - ({ proto, from, host, guest }: 1084 - if from == "host" 1085 - then "hostfwd=${proto}:${host.address}:${toString host.port}-" + 1086 - "${guest.address}:${toString guest.port}," 1087 - else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + 1088 - "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," 1089 - ); 1140 + forwardingOptions = flip concatMapStrings cfg.forwardPorts ( 1141 + { 1142 + proto, 1143 + from, 1144 + host, 1145 + guest, 1146 + }: 1147 + if from == "host" then 1148 + "hostfwd=${proto}:${host.address}:${toString host.port}-" 1149 + + "${guest.address}:${toString guest.port}," 1150 + else 1151 + "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" 1152 + + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," 1153 + ); 1090 1154 restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,"; 1091 1155 in 1092 1156 [ ··· 1099 1163 "-device virtio-keyboard" 1100 1164 ]) 1101 1165 (mkIf pkgs.stdenv.hostPlatform.isx86 [ 1102 - "-usb" "-device usb-tablet,bus=usb-bus.0" 1166 + "-usb" 1167 + "-device usb-tablet,bus=usb-bus.0" 1103 1168 ]) 1104 1169 (mkIf pkgs.stdenv.hostPlatform.isAarch [ 1105 - "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" 1170 + "-device virtio-gpu-pci" 1171 + "-device usb-ehci,id=usb0" 1172 + "-device usb-kbd" 1173 + "-device usb-tablet" 1106 1174 ]) 1107 - (let 1108 - alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); 1109 - # Replace all non-alphanumeric characters with underscores 1110 - sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); 1111 - in mkIf cfg.directBoot.enable [ 1112 - "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" 1113 - "-initrd ${cfg.directBoot.initrd}" 1114 - ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' 1115 - ]) 1175 + ( 1176 + let 1177 + alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); 1178 + # Replace all non-alphanumeric characters with underscores 1179 + sanitizeShellIdent = 1180 + s: 1181 + concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); 1182 + in 1183 + mkIf cfg.directBoot.enable [ 1184 + "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" 1185 + "-initrd ${cfg.directBoot.initrd}" 1186 + ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' 1187 + ] 1188 + ) 1116 1189 (mkIf cfg.useEFIBoot [ 1117 1190 "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}" 1118 1191 "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS" ··· 1129 1202 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" 1130 1203 ]) 1131 1204 (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [ 1132 - "-machine" "q35,smm=on" 1133 - "-global" "driver=cfi.pflash01,property=secure,value=on" 1205 + "-machine" 1206 + "q35,smm=on" 1207 + "-global" 1208 + "driver=cfi.pflash01,property=secure,value=on" 1134 1209 ]) 1135 1210 ]; 1136 1211 1137 1212 virtualisation.qemu.drives = mkMerge [ 1138 - (mkIf (cfg.diskImage != null) [{ 1139 - name = "root"; 1140 - file = ''"$NIX_DISK_IMAGE"''; 1141 - driveExtraOpts.cache = "writeback"; 1142 - driveExtraOpts.werror = "report"; 1143 - deviceExtraOpts.bootindex = "1"; 1144 - deviceExtraOpts.serial = rootDriveSerialAttr; 1145 - }]) 1146 - (mkIf cfg.useNixStoreImage [{ 1147 - name = "nix-store"; 1148 - file = ''"$TMPDIR"/store.img''; 1149 - deviceExtraOpts.bootindex = "2"; 1150 - driveExtraOpts.format = "raw"; 1151 - }]) 1213 + (mkIf (cfg.diskImage != null) [ 1214 + { 1215 + name = "root"; 1216 + file = ''"$NIX_DISK_IMAGE"''; 1217 + driveExtraOpts.cache = "writeback"; 1218 + driveExtraOpts.werror = "report"; 1219 + deviceExtraOpts.bootindex = "1"; 1220 + deviceExtraOpts.serial = rootDriveSerialAttr; 1221 + } 1222 + ]) 1223 + (mkIf cfg.useNixStoreImage [ 1224 + { 1225 + name = "nix-store"; 1226 + file = ''"$TMPDIR"/store.img''; 1227 + deviceExtraOpts.bootindex = "2"; 1228 + driveExtraOpts.format = "raw"; 1229 + } 1230 + ]) 1152 1231 (imap0 (idx: _: { 1153 1232 file = "$(pwd)/empty${toString idx}.qcow2"; 1154 1233 driveExtraOpts.werror = "report"; ··· 1162 1241 # override by setting `virtualisation.fileSystems = lib.mkForce { };`. 1163 1242 fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems); 1164 1243 1165 - virtualisation.fileSystems = let 1166 - mkSharedDir = tag: share: 1167 - { 1244 + virtualisation.diskSizeAutoSupported = false; 1245 + 1246 + virtualisation.fileSystems = 1247 + let 1248 + mkSharedDir = tag: share: { 1168 1249 name = share.target; 1169 1250 value.device = tag; 1170 1251 value.fsType = "9p"; 1171 1252 value.neededForBoot = true; 1172 - value.options = 1173 - [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" "x-systemd.requires=modprobe@9pnet_virtio.service" ] 1174 - ++ lib.optional (tag == "nix-store") "cache=loose"; 1175 - }; 1176 - in lib.mkMerge [ 1177 - (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) 1178 - { 1179 - "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then { 1180 - device = "tmpfs"; 1181 - fsType = "tmpfs"; 1182 - } else { 1183 - device = cfg.rootDevice; 1184 - fsType = "ext4"; 1185 - }); 1186 - "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { 1187 - device = "tmpfs"; 1188 - fsType = "tmpfs"; 1189 - neededForBoot = true; 1190 - # Sync with systemd's tmp.mount; 1191 - options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; 1253 + value.options = [ 1254 + "trans=virtio" 1255 + "version=9p2000.L" 1256 + "msize=${toString cfg.msize}" 1257 + "x-systemd.requires=modprobe@9pnet_virtio.service" 1258 + ] ++ lib.optional (tag == "nix-store") "cache=loose"; 1192 1259 }; 1193 - "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then { 1194 - overlay = { 1195 - lowerdir = [ "/nix/.ro-store" ]; 1196 - upperdir = "/nix/.rw-store/upper"; 1197 - workdir = "/nix/.rw-store/work"; 1260 + in 1261 + lib.mkMerge [ 1262 + (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) 1263 + { 1264 + "/" = lib.mkIf cfg.useDefaultFilesystems ( 1265 + if cfg.diskImage == null then 1266 + { 1267 + device = "tmpfs"; 1268 + fsType = "tmpfs"; 1269 + } 1270 + else 1271 + { 1272 + device = cfg.rootDevice; 1273 + fsType = "ext4"; 1274 + } 1275 + ); 1276 + "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { 1277 + device = "tmpfs"; 1278 + fsType = "tmpfs"; 1279 + neededForBoot = true; 1280 + # Sync with systemd's tmp.mount; 1281 + options = [ 1282 + "mode=1777" 1283 + "strictatime" 1284 + "nosuid" 1285 + "nodev" 1286 + "size=${toString config.boot.tmp.tmpfsSize}" 1287 + ]; 1198 1288 }; 1199 - } else { 1200 - device = "/nix/.ro-store"; 1201 - options = [ "bind" ]; 1202 - }); 1203 - "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { 1204 - device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; 1205 - fsType = "erofs"; 1206 - neededForBoot = true; 1207 - options = [ "ro" ]; 1208 - }; 1209 - "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { 1210 - fsType = "tmpfs"; 1211 - options = [ "mode=0755" ]; 1212 - neededForBoot = true; 1213 - }; 1214 - "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) { 1215 - device = cfg.bootPartition; 1216 - fsType = "vfat"; 1217 - }; 1218 - } 1219 - ]; 1289 + "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) ( 1290 + if cfg.writableStore then 1291 + { 1292 + overlay = { 1293 + lowerdir = [ "/nix/.ro-store" ]; 1294 + upperdir = "/nix/.rw-store/upper"; 1295 + workdir = "/nix/.rw-store/work"; 1296 + }; 1297 + } 1298 + else 1299 + { 1300 + device = "/nix/.ro-store"; 1301 + options = [ "bind" ]; 1302 + } 1303 + ); 1304 + "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { 1305 + device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; 1306 + fsType = "erofs"; 1307 + neededForBoot = true; 1308 + options = [ "ro" ]; 1309 + }; 1310 + "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { 1311 + fsType = "tmpfs"; 1312 + options = [ "mode=0755" ]; 1313 + neededForBoot = true; 1314 + }; 1315 + "${config.boot.loader.efi.efiSysMountPoint}" = 1316 + lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) 1317 + { 1318 + device = cfg.bootPartition; 1319 + fsType = "vfat"; 1320 + }; 1321 + } 1322 + ]; 1220 1323 1221 1324 swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; 1222 - boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; 1325 + boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) { }; 1223 1326 1224 1327 # Don't run ntpd in the guest. It should get the correct time from KVM. 1225 1328 services.timesyncd.enable = false; 1226 1329 1227 1330 services.qemuGuest.enable = cfg.qemu.guestAgent.enable; 1228 1331 1229 - system.build.vm = hostPkgs.runCommand "nixos-vm" { 1230 - preferLocalBuild = true; 1231 - meta.mainProgram = "run-${config.system.name}-vm"; 1232 - } 1233 - '' 1234 - mkdir -p $out/bin 1235 - ln -s ${config.system.build.toplevel} $out/system 1236 - ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm 1237 - ''; 1332 + system.build.vm = 1333 + hostPkgs.runCommand "nixos-vm" 1334 + { 1335 + preferLocalBuild = true; 1336 + meta.mainProgram = "run-${config.system.name}-vm"; 1337 + } 1338 + '' 1339 + mkdir -p $out/bin 1340 + ln -s ${config.system.build.toplevel} $out/system 1341 + ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm 1342 + ''; 1238 1343 1239 1344 # When building a regular system configuration, override whatever 1240 1345 # video driver the host uses. 1241 1346 services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; 1242 1347 services.xserver.defaultDepth = mkVMOverride 0; 1243 1348 services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; 1244 - services.xserver.monitorSection = 1245 - '' 1246 - # Set a higher refresh rate so that resolutions > 800x600 work. 1247 - HorizSync 30-140 1248 - VertRefresh 50-160 1249 - ''; 1349 + services.xserver.monitorSection = '' 1350 + # Set a higher refresh rate so that resolutions > 800x600 work. 1351 + HorizSync 30-140 1352 + VertRefresh 50-160 1353 + ''; 1250 1354 1251 1355 # Wireless won't work in the VM. 1252 1356 networking.wireless.enable = mkVMOverride false; ··· 1257 1361 1258 1362 networking.usePredictableInterfaceNames = false; 1259 1363 1260 - system.requiredKernelConfig = with config.lib.kernelConfig; 1261 - [ (isEnabled "VIRTIO_BLK") 1364 + system.requiredKernelConfig = 1365 + with config.lib.kernelConfig; 1366 + [ 1367 + (isEnabled "VIRTIO_BLK") 1262 1368 (isEnabled "VIRTIO_PCI") 1263 1369 (isEnabled "VIRTIO_NET") 1264 1370 (isEnabled "EXT4_FS") ··· 1270 1376 (isYes "NET_CORE") 1271 1377 (isYes "INET") 1272 1378 (isYes "NETWORK_FILESYSTEMS") 1273 - ] ++ optionals (!cfg.graphics) [ 1379 + ] 1380 + ++ optionals (!cfg.graphics) [ 1274 1381 (isYes "SERIAL_8250_CONSOLE") 1275 1382 (isYes "SERIAL_8250") 1276 - ] ++ optionals (cfg.writableStore) [ 1383 + ] 1384 + ++ optionals (cfg.writableStore) [ 1277 1385 (isEnabled "OVERLAY_FS") 1278 1386 ]; 1279 1387
+135 -92
nixos/modules/virtualisation/virtualbox-image.nix
··· 1 - { config, lib, pkgs, ... }: 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 2 7 let 3 8 4 9 cfg = config.virtualbox; 5 - 6 - in { 10 + in 11 + { 12 + imports = [ 13 + ./disk-size-option.nix 14 + (lib.mkRenamedOptionModuleWith { 15 + sinceRelease = 2411; 16 + from = [ 17 + "virtualbox" 18 + "baseImageSize" 19 + ]; 20 + to = [ 21 + "virtualisation" 22 + "diskSize" 23 + ]; 24 + }) 25 + ]; 7 26 8 27 options = { 9 28 virtualbox = { 10 - baseImageSize = lib.mkOption { 11 - type = with lib.types; either (enum [ "auto" ]) int; 12 - default = "auto"; 13 - example = 50 * 1024; 14 - description = '' 15 - The size of the VirtualBox base image in MiB. 16 - ''; 17 - }; 18 29 baseImageFreeSpace = lib.mkOption { 19 - type = with lib.types; int; 30 + type = lib.types.int; 20 31 default = 30 * 1024; 21 32 description = '' 22 33 Free space in the VirtualBox base image in MiB. ··· 51 62 ''; 52 63 }; 53 64 params = lib.mkOption { 54 - type = with lib.types; attrsOf (oneOf [ str int bool (listOf str) ]); 65 + type = 66 + with lib.types; 67 + attrsOf (oneOf [ 68 + str 69 + int 70 + bool 71 + (listOf str) 72 + ]); 55 73 example = { 56 74 audio = "alsa"; 57 75 rtcuseutc = "on"; ··· 64 82 ''; 65 83 }; 66 84 exportParams = lib.mkOption { 67 - type = with lib.types; listOf (oneOf [ str int bool (listOf str) ]); 85 + type = 86 + with lib.types; 87 + listOf (oneOf [ 88 + str 89 + int 90 + bool 91 + (listOf str) 92 + ]); 68 93 example = [ 69 - "--vsys" "0" "--vendor" "ACME Inc." 94 + "--vsys" 95 + "0" 96 + "--vendor" 97 + "ACME Inc." 70 98 ]; 71 - default = []; 99 + default = [ ]; 72 100 description = '' 73 101 Parameters passed to the Virtualbox export command. 74 102 ··· 86 114 mountPoint = "/home/demo/storage"; 87 115 size = 100 * 1024; 88 116 }; 89 - type = lib.types.nullOr (lib.types.submodule { 90 - options = { 91 - size = lib.mkOption { 92 - type = lib.types.int; 93 - description = "Size in MiB"; 117 + type = lib.types.nullOr ( 118 + lib.types.submodule { 119 + options = { 120 + size = lib.mkOption { 121 + type = lib.types.int; 122 + description = "Size in MiB"; 123 + }; 124 + label = lib.mkOption { 125 + type = lib.types.str; 126 + default = "vm-extra-storage"; 127 + description = "Label for the disk partition"; 128 + }; 129 + mountPoint = lib.mkOption { 130 + type = lib.types.str; 131 + description = "Path where to mount this disk."; 132 + }; 94 133 }; 95 - label = lib.mkOption { 96 - type = lib.types.str; 97 - default = "vm-extra-storage"; 98 - description = "Label for the disk partition"; 99 - }; 100 - mountPoint = lib.mkOption { 101 - type = lib.types.str; 102 - description = "Path where to mount this disk."; 103 - }; 104 - }; 105 - }); 134 + } 135 + ); 106 136 }; 107 137 postExportCommands = lib.mkOption { 108 138 type = lib.types.lines; ··· 122 152 ''; 123 153 }; 124 154 storageController = lib.mkOption { 125 - type = with lib.types; attrsOf (oneOf [ str int bool (listOf str) ]); 155 + type = 156 + with lib.types; 157 + attrsOf (oneOf [ 158 + str 159 + int 160 + bool 161 + (listOf str) 162 + ]); 126 163 example = { 127 164 name = "SCSI"; 128 165 add = "scsi"; ··· 148 185 }; 149 186 150 187 config = { 188 + # Use a priority just below mkOptionDefault (1500) instead of lib.mkDefault 189 + # to avoid breaking existing configs using that. 190 + virtualisation.diskSize = lib.mkOverride 1490 (50 * 1024); 151 191 152 192 virtualbox.params = lib.mkMerge [ 153 193 (lib.mapAttrs (name: lib.mkDefault) { ··· 172 212 173 213 inherit pkgs lib config; 174 214 partitionTableType = "legacy"; 175 - diskSize = cfg.baseImageSize; 215 + inherit (config.virtualisation) diskSize; 176 216 additionalSpace = "${toString cfg.baseImageFreeSpace}M"; 177 217 178 - postVM = 179 - '' 180 - export HOME=$PWD 181 - export PATH=${pkgs.virtualbox}/bin:$PATH 218 + postVM = '' 219 + export HOME=$PWD 220 + export PATH=${pkgs.virtualbox}/bin:$PATH 182 221 183 - echo "converting image to VirtualBox format..." 184 - VBoxManage convertfromraw $diskImage disk.vdi 222 + echo "converting image to VirtualBox format..." 223 + VBoxManage convertfromraw $diskImage disk.vdi 185 224 186 - ${lib.optionalString (cfg.extraDisk != null) '' 187 - echo "creating extra disk: data-disk.raw" 188 - dataDiskImage=data-disk.raw 189 - truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage 225 + ${lib.optionalString (cfg.extraDisk != null) '' 226 + echo "creating extra disk: data-disk.raw" 227 + dataDiskImage=data-disk.raw 228 + truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage 190 229 191 - parted --script $dataDiskImage -- \ 192 - mklabel msdos \ 193 - mkpart primary ext4 1MiB -1 194 - eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) 195 - mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K 196 - echo "creating extra disk: data-disk.vdi" 197 - VBoxManage convertfromraw $dataDiskImage data-disk.vdi 198 - ''} 230 + parted --script $dataDiskImage -- \ 231 + mklabel msdos \ 232 + mkpart primary ext4 1MiB -1 233 + eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs) 234 + mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K 235 + echo "creating extra disk: data-disk.vdi" 236 + VBoxManage convertfromraw $dataDiskImage data-disk.vdi 237 + ''} 199 238 200 - echo "creating VirtualBox VM..." 201 - vmName="${cfg.vmName}"; 202 - VBoxManage createvm --name "$vmName" --register \ 203 - --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} 204 - VBoxManage modifyvm "$vmName" \ 205 - --memory ${toString cfg.memorySize} \ 206 - ${lib.cli.toGNUCommandLineShell { } cfg.params} 207 - VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} 208 - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ 209 - --medium disk.vdi 210 - ${lib.optionalString (cfg.extraDisk != null) '' 211 - VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ 212 - --medium data-disk.vdi 213 - ''} 239 + echo "creating VirtualBox VM..." 240 + vmName="${cfg.vmName}"; 241 + VBoxManage createvm --name "$vmName" --register \ 242 + --ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"} 243 + VBoxManage modifyvm "$vmName" \ 244 + --memory ${toString cfg.memorySize} \ 245 + ${lib.cli.toGNUCommandLineShell { } cfg.params} 246 + VBoxManage storagectl "$vmName" ${lib.cli.toGNUCommandLineShell { } cfg.storageController} 247 + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 0 --device 0 --type hdd \ 248 + --medium disk.vdi 249 + ${lib.optionalString (cfg.extraDisk != null) '' 250 + VBoxManage storageattach "$vmName" --storagectl ${cfg.storageController.name} --port 1 --device 0 --type hdd \ 251 + --medium data-disk.vdi 252 + ''} 214 253 215 - echo "exporting VirtualBox VM..." 216 - mkdir -p $out 217 - fn="$out/${cfg.vmFileName}" 218 - VBoxManage export "$vmName" --output "$fn" --options manifest ${lib.escapeShellArgs cfg.exportParams} 219 - ${cfg.postExportCommands} 254 + echo "exporting VirtualBox VM..." 255 + mkdir -p $out 256 + fn="$out/${cfg.vmFileName}" 257 + VBoxManage export "$vmName" --output "$fn" --options manifest ${lib.escapeShellArgs cfg.exportParams} 258 + ${cfg.postExportCommands} 220 259 221 - rm -v $diskImage 260 + rm -v $diskImage 222 261 223 - mkdir -p $out/nix-support 224 - echo "file ova $fn" >> $out/nix-support/hydra-build-products 225 - ''; 262 + mkdir -p $out/nix-support 263 + echo "file ova $fn" >> $out/nix-support/hydra-build-products 264 + ''; 226 265 }; 227 266 228 - fileSystems = { 229 - "/" = { 230 - device = "/dev/disk/by-label/nixos"; 231 - autoResize = true; 232 - fsType = "ext4"; 233 - }; 234 - } // (lib.optionalAttrs (cfg.extraDisk != null) { 235 - ${cfg.extraDisk.mountPoint} = { 236 - device = "/dev/disk/by-label/" + cfg.extraDisk.label; 237 - autoResize = true; 238 - fsType = "ext4"; 239 - }; 240 - }); 267 + fileSystems = 268 + { 269 + "/" = { 270 + device = "/dev/disk/by-label/nixos"; 271 + autoResize = true; 272 + fsType = "ext4"; 273 + }; 274 + } 275 + // (lib.optionalAttrs (cfg.extraDisk != null) { 276 + ${cfg.extraDisk.mountPoint} = { 277 + device = "/dev/disk/by-label/" + cfg.extraDisk.label; 278 + autoResize = true; 279 + fsType = "ext4"; 280 + }; 281 + }); 241 282 242 283 boot.growPartition = true; 243 284 boot.loader.grub.device = "/dev/sda"; 244 285 245 - swapDevices = [{ 246 - device = "/var/swap"; 247 - size = 2048; 248 - }]; 286 + swapDevices = [ 287 + { 288 + device = "/var/swap"; 289 + size = 2048; 290 + } 291 + ]; 249 292 250 293 virtualisation.virtualbox.guest.enable = true; 251 294
+1 -1
nixos/release.nix
··· 312 312 [ configuration 313 313 versionModule 314 314 ./maintainers/scripts/ec2/amazon-image.nix 315 - ({ ... }: { amazonImage.sizeMB = "auto"; }) 315 + ({ ... }: { amazonImage.virtualisation.diskSize = "auto"; }) 316 316 ]; 317 317 }).config.system.build.amazonImage) 318 318