at 23.11-beta 49 kB view raw
1# This module creates a virtual machine from the NixOS configuration. 2# Building the `config.system.build.vm' attribute gives you a command 3# that starts a KVM/QEMU VM running the NixOS configuration defined in 4# `config'. By default, the Nix store is shared read-only with the 5# host, which makes (re)building VMs very efficient. 6 7{ config, lib, pkgs, options, ... }: 8 9with lib; 10 11let 12 13 qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; }; 14 15 cfg = config.virtualisation; 16 17 opt = options.virtualisation; 18 19 qemu = cfg.qemu.package; 20 21 hostPkgs = cfg.host.pkgs; 22 23 consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; 24 25 driveOpts = { ... }: { 26 27 options = { 28 29 file = mkOption { 30 type = types.str; 31 description = lib.mdDoc "The file image used for this drive."; 32 }; 33 34 driveExtraOpts = mkOption { 35 type = types.attrsOf types.str; 36 default = {}; 37 description = lib.mdDoc "Extra options passed to drive flag."; 38 }; 39 40 deviceExtraOpts = mkOption { 41 type = types.attrsOf types.str; 42 default = {}; 43 description = lib.mdDoc "Extra options passed to device flag."; 44 }; 45 46 name = mkOption { 47 type = types.nullOr types.str; 48 default = null; 49 description = 50 lib.mdDoc "A name for the drive. Must be unique in the drives list. Not passed to qemu."; 51 }; 52 53 }; 54 55 }; 56 57 selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }: 58 if useDefaultFilesystems then 59 if useEFIBoot then "efi" else "legacy" 60 else "none"; 61 62 driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: 63 let 64 drvId = "drive${toString idx}"; 65 mkKeyValue = generators.mkKeyValueDefault {} "="; 66 mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); 67 driveOpts = mkOpts (driveExtraOpts // { 68 index = idx; 69 id = drvId; 70 "if" = "none"; 71 inherit file; 72 }); 73 deviceOpts = mkOpts (deviceExtraOpts // { 74 drive = drvId; 75 }); 76 device = 77 if cfg.qemu.diskInterface == "scsi" then 78 "-device lsi53c895a -device scsi-hd,${deviceOpts}" 79 else 80 "-device virtio-blk-pci,${deviceOpts}"; 81 in 82 "-drive ${driveOpts} ${device}"; 83 84 drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); 85 86 # Shell script to start the VM. 87 startVM = 88 '' 89 #! ${hostPkgs.runtimeShell} 90 91 export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH 92 93 set -e 94 95 # Create an empty ext4 filesystem image. A filesystem image does not 96 # contain a partition table but just a filesystem. 97 createEmptyFilesystemImage() { 98 local name=$1 99 local size=$2 100 local temp=$(mktemp) 101 ${qemu}/bin/qemu-img create -f raw "$temp" "$size" 102 ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" 103 ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" 104 rm "$temp" 105 } 106 107 NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" 108 109 if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then 110 echo "Disk image do not exist, creating the virtualisation disk image..." 111 112 ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then '' 113 # Create a writable qcow2 image using the systemImage as a backing 114 # image. 115 116 # CoW prevent size to be attributed to an image. 117 # FIXME: raise this issue to upstream. 118 ${qemu}/bin/qemu-img create \ 119 -f qcow2 \ 120 -b ${systemImage}/nixos.qcow2 \ 121 -F qcow2 \ 122 "$NIX_DISK_IMAGE" 123 '' else if cfg.useDefaultFilesystems then '' 124 createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 125 '' else '' 126 # Create an empty disk image without a filesystem. 127 ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 128 '' 129 } 130 echo "Virtualisation disk image created." 131 fi 132 133 # Create a directory for storing temporary data of the running VM. 134 if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then 135 TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) 136 fi 137 138 ${lib.optionalString (cfg.useNixStoreImage) 139 (if cfg.writableStore 140 then '' 141 # Create a writable copy/snapshot of the store image. 142 ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${storeImage}/nixos.qcow2 "$TMPDIR"/store.img 143 '' 144 else '' 145 ( 146 cd ${builtins.storeDir} 147 ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ 148 --force-uid=0 \ 149 --force-gid=0 \ 150 -L ${nixStoreFilesystemLabel} \ 151 -U eb176051-bd15-49b7-9e6b-462e0b467019 \ 152 -T 0 \ 153 --exclude-regex="$( 154 <${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \ 155 sed -e 's^.*/^^g' \ 156 | cut -c -10 \ 157 | ${hostPkgs.python3}/bin/python ${./includes-to-excludes.py} )" \ 158 "$TMPDIR"/store.img \ 159 . \ 160 </dev/null >/dev/null 161 ) 162 '' 163 ) 164 } 165 166 # Create a directory for exchanging data with the VM. 167 mkdir -p "$TMPDIR/xchg" 168 169 ${lib.optionalString cfg.useHostCerts 170 '' 171 mkdir -p "$TMPDIR/certs" 172 if [ -e "$NIX_SSL_CERT_FILE" ]; then 173 cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt 174 else 175 echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. 176 fi 177 ''} 178 179 ${lib.optionalString cfg.useEFIBoot 180 '' 181 # Expose EFI variables, it's useful even when we are not using a bootloader (!). 182 # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence 183 # no guard against `useBootLoader`. Examples: 184 # - testing PXE boot or other EFI applications 185 # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows 186 NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") 187 # VM needs writable EFI vars 188 if ! test -e "$NIX_EFI_VARS"; then 189 ${if cfg.useBootLoader then 190 # We still need the EFI var from the make-disk-image derivation 191 # because our "switch-to-configuration" process might 192 # write into it and we want to keep this data. 193 ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' 194 else 195 ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' 196 } 197 chmod 0644 "$NIX_EFI_VARS" 198 fi 199 ''} 200 201 ${lib.optionalString cfg.tpm.enable '' 202 NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") 203 mkdir -p "$NIX_SWTPM_DIR" 204 ${lib.getExe cfg.tpm.package} \ 205 socket \ 206 --tpmstate dir="$NIX_SWTPM_DIR" \ 207 --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ 208 --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 209 --tpm2 \ 210 --log file="$NIX_SWTPM_DIR"/stdout,level=6 211 212 # Enable `fdflags` builtin in Bash 213 # We will need it to perform surgical modification of the file descriptor 214 # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor 215 # on exec. 216 # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` 217 # at the end of this script. To work around that, we will just clear 218 # the `FD_CLOEXEC` bits as a first step. 219 enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags 220 # leave a dangling subprocess because the swtpm ctrl socket has 221 # "terminate" when the last connection disconnects, it stops swtpm. 222 # When qemu stops, or if the main shell process ends, the coproc will 223 # get signaled by virtue of the pipe between main and coproc ending. 224 # Which in turns triggers a socat connect-disconnect to swtpm which 225 # will stop it. 226 coproc waitingswtpm { 227 read || : 228 echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket 229 } 230 # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 231 fdflags -s-cloexec ''${waitingswtpm[1]} 232 ''} 233 234 cd "$TMPDIR" 235 236 ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} 237 ${flip concatMapStrings cfg.emptyDiskImages (size: '' 238 if ! test -e "empty$idx.qcow2"; then 239 ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" 240 fi 241 idx=$((idx + 1)) 242 '')} 243 244 # Start QEMU. 245 exec ${qemu-common.qemuBinary qemu} \ 246 -name ${config.system.name} \ 247 -m ${toString config.virtualisation.memorySize} \ 248 -smp ${toString config.virtualisation.cores} \ 249 -device virtio-rng-pci \ 250 ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ 251 ${concatStringsSep " \\\n " 252 (mapAttrsToList 253 (tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}") 254 config.virtualisation.sharedDirectories)} \ 255 ${drivesCmdLine config.virtualisation.qemu.drives} \ 256 ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ 257 $QEMU_OPTS \ 258 "$@" 259 ''; 260 261 262 regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; }; 263 264 # Use well-defined and persistent filesystem labels to identify block devices. 265 rootFilesystemLabel = "nixos"; 266 espFilesystemLabel = "ESP"; # Hard-coded by make-disk-image.nix 267 nixStoreFilesystemLabel = "nix-store"; 268 269 # The root drive is a raw disk which does not necessarily contain a 270 # filesystem or partition table. It thus cannot be identified via the typical 271 # persistent naming schemes (e.g. /dev/disk/by-{label, uuid, partlabel, 272 # partuuid}. Instead, supply a well-defined and persistent serial attribute 273 # via QEMU. Inside the running system, the disk can then be identified via 274 # the /dev/disk/by-id scheme. 275 rootDriveSerialAttr = "root"; 276 277 # System image is akin to a complete NixOS install with 278 # a boot partition and root partition. 279 systemImage = import ../../lib/make-disk-image.nix { 280 inherit pkgs config lib; 281 additionalPaths = [ regInfo ]; 282 format = "qcow2"; 283 onlyNixStore = false; 284 label = rootFilesystemLabel; 285 partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; }; 286 # Bootloader should be installed on the system image only if we are booting through bootloaders. 287 # Though, if a user is not using our default filesystems, it is possible to not have any ESP 288 # or a strange partition table that's incompatible with GRUB configuration. 289 # As a consequence, this may lead to disk image creation failures. 290 # To avoid this, we prefer to let the user find out about how to install the bootloader on its ESP/disk. 291 # Usually, this can be through building your own disk image. 292 # TODO: If a user is interested into a more fine grained heuristic for `installBootLoader` 293 # by examining the actual contents of `cfg.fileSystems`, please send a PR. 294 installBootLoader = cfg.useBootLoader && cfg.useDefaultFilesystems; 295 touchEFIVars = cfg.useEFIBoot; 296 diskSize = "auto"; 297 additionalSpace = "0M"; 298 copyChannel = false; 299 OVMF = cfg.efi.OVMF; 300 }; 301 302 storeImage = import ../../lib/make-disk-image.nix { 303 name = "nix-store-image"; 304 inherit pkgs config lib; 305 additionalPaths = [ regInfo ]; 306 format = "qcow2"; 307 onlyNixStore = true; 308 label = nixStoreFilesystemLabel; 309 partitionTableType = "none"; 310 installBootLoader = false; 311 touchEFIVars = false; 312 diskSize = "auto"; 313 additionalSpace = "0M"; 314 copyChannel = false; 315 }; 316 317in 318 319{ 320 imports = [ 321 ../profiles/qemu-guest.nix 322 (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ]) 323 (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.") 324 (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") 325 (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.`") 326 ]; 327 328 options = { 329 330 virtualisation.fileSystems = options.fileSystems; 331 332 virtualisation.memorySize = 333 mkOption { 334 type = types.ints.positive; 335 default = 1024; 336 description = 337 lib.mdDoc '' 338 The memory size in megabytes of the virtual machine. 339 ''; 340 }; 341 342 virtualisation.msize = 343 mkOption { 344 type = types.ints.positive; 345 default = 16384; 346 description = 347 lib.mdDoc '' 348 The msize (maximum packet size) option passed to 9p file systems, in 349 bytes. Increasing this should increase performance significantly, 350 at the cost of higher RAM usage. 351 ''; 352 }; 353 354 virtualisation.diskSize = 355 mkOption { 356 type = types.nullOr types.ints.positive; 357 default = 1024; 358 description = 359 lib.mdDoc '' 360 The disk size in megabytes of the virtual machine. 361 ''; 362 }; 363 364 virtualisation.diskImage = 365 mkOption { 366 type = types.nullOr types.str; 367 default = "./${config.system.name}.qcow2"; 368 defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; 369 description = 370 lib.mdDoc '' 371 Path to the disk image containing the root filesystem. 372 The image will be created on startup if it does not 373 exist. 374 375 If null, a tmpfs will be used as the root filesystem and 376 the VM's state will not be persistent. 377 ''; 378 }; 379 380 virtualisation.bootLoaderDevice = 381 mkOption { 382 type = types.path; 383 default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; 384 defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; 385 example = "/dev/disk/by-id/virtio-boot-loader-device"; 386 description = 387 lib.mdDoc '' 388 The path (inside th VM) to the device to boot from when legacy booting. 389 ''; 390 }; 391 392 virtualisation.bootPartition = 393 mkOption { 394 type = types.nullOr types.path; 395 default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; 396 defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; 397 example = "/dev/disk/by-label/esp"; 398 description = 399 lib.mdDoc '' 400 The path (inside the VM) to the device containing the EFI System Partition (ESP). 401 402 If you are *not* booting from a UEFI firmware, this value is, by 403 default, `null`. The ESP is mounted under `/boot`. 404 ''; 405 }; 406 407 virtualisation.rootDevice = 408 mkOption { 409 type = types.nullOr types.path; 410 default = "/dev/disk/by-label/${rootFilesystemLabel}"; 411 defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; 412 example = "/dev/disk/by-label/nixos"; 413 description = 414 lib.mdDoc '' 415 The path (inside the VM) to the device containing the root filesystem. 416 ''; 417 }; 418 419 virtualisation.emptyDiskImages = 420 mkOption { 421 type = types.listOf types.ints.positive; 422 default = []; 423 description = 424 lib.mdDoc '' 425 Additional disk images to provide to the VM. The value is 426 a list of size in megabytes of each disk. These disks are 427 writeable by the VM. 428 ''; 429 }; 430 431 virtualisation.graphics = 432 mkOption { 433 type = types.bool; 434 default = true; 435 description = 436 lib.mdDoc '' 437 Whether to run QEMU with a graphics window, or in nographic mode. 438 Serial console will be enabled on both settings, but this will 439 change the preferred console. 440 ''; 441 }; 442 443 virtualisation.resolution = 444 mkOption { 445 type = options.services.xserver.resolutions.type.nestedTypes.elemType; 446 default = { x = 1024; y = 768; }; 447 description = 448 lib.mdDoc '' 449 The resolution of the virtual machine display. 450 ''; 451 }; 452 453 virtualisation.cores = 454 mkOption { 455 type = types.ints.positive; 456 default = 1; 457 description = 458 lib.mdDoc '' 459 Specify the number of cores the guest is permitted to use. 460 The number can be higher than the available cores on the 461 host system. 462 ''; 463 }; 464 465 virtualisation.sharedDirectories = 466 mkOption { 467 type = types.attrsOf 468 (types.submodule { 469 options.source = mkOption { 470 type = types.str; 471 description = lib.mdDoc "The path of the directory to share, can be a shell variable"; 472 }; 473 options.target = mkOption { 474 type = types.path; 475 description = lib.mdDoc "The mount point of the directory inside the virtual machine"; 476 }; 477 }); 478 default = { }; 479 example = { 480 my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; 481 }; 482 description = 483 lib.mdDoc '' 484 An attributes set of directories that will be shared with the 485 virtual machine using VirtFS (9P filesystem over VirtIO). 486 The attribute name will be used as the 9P mount tag. 487 ''; 488 }; 489 490 virtualisation.additionalPaths = 491 mkOption { 492 type = types.listOf types.path; 493 default = []; 494 description = 495 lib.mdDoc '' 496 A list of paths whose closure should be made available to 497 the VM. 498 499 When 9p is used, the closure is registered in the Nix 500 database in the VM. All other paths in the host Nix store 501 appear in the guest Nix store as well, but are considered 502 garbage (because they are not registered in the Nix 503 database of the guest). 504 505 When {option}`virtualisation.useNixStoreImage` is 506 set, the closure is copied to the Nix store image. 507 ''; 508 }; 509 510 virtualisation.forwardPorts = mkOption { 511 type = types.listOf 512 (types.submodule { 513 options.from = mkOption { 514 type = types.enum [ "host" "guest" ]; 515 default = "host"; 516 description = 517 lib.mdDoc '' 518 Controls the direction in which the ports are mapped: 519 520 - `"host"` means traffic from the host ports 521 is forwarded to the given guest port. 522 - `"guest"` means traffic from the guest ports 523 is forwarded to the given host port. 524 ''; 525 }; 526 options.proto = mkOption { 527 type = types.enum [ "tcp" "udp" ]; 528 default = "tcp"; 529 description = lib.mdDoc "The protocol to forward."; 530 }; 531 options.host.address = mkOption { 532 type = types.str; 533 default = ""; 534 description = lib.mdDoc "The IPv4 address of the host."; 535 }; 536 options.host.port = mkOption { 537 type = types.port; 538 description = lib.mdDoc "The host port to be mapped."; 539 }; 540 options.guest.address = mkOption { 541 type = types.str; 542 default = ""; 543 description = lib.mdDoc "The IPv4 address on the guest VLAN."; 544 }; 545 options.guest.port = mkOption { 546 type = types.port; 547 description = lib.mdDoc "The guest port to be mapped."; 548 }; 549 }); 550 default = []; 551 example = lib.literalExpression 552 '' 553 [ # forward local port 2222 -> 22, to ssh into the VM 554 { from = "host"; host.port = 2222; guest.port = 22; } 555 556 # forward local port 80 -> 10.0.2.10:80 in the VLAN 557 { from = "guest"; 558 guest.address = "10.0.2.10"; guest.port = 80; 559 host.address = "127.0.0.1"; host.port = 80; 560 } 561 ] 562 ''; 563 description = 564 lib.mdDoc '' 565 When using the SLiRP user networking (default), this option allows to 566 forward ports to/from the host/guest. 567 568 ::: {.warning} 569 If the NixOS firewall on the virtual machine is enabled, you also 570 have to open the guest ports to enable the traffic between host and 571 guest. 572 ::: 573 574 ::: {.note} 575 Currently QEMU supports only IPv4 forwarding. 576 ::: 577 ''; 578 }; 579 580 virtualisation.restrictNetwork = 581 mkOption { 582 type = types.bool; 583 default = false; 584 example = true; 585 description = 586 lib.mdDoc '' 587 If this option is enabled, the guest will be isolated, i.e. it will 588 not be able to contact the host and no guest IP packets will be 589 routed over the host to the outside. This option does not affect 590 any explicitly set forwarding rules. 591 ''; 592 }; 593 594 virtualisation.vlans = 595 mkOption { 596 type = types.listOf types.ints.unsigned; 597 default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; 598 defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; 599 example = [ 1 2 ]; 600 description = 601 lib.mdDoc '' 602 Virtual networks to which the VM is connected. Each 603 number «N» in this list causes 604 the VM to have a virtual Ethernet interface attached to a 605 separate virtual network on which it will be assigned IP 606 address 607 `192.168.«N».«M»`, 608 where «M» is the index of this VM 609 in the list of VMs. 610 ''; 611 }; 612 613 virtualisation.interfaces = mkOption { 614 default = {}; 615 example = { 616 enp1s0.vlan = 1; 617 }; 618 description = lib.mdDoc '' 619 Network interfaces to add to the VM. 620 ''; 621 type = with types; attrsOf (submodule { 622 options = { 623 vlan = mkOption { 624 type = types.ints.unsigned; 625 description = lib.mdDoc '' 626 VLAN to which the network interface is connected. 627 ''; 628 }; 629 630 assignIP = mkOption { 631 type = types.bool; 632 default = false; 633 description = lib.mdDoc '' 634 Automatically assign an IP address to the network interface using the same scheme as 635 virtualisation.vlans. 636 ''; 637 }; 638 }; 639 }); 640 }; 641 642 virtualisation.writableStore = 643 mkOption { 644 type = types.bool; 645 default = cfg.mountHostNixStore; 646 defaultText = literalExpression "cfg.mountHostNixStore"; 647 description = 648 lib.mdDoc '' 649 If enabled, the Nix store in the VM is made writable by 650 layering an overlay filesystem on top of the host's Nix 651 store. 652 653 By default, this is enabled if you mount a host Nix store. 654 ''; 655 }; 656 657 virtualisation.writableStoreUseTmpfs = 658 mkOption { 659 type = types.bool; 660 default = true; 661 description = 662 lib.mdDoc '' 663 Use a tmpfs for the writable store instead of writing to the VM's 664 own filesystem. 665 ''; 666 }; 667 668 networking.primaryIPAddress = 669 mkOption { 670 type = types.str; 671 default = ""; 672 internal = true; 673 description = lib.mdDoc "Primary IP address used in /etc/hosts."; 674 }; 675 676 virtualisation.host.pkgs = mkOption { 677 type = options.nixpkgs.pkgs.type; 678 default = pkgs; 679 defaultText = literalExpression "pkgs"; 680 example = literalExpression '' 681 import pkgs.path { system = "x86_64-darwin"; } 682 ''; 683 description = lib.mdDoc '' 684 Package set to use for the host-specific packages of the VM runner. 685 Changing this to e.g. a Darwin package set allows running NixOS VMs on Darwin. 686 ''; 687 }; 688 689 virtualisation.qemu = { 690 package = 691 mkOption { 692 type = types.package; 693 default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu; 694 defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; 695 example = literalExpression "pkgs.qemu_test"; 696 description = lib.mdDoc "QEMU package to use."; 697 }; 698 699 options = 700 mkOption { 701 type = types.listOf types.str; 702 default = []; 703 example = [ "-vga std" ]; 704 description = lib.mdDoc "Options passed to QEMU."; 705 }; 706 707 consoles = mkOption { 708 type = types.listOf types.str; 709 default = let 710 consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; 711 in if cfg.graphics then consoles else reverseList consoles; 712 example = [ "console=tty1" ]; 713 description = lib.mdDoc '' 714 The output console devices to pass to the kernel command line via the 715 `console` parameter, the primary console is the last 716 item of this list. 717 718 By default it enables both serial console and 719 `tty0`. The preferred console (last one) is based on 720 the value of {option}`virtualisation.graphics`. 721 ''; 722 }; 723 724 networkingOptions = 725 mkOption { 726 type = types.listOf types.str; 727 default = [ ]; 728 example = [ 729 "-net nic,netdev=user.0,model=virtio" 730 "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" 731 ]; 732 description = lib.mdDoc '' 733 Networking-related command-line options that should be passed to qemu. 734 The default is to use userspace networking (SLiRP). 735 736 If you override this option, be advised to keep 737 ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the example) 738 to keep the default runtime behaviour. 739 ''; 740 }; 741 742 drives = 743 mkOption { 744 type = types.listOf (types.submodule driveOpts); 745 description = lib.mdDoc "Drives passed to qemu."; 746 }; 747 748 diskInterface = 749 mkOption { 750 type = types.enum [ "virtio" "scsi" "ide" ]; 751 default = "virtio"; 752 example = "scsi"; 753 description = lib.mdDoc "The interface used for the virtual hard disks."; 754 }; 755 756 guestAgent.enable = 757 mkOption { 758 type = types.bool; 759 default = true; 760 description = lib.mdDoc '' 761 Enable the Qemu guest agent. 762 ''; 763 }; 764 765 virtioKeyboard = 766 mkOption { 767 type = types.bool; 768 default = true; 769 description = lib.mdDoc '' 770 Enable the virtio-keyboard device. 771 ''; 772 }; 773 }; 774 775 virtualisation.useNixStoreImage = 776 mkOption { 777 type = types.bool; 778 default = false; 779 description = lib.mdDoc '' 780 Build and use a disk image for the Nix store, instead of 781 accessing the host's one through 9p. 782 783 For applications which do a lot of reads from the store, 784 this can drastically improve performance, but at the cost of 785 disk space and image build time. 786 787 As an alternative, you can use a bootloader which will provide you 788 with a full NixOS system image containing a Nix store and 789 avoid mounting the host nix store through 790 {option}`virtualisation.mountHostNixStore`. 791 ''; 792 }; 793 794 virtualisation.mountHostNixStore = 795 mkOption { 796 type = types.bool; 797 default = !cfg.useNixStoreImage && !cfg.useBootLoader; 798 defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; 799 description = lib.mdDoc '' 800 Mount the host Nix store as a 9p mount. 801 ''; 802 }; 803 804 virtualisation.directBoot = { 805 enable = 806 mkOption { 807 type = types.bool; 808 default = !cfg.useBootLoader; 809 defaultText = "!cfg.useBootLoader"; 810 description = 811 lib.mdDoc '' 812 If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. Other relevant parameters such as the initrd are also passed to QEMU. 813 814 If you want to test netboot, consider disabling this option. 815 816 This will not boot / reboot correctly into a system that has switched to a different configuration on disk. 817 818 This is enabled by default if you don't enable bootloaders, but you can still enable a bootloader if you need. 819 Read more about this feature: <https://qemu-project.gitlab.io/qemu/system/linuxboot.html>. 820 ''; 821 }; 822 initrd = 823 mkOption { 824 type = types.str; 825 default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; 826 defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; 827 description = 828 lib.mdDoc '' 829 In direct boot situations, you may want to influence the initrd to load 830 to use your own customized payload. 831 832 This is useful if you want to test the netboot image without 833 testing the firmware or the loading part. 834 ''; 835 }; 836 }; 837 838 virtualisation.useBootLoader = 839 mkOption { 840 type = types.bool; 841 default = false; 842 description = 843 lib.mdDoc '' 844 Use a boot loader to boot the system. 845 This allows, among other things, testing the boot loader. 846 847 If disabled, the kernel and initrd are directly booted, 848 forgoing any bootloader. 849 ''; 850 }; 851 852 virtualisation.useEFIBoot = 853 mkOption { 854 type = types.bool; 855 default = false; 856 description = 857 lib.mdDoc '' 858 If enabled, the virtual machine will provide a EFI boot 859 manager. 860 useEFIBoot is ignored if useBootLoader == false. 861 ''; 862 }; 863 864 virtualisation.efi = { 865 OVMF = mkOption { 866 type = types.package; 867 default = (pkgs.OVMF.override { 868 secureBoot = cfg.useSecureBoot; 869 }).fd; 870 defaultText = ''(pkgs.OVMF.override { 871 secureBoot = cfg.useSecureBoot; 872 }).fd''; 873 description = 874 lib.mdDoc "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; 875 }; 876 877 firmware = mkOption { 878 type = types.path; 879 default = cfg.efi.OVMF.firmware; 880 defaultText = literalExpression "cfg.efi.OVMF.firmware"; 881 description = 882 lib.mdDoc '' 883 Firmware binary for EFI implementation, defaults to OVMF. 884 ''; 885 }; 886 887 variables = mkOption { 888 type = types.path; 889 default = cfg.efi.OVMF.variables; 890 defaultText = literalExpression "cfg.efi.OVMF.variables"; 891 description = 892 lib.mdDoc '' 893 Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. 894 Defaults to OVMF. 895 ''; 896 }; 897 }; 898 899 virtualisation.tpm = { 900 enable = mkEnableOption "a TPM device in the virtual machine with a driver, using swtpm."; 901 902 package = mkPackageOptionMD cfg.host.pkgs "swtpm" { }; 903 904 deviceModel = mkOption { 905 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.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")); 913 defaultText = '' 914 Based on the guest platform Linux system: 915 916 - `tpm-tis` for (i686, x86_64) 917 - `tpm-spapr` for ppc64 918 - `tpm-tis-device` for (armv7, aarch64) 919 ''; 920 example = "tpm-tis-device"; 921 description = lib.mdDoc "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed."; 922 }; 923 }; 924 925 virtualisation.useDefaultFilesystems = 926 mkOption { 927 type = types.bool; 928 default = true; 929 description = 930 lib.mdDoc '' 931 If enabled, the boot disk of the virtual machine will be 932 formatted and mounted with the default filesystems for 933 testing. Swap devices and LUKS will be disabled. 934 935 If disabled, a root filesystem has to be specified and 936 formatted (for example in the initial ramdisk). 937 ''; 938 }; 939 940 virtualisation.useSecureBoot = 941 mkOption { 942 type = types.bool; 943 default = false; 944 description = 945 lib.mdDoc '' 946 Enable Secure Boot support in the EFI firmware. 947 ''; 948 }; 949 950 virtualisation.bios = 951 mkOption { 952 type = types.nullOr types.package; 953 default = null; 954 description = 955 lib.mdDoc '' 956 An alternate BIOS (such as `qboot`) with which to start the VM. 957 Should contain a file named `bios.bin`. 958 If `null`, QEMU's builtin SeaBIOS will be used. 959 ''; 960 }; 961 962 virtualisation.useHostCerts = 963 mkOption { 964 type = types.bool; 965 default = false; 966 description = 967 lib.mdDoc '' 968 If enabled, when `NIX_SSL_CERT_FILE` is set on the host, 969 pass the CA certificates from the host to the VM. 970 ''; 971 }; 972 973 }; 974 975 config = { 976 977 assertions = 978 lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: 979 [ 980 { assertion = rule.from == "guest" -> rule.proto == "tcp"; 981 message = 982 '' 983 Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto: 984 Guest forwarding supports only TCP connections. 985 ''; 986 } 987 { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; 988 message = 989 '' 990 Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address: 991 The address must be in the default VLAN (10.0.2.0/24). 992 ''; 993 } 994 ])) ++ [ 995 { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; 996 message = '' 997 virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. 998 ''; 999 } 1000 { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; 1001 message = 1002 '' 1003 You changed the default of `virtualisation.directBoot.initrd` but you are not 1004 using QEMU direct boot. This initrd will not be used in your current 1005 boot configuration. 1006 1007 Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. 1008 1009 If you have a more advanced usecase, please open an issue or a pull request. 1010 ''; 1011 } 1012 ]; 1013 1014 warnings = 1015 optional ( 1016 cfg.writableStore && 1017 cfg.useNixStoreImage && 1018 opt.writableStore.highestPrio > lib.modules.defaultOverridePriority) 1019 '' 1020 You have enabled ${opt.useNixStoreImage} = true, 1021 without setting ${opt.writableStore} = false. 1022 1023 This causes a store image to be written to the store, which is 1024 costly, especially for the binary cache, and because of the need 1025 for more frequent garbage collection. 1026 1027 If you really need this combination, you can set ${opt.writableStore} 1028 explicitly to true, incur the cost and make this warning go away. 1029 Otherwise, we recommend 1030 1031 ${opt.writableStore} = false; 1032 '' 1033 ++ optional (cfg.directBoot.enable && cfg.useBootLoader) 1034 '' 1035 You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering 1036 `useBootLoader` useless. You might want to disable one of those options. 1037 ''; 1038 1039 # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install 1040 # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs. 1041 # Otherwise, we set the proper bootloader device for this. 1042 # FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint? 1043 boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice); 1044 boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}"; 1045 1046 boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ]; 1047 1048 boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false); 1049 1050 boot.initrd.postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable) 1051 '' 1052 # Mark this as a NixOS machine. 1053 mkdir -p $targetRoot/etc 1054 echo -n > $targetRoot/etc/NIXOS 1055 1056 # Fix the permissions on /tmp. 1057 chmod 1777 $targetRoot/tmp 1058 1059 mkdir -p $targetRoot/boot 1060 1061 ${optionalString cfg.writableStore '' 1062 echo "mounting overlay filesystem on /nix/store..." 1063 mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store 1064 mount -t overlay overlay $targetRoot/nix/store \ 1065 -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail 1066 ''} 1067 ''; 1068 1069 systemd.tmpfiles.rules = lib.mkIf config.boot.initrd.systemd.enable [ 1070 "f /etc/NIXOS 0644 root root -" 1071 "d /boot 0644 root root -" 1072 ]; 1073 1074 # After booting, register the closure of the paths in 1075 # `virtualisation.additionalPaths' in the Nix database in the VM. This 1076 # allows Nix operations to work in the VM. The path to the 1077 # registration file is passed through the kernel command line to 1078 # allow `system.build.toplevel' to be included. (If we had a direct 1079 # reference to ${regInfo} here, then we would get a cyclic 1080 # dependency.) 1081 boot.postBootCommands = lib.mkIf config.nix.enable 1082 '' 1083 if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then 1084 ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} 1085 fi 1086 ''; 1087 1088 boot.initrd.availableKernelModules = 1089 optional cfg.writableStore "overlay" 1090 ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" 1091 ++ optional (cfg.tpm.enable) "tpm_tis"; 1092 1093 virtualisation.additionalPaths = [ config.system.build.toplevel ]; 1094 1095 virtualisation.sharedDirectories = { 1096 nix-store = mkIf cfg.mountHostNixStore { 1097 source = builtins.storeDir; 1098 target = "/nix/store"; 1099 }; 1100 xchg = { 1101 source = ''"$TMPDIR"/xchg''; 1102 target = "/tmp/xchg"; 1103 }; 1104 shared = { 1105 source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; 1106 target = "/tmp/shared"; 1107 }; 1108 certs = mkIf cfg.useHostCerts { 1109 source = ''"$TMPDIR"/certs''; 1110 target = "/etc/ssl/certs"; 1111 }; 1112 }; 1113 1114 security.pki.installCACerts = mkIf cfg.useHostCerts false; 1115 1116 virtualisation.qemu.networkingOptions = 1117 let 1118 forwardingOptions = flip concatMapStrings cfg.forwardPorts 1119 ({ proto, from, host, guest }: 1120 if from == "host" 1121 then "hostfwd=${proto}:${host.address}:${toString host.port}-" + 1122 "${guest.address}:${toString guest.port}," 1123 else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + 1124 "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," 1125 ); 1126 restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,"; 1127 in 1128 [ 1129 "-net nic,netdev=user.0,model=virtio" 1130 "-netdev user,id=user.0,${forwardingOptions}${restrictNetworkOption}\"$QEMU_NET_OPTS\"" 1131 ]; 1132 1133 virtualisation.qemu.options = mkMerge [ 1134 (mkIf cfg.qemu.virtioKeyboard [ 1135 "-device virtio-keyboard" 1136 ]) 1137 (mkIf pkgs.stdenv.hostPlatform.isx86 [ 1138 "-usb" "-device usb-tablet,bus=usb-bus.0" 1139 ]) 1140 (mkIf pkgs.stdenv.hostPlatform.isAarch [ 1141 "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" 1142 ]) 1143 (let 1144 alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); 1145 # Replace all non-alphanumeric characters with underscores 1146 sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); 1147 in mkIf cfg.directBoot.enable [ 1148 "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" 1149 "-initrd ${cfg.directBoot.initrd}" 1150 ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' 1151 ]) 1152 (mkIf cfg.useEFIBoot [ 1153 "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}" 1154 "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS" 1155 ]) 1156 (mkIf (cfg.bios != null) [ 1157 "-bios ${cfg.bios}/bios.bin" 1158 ]) 1159 (mkIf (!cfg.graphics) [ 1160 "-nographic" 1161 ]) 1162 (mkIf (cfg.tpm.enable) [ 1163 "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket" 1164 "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" 1165 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" 1166 ]) 1167 ]; 1168 1169 virtualisation.qemu.drives = mkMerge [ 1170 (mkIf (cfg.diskImage != null) [{ 1171 name = "root"; 1172 file = ''"$NIX_DISK_IMAGE"''; 1173 driveExtraOpts.cache = "writeback"; 1174 driveExtraOpts.werror = "report"; 1175 deviceExtraOpts.bootindex = "1"; 1176 deviceExtraOpts.serial = rootDriveSerialAttr; 1177 }]) 1178 (mkIf cfg.useNixStoreImage [{ 1179 name = "nix-store"; 1180 file = ''"$TMPDIR"/store.img''; 1181 deviceExtraOpts.bootindex = "2"; 1182 driveExtraOpts.format = if cfg.writableStore then "qcow2" else "raw"; 1183 }]) 1184 (imap0 (idx: _: { 1185 file = "$(pwd)/empty${toString idx}.qcow2"; 1186 driveExtraOpts.werror = "report"; 1187 }) cfg.emptyDiskImages) 1188 ]; 1189 1190 # By default, use mkVMOverride to enable building test VMs (e.g. via 1191 # `nixos-rebuild build-vm`) of a system configuration, where the regular 1192 # value for the `fileSystems' attribute should be disregarded (since those 1193 # filesystems don't necessarily exist in the VM). You can disable this 1194 # override by setting `virtualisation.fileSystems = lib.mkForce { };`. 1195 fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems); 1196 1197 virtualisation.fileSystems = let 1198 mkSharedDir = tag: share: 1199 { 1200 name = 1201 if tag == "nix-store" && cfg.writableStore 1202 then "/nix/.ro-store" 1203 else share.target; 1204 value.device = tag; 1205 value.fsType = "9p"; 1206 value.neededForBoot = true; 1207 value.options = 1208 [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] 1209 ++ lib.optional (tag == "nix-store") "cache=loose"; 1210 }; 1211 in lib.mkMerge [ 1212 (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) 1213 { 1214 "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then { 1215 device = "tmpfs"; 1216 fsType = "tmpfs"; 1217 } else { 1218 device = cfg.rootDevice; 1219 fsType = "ext4"; 1220 }); 1221 "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { 1222 device = "tmpfs"; 1223 fsType = "tmpfs"; 1224 neededForBoot = true; 1225 # Sync with systemd's tmp.mount; 1226 options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; 1227 }; 1228 "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage { 1229 device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; 1230 neededForBoot = true; 1231 options = [ "ro" ]; 1232 }; 1233 "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { 1234 fsType = "tmpfs"; 1235 options = [ "mode=0755" ]; 1236 neededForBoot = true; 1237 }; 1238 "/boot" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) { 1239 device = cfg.bootPartition; 1240 fsType = "vfat"; 1241 noCheck = true; # fsck fails on a r/o filesystem 1242 }; 1243 } 1244 ]; 1245 1246 boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) { 1247 mounts = [{ 1248 where = "/sysroot/nix/store"; 1249 what = "overlay"; 1250 type = "overlay"; 1251 options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work"; 1252 wantedBy = ["initrd-fs.target"]; 1253 before = ["initrd-fs.target"]; 1254 requires = ["rw-store.service"]; 1255 after = ["rw-store.service"]; 1256 unitConfig.RequiresMountsFor = "/sysroot/nix/.ro-store"; 1257 }]; 1258 services.rw-store = { 1259 unitConfig = { 1260 DefaultDependencies = false; 1261 RequiresMountsFor = "/sysroot/nix/.rw-store"; 1262 }; 1263 serviceConfig = { 1264 Type = "oneshot"; 1265 ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store"; 1266 }; 1267 }; 1268 }; 1269 1270 swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; 1271 boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; 1272 1273 # Don't run ntpd in the guest. It should get the correct time from KVM. 1274 services.timesyncd.enable = false; 1275 1276 services.qemuGuest.enable = cfg.qemu.guestAgent.enable; 1277 1278 system.build.vm = hostPkgs.runCommand "nixos-vm" { 1279 preferLocalBuild = true; 1280 meta.mainProgram = "run-${config.system.name}-vm"; 1281 } 1282 '' 1283 mkdir -p $out/bin 1284 ln -s ${config.system.build.toplevel} $out/system 1285 ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm 1286 ''; 1287 1288 # When building a regular system configuration, override whatever 1289 # video driver the host uses. 1290 services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; 1291 services.xserver.defaultDepth = mkVMOverride 0; 1292 services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; 1293 services.xserver.monitorSection = 1294 '' 1295 # Set a higher refresh rate so that resolutions > 800x600 work. 1296 HorizSync 30-140 1297 VertRefresh 50-160 1298 ''; 1299 1300 # Wireless won't work in the VM. 1301 networking.wireless.enable = mkVMOverride false; 1302 services.connman.enable = mkVMOverride false; 1303 1304 # Speed up booting by not waiting for ARP. 1305 networking.dhcpcd.extraConfig = "noarp"; 1306 1307 networking.usePredictableInterfaceNames = false; 1308 1309 system.requiredKernelConfig = with config.lib.kernelConfig; 1310 [ (isEnabled "VIRTIO_BLK") 1311 (isEnabled "VIRTIO_PCI") 1312 (isEnabled "VIRTIO_NET") 1313 (isEnabled "EXT4_FS") 1314 (isEnabled "NET_9P_VIRTIO") 1315 (isEnabled "9P_FS") 1316 (isYes "BLK_DEV") 1317 (isYes "PCI") 1318 (isYes "NETDEVICES") 1319 (isYes "NET_CORE") 1320 (isYes "INET") 1321 (isYes "NETWORK_FILESYSTEMS") 1322 ] ++ optionals (!cfg.graphics) [ 1323 (isYes "SERIAL_8250_CONSOLE") 1324 (isYes "SERIAL_8250") 1325 ] ++ optionals (cfg.writableStore) [ 1326 (isEnabled "OVERLAY_FS") 1327 ]; 1328 1329 }; 1330 1331 # uses types of services/x11/xserver.nix 1332 meta.buildDocsInSandbox = false; 1333}