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