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