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