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