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}