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