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, ... }:
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 = { ... }: {
31 description = "The file image used for this drive.";
34 driveExtraOpts = mkOption {
35 type = types.attrsOf types.str;
37 description = "Extra options passed to drive flag.";
40 deviceExtraOpts = mkOption {
41 type = types.attrsOf types.str;
43 description = "Extra options passed to device flag.";
47 type = types.nullOr types.str;
49 description = "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
56 selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }:
57 if useDefaultFilesystems then
58 if useEFIBoot then "efi" else "legacy"
61 driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
63 drvId = "drive${toString idx}";
64 mkKeyValue = generators.mkKeyValueDefault {} "=";
65 mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts);
66 driveOpts = mkOpts (driveExtraOpts // {
72 deviceOpts = mkOpts (deviceExtraOpts // {
76 if cfg.qemu.diskInterface == "scsi" then
77 "-device lsi53c895a -device scsi-hd,${deviceOpts}"
79 "-device virtio-blk-pci,${deviceOpts}";
81 "-drive ${driveOpts} ${device}";
83 drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives);
85 # Shell script to start the VM.
88 #! ${hostPkgs.runtimeShell}
90 export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH
94 # Create an empty ext4 filesystem image. A filesystem image does not
95 # contain a partition table but just a filesystem.
96 createEmptyFilesystemImage() {
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"
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
115 # CoW prevent size to be attributed to an image.
116 # FIXME: raise this issue to upstream.
117 ${qemu}/bin/qemu-img create \
119 -b ${systemImage}/nixos.qcow2 \
122 '' else if cfg.useDefaultFilesystems then ''
123 createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
125 # Create an empty disk image without a filesystem.
126 ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
129 echo "Virtualisation disk image created."
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)
137 ${lib.optionalString (cfg.useNixStoreImage) ''
138 echo "Creating Nix store image..."
140 ${hostPkgs.gnutar}/bin/tar --create \
142 --verbatim-files-from \
143 --transform 'flags=rSh;s|/nix/store/||' \
144 --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \
145 | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \
149 -L ${nixStoreFilesystemLabel} \
150 -U eb176051-bd15-49b7-9e6b-462e0b467019 \
155 echo "Created Nix store image."
159 # Create a directory for exchanging data with the VM.
160 mkdir -p "$TMPDIR/xchg"
162 ${lib.optionalString cfg.useHostCerts
164 mkdir -p "$TMPDIR/certs"
165 if [ -e "$NIX_SSL_CERT_FILE" ]; then
166 cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt
168 echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled.
172 ${lib.optionalString cfg.useEFIBoot
174 # Expose EFI variables, it's useful even when we are not using a bootloader (!).
175 # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence
176 # no guard against `useBootLoader`. Examples:
177 # - testing PXE boot or other EFI applications
178 # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows
179 NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
180 # VM needs writable EFI vars
181 if ! test -e "$NIX_EFI_VARS"; then
182 ${if cfg.efi.keepVariables then
183 # We still need the EFI var from the make-disk-image derivation
184 # because our "switch-to-configuration" process might
185 # write into it and we want to keep this data.
186 ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"''
188 ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"''
190 chmod 0644 "$NIX_EFI_VARS"
194 ${lib.optionalString cfg.tpm.enable ''
195 NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}")
196 mkdir -p "$NIX_SWTPM_DIR"
197 ${lib.getExe cfg.tpm.package} \
199 --tpmstate dir="$NIX_SWTPM_DIR" \
200 --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \
201 --pid file="$NIX_SWTPM_DIR"/pid --daemon \
203 --log file="$NIX_SWTPM_DIR"/stdout,level=6
205 # Enable `fdflags` builtin in Bash
206 # We will need it to perform surgical modification of the file descriptor
207 # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor
209 # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec`
210 # at the end of this script. To work around that, we will just clear
211 # the `FD_CLOEXEC` bits as a first step.
212 enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags
213 # leave a dangling subprocess because the swtpm ctrl socket has
214 # "terminate" when the last connection disconnects, it stops swtpm.
215 # When qemu stops, or if the main shell process ends, the coproc will
216 # get signaled by virtue of the pipe between main and coproc ending.
217 # Which in turns triggers a socat connect-disconnect to swtpm which
219 coproc waitingswtpm {
221 echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket
223 # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin.
224 fdflags -s-cloexec ''${waitingswtpm[1]}
229 ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
230 ${flip concatMapStrings cfg.emptyDiskImages (size: ''
231 if ! test -e "empty$idx.qcow2"; then
232 ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
238 exec ${qemu-common.qemuBinary qemu} \
239 -name ${config.system.name} \
240 -m ${toString config.virtualisation.memorySize} \
241 -smp ${toString config.virtualisation.cores} \
242 -device virtio-rng-pci \
243 ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
244 ${concatStringsSep " \\\n "
246 (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}")
247 config.virtualisation.sharedDirectories)} \
248 ${drivesCmdLine config.virtualisation.qemu.drives} \
249 ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \
255 regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
257 # Use well-defined and persistent filesystem labels to identify block devices.
258 rootFilesystemLabel = "nixos";
259 espFilesystemLabel = "ESP"; # Hard-coded by make-disk-image.nix
260 nixStoreFilesystemLabel = "nix-store";
262 # The root drive is a raw disk which does not necessarily contain a
263 # filesystem or partition table. It thus cannot be identified via the typical
264 # persistent naming schemes (e.g. /dev/disk/by-{label, uuid, partlabel,
265 # partuuid}. Instead, supply a well-defined and persistent serial attribute
266 # via QEMU. Inside the running system, the disk can then be identified via
267 # the /dev/disk/by-id scheme.
268 rootDriveSerialAttr = "root";
270 # System image is akin to a complete NixOS install with
271 # a boot partition and root partition.
272 systemImage = import ../../lib/make-disk-image.nix {
273 inherit pkgs config lib;
274 additionalPaths = [ regInfo ];
276 onlyNixStore = false;
277 label = rootFilesystemLabel;
278 partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; };
279 installBootLoader = cfg.installBootLoader;
280 touchEFIVars = cfg.useEFIBoot;
282 additionalSpace = "0M";
291 ../profiles/qemu-guest.nix
292 (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ])
293 (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.")
294 (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")
295 (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.`")
300 virtualisation.fileSystems = options.fileSystems;
302 virtualisation.memorySize =
304 type = types.ints.positive;
307 The memory size in megabytes of the virtual machine.
311 virtualisation.msize =
313 type = types.ints.positive;
316 The msize (maximum packet size) option passed to 9p file systems, in
317 bytes. Increasing this should increase performance significantly,
318 at the cost of higher RAM usage.
322 virtualisation.diskSize =
324 type = types.ints.positive;
327 The disk size in megabytes of the virtual machine.
331 virtualisation.diskImage =
333 type = types.nullOr types.str;
334 default = "./${config.system.name}.qcow2";
335 defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
337 Path to the disk image containing the root filesystem.
338 The image will be created on startup if it does not
341 If null, a tmpfs will be used as the root filesystem and
342 the VM's state will not be persistent.
346 virtualisation.bootLoaderDevice =
349 default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}";
350 defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}'';
351 example = "/dev/disk/by-id/virtio-boot-loader-device";
353 The path (inside th VM) to the device to boot from when legacy booting.
357 virtualisation.bootPartition =
359 type = types.nullOr types.path;
360 default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null;
361 defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null'';
362 example = "/dev/disk/by-label/esp";
364 The path (inside the VM) to the device containing the EFI System Partition (ESP).
366 If you are *not* booting from a UEFI firmware, this value is, by
367 default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`.
371 virtualisation.rootDevice =
373 type = types.nullOr types.path;
374 default = "/dev/disk/by-label/${rootFilesystemLabel}";
375 defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}'';
376 example = "/dev/disk/by-label/nixos";
378 The path (inside the VM) to the device containing the root filesystem.
382 virtualisation.emptyDiskImages =
384 type = types.listOf types.ints.positive;
387 Additional disk images to provide to the VM. The value is
388 a list of size in megabytes of each disk. These disks are
393 virtualisation.graphics =
398 Whether to run QEMU with a graphics window, or in nographic mode.
399 Serial console will be enabled on both settings, but this will
400 change the preferred console.
404 virtualisation.resolution =
406 type = options.services.xserver.resolutions.type.nestedTypes.elemType;
407 default = { x = 1024; y = 768; };
409 The resolution of the virtual machine display.
413 virtualisation.cores =
415 type = types.ints.positive;
418 Specify the number of cores the guest is permitted to use.
419 The number can be higher than the available cores on the
424 virtualisation.sharedDirectories =
428 options.source = mkOption {
430 description = "The path of the directory to share, can be a shell variable";
432 options.target = mkOption {
434 description = "The mount point of the directory inside the virtual machine";
436 options.securityModel = mkOption {
437 type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ];
438 default = "mapped-xattr";
440 The security model to use for this share:
442 - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root)
443 - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes
444 - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools
445 - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership
451 my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
454 An attributes set of directories that will be shared with the
455 virtual machine using VirtFS (9P filesystem over VirtIO).
456 The attribute name will be used as the 9P mount tag.
460 virtualisation.additionalPaths =
462 type = types.listOf types.path;
465 A list of paths whose closure should be made available to
468 When 9p is used, the closure is registered in the Nix
469 database in the VM. All other paths in the host Nix store
470 appear in the guest Nix store as well, but are considered
471 garbage (because they are not registered in the Nix
472 database of the guest).
474 When {option}`virtualisation.useNixStoreImage` is
475 set, the closure is copied to the Nix store image.
479 virtualisation.forwardPorts = mkOption {
482 options.from = mkOption {
483 type = types.enum [ "host" "guest" ];
486 Controls the direction in which the ports are mapped:
488 - `"host"` means traffic from the host ports
489 is forwarded to the given guest port.
490 - `"guest"` means traffic from the guest ports
491 is forwarded to the given host port.
494 options.proto = mkOption {
495 type = types.enum [ "tcp" "udp" ];
497 description = "The protocol to forward.";
499 options.host.address = mkOption {
502 description = "The IPv4 address of the host.";
504 options.host.port = mkOption {
506 description = "The host port to be mapped.";
508 options.guest.address = mkOption {
511 description = "The IPv4 address on the guest VLAN.";
513 options.guest.port = mkOption {
515 description = "The guest port to be mapped.";
519 example = lib.literalExpression
521 [ # forward local port 2222 -> 22, to ssh into the VM
522 { from = "host"; host.port = 2222; guest.port = 22; }
524 # forward local port 80 -> 10.0.2.10:80 in the VLAN
526 guest.address = "10.0.2.10"; guest.port = 80;
527 host.address = "127.0.0.1"; host.port = 80;
532 When using the SLiRP user networking (default), this option allows to
533 forward ports to/from the host/guest.
536 If the NixOS firewall on the virtual machine is enabled, you also
537 have to open the guest ports to enable the traffic between host and
542 Currently QEMU supports only IPv4 forwarding.
547 virtualisation.restrictNetwork =
553 If this option is enabled, the guest will be isolated, i.e. it will
554 not be able to contact the host and no guest IP packets will be
555 routed over the host to the outside. This option does not affect
556 any explicitly set forwarding rules.
560 virtualisation.vlans =
562 type = types.listOf types.ints.unsigned;
563 default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ];
564 defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]'';
567 Virtual networks to which the VM is connected. Each
568 number «N» in this list causes
569 the VM to have a virtual Ethernet interface attached to a
570 separate virtual network on which it will be assigned IP
572 `192.168.«N».«M»`,
573 where «M» is the index of this VM
578 virtualisation.interfaces = mkOption {
584 Network interfaces to add to the VM.
586 type = with types; attrsOf (submodule {
589 type = types.ints.unsigned;
591 VLAN to which the network interface is connected.
595 assignIP = mkOption {
599 Automatically assign an IP address to the network interface using the same scheme as
600 virtualisation.vlans.
607 virtualisation.writableStore =
610 default = cfg.mountHostNixStore;
611 defaultText = literalExpression "cfg.mountHostNixStore";
613 If enabled, the Nix store in the VM is made writable by
614 layering an overlay filesystem on top of the host's Nix
617 By default, this is enabled if you mount a host Nix store.
621 virtualisation.writableStoreUseTmpfs =
626 Use a tmpfs for the writable store instead of writing to the VM's
631 networking.primaryIPAddress =
636 description = "Primary IP address used in /etc/hosts.";
639 networking.primaryIPv6Address =
644 description = "Primary IPv6 address used in /etc/hosts.";
647 virtualisation.host.pkgs = mkOption {
648 type = options.nixpkgs.pkgs.type;
650 defaultText = literalExpression "pkgs";
651 example = literalExpression ''
652 import pkgs.path { system = "x86_64-darwin"; }
655 Package set to use for the host-specific packages of the VM runner.
656 Changing this to e.g. a Darwin package set allows running NixOS VMs on Darwin.
660 virtualisation.qemu = {
663 type = types.package;
664 default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu;
665 defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu";
666 example = literalExpression "pkgs.qemu_test";
667 description = "QEMU package to use.";
672 type = types.listOf types.str;
674 example = [ "-vga std" ];
676 Options passed to QEMU.
677 See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list.
681 consoles = mkOption {
682 type = types.listOf types.str;
684 consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
685 in if cfg.graphics then consoles else reverseList consoles;
686 example = [ "console=tty1" ];
688 The output console devices to pass to the kernel command line via the
689 `console` parameter, the primary console is the last
692 By default it enables both serial console and
693 `tty0`. The preferred console (last one) is based on
694 the value of {option}`virtualisation.graphics`.
700 type = types.listOf types.str;
703 "-net nic,netdev=user.0,model=virtio"
704 "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
707 Networking-related command-line options that should be passed to qemu.
708 The default is to use userspace networking (SLiRP).
709 See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details.
711 If you override this option, be advised to keep
712 `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example)
713 to keep the default runtime behaviour.
719 type = types.listOf (types.submodule driveOpts);
720 description = "Drives passed to qemu.";
725 type = types.enum [ "virtio" "scsi" "ide" ];
728 description = "The interface used for the virtual hard disks.";
736 Enable the Qemu guest agent.
745 Enable the virtio-keyboard device.
750 virtualisation.useNixStoreImage =
755 Build and use a disk image for the Nix store, instead of
756 accessing the host's one through 9p.
758 For applications which do a lot of reads from the store,
759 this can drastically improve performance, but at the cost of
760 disk space and image build time.
762 The Nix store image is built just-in-time right before the VM is
763 started. Because it does not produce another derivation, the image is
764 not cached between invocations and never lands in the store or binary
767 If you want a full disk image with a partition table and a root
768 filesystem instead of only a store image, enable
769 {option}`virtualisation.useBootLoader` instead.
773 virtualisation.mountHostNixStore =
776 default = !cfg.useNixStoreImage && !cfg.useBootLoader;
777 defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
779 Mount the host Nix store as a 9p mount.
783 virtualisation.directBoot = {
787 default = !cfg.useBootLoader;
788 defaultText = "!cfg.useBootLoader";
790 If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader.
791 Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html)
793 This is enabled by default.
794 If you want to test netboot, consider disabling this option.
795 Enable a bootloader with {option}`virtualisation.useBootLoader` if you need.
797 Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU.
798 Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`.
799 They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details
800 For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`.
802 This will not (re-)boot correctly into a system that has switched to a different configuration on disk.
808 default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
809 defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}";
811 In direct boot situations, you may want to influence the initrd to load
812 to use your own customized payload.
814 This is useful if you want to test the netboot image without
815 testing the firmware or the loading part.
820 virtualisation.useBootLoader =
825 Use a boot loader to boot the system.
826 This allows, among other things, testing the boot loader.
828 If disabled, the kernel and initrd are directly booted,
829 forgoing any bootloader.
831 Check the documentation on {option}`virtualisation.directBoot.enable` for details.
835 virtualisation.installBootLoader =
838 default = cfg.useBootLoader && cfg.useDefaultFilesystems;
839 defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems";
841 Install boot loader to target image.
843 This is best-effort and may break with unconventional partition setups.
844 Use `virtualisation.useDefaultFilesystems` for a known-working configuration.
848 virtualisation.useEFIBoot =
853 If enabled, the virtual machine will provide a EFI boot
855 useEFIBoot is ignored if useBootLoader == false.
859 virtualisation.efi = {
861 type = types.package;
862 default = (pkgs.OVMF.override {
863 secureBoot = cfg.useSecureBoot;
865 defaultText = ''(pkgs.OVMF.override {
866 secureBoot = cfg.useSecureBoot;
868 description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed.";
871 firmware = mkOption {
873 default = cfg.efi.OVMF.firmware;
874 defaultText = literalExpression "cfg.efi.OVMF.firmware";
876 Firmware binary for EFI implementation, defaults to OVMF.
880 variables = mkOption {
882 default = cfg.efi.OVMF.variables;
883 defaultText = literalExpression "cfg.efi.OVMF.variables";
885 Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware.
890 keepVariables = mkOption {
892 default = cfg.useBootLoader;
893 defaultText = literalExpression "cfg.useBootLoader";
894 description = "Whether to keep EFI variable values from the generated system image";
898 virtualisation.tpm = {
899 enable = mkEnableOption "a TPM device in the virtual machine with a driver, using swtpm";
901 package = mkPackageOption cfg.host.pkgs "swtpm" { };
903 deviceModel = mkOption {
906 "i686-linux" = "tpm-tis";
907 "x86_64-linux" = "tpm-tis";
908 "ppc64-linux" = "tpm-spapr";
909 "armv7-linux" = "tpm-tis-device";
910 "aarch64-linux" = "tpm-tis-device";
911 }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
913 Based on the guest platform Linux system:
915 - `tpm-tis` for (i686, x86_64)
916 - `tpm-spapr` for ppc64
917 - `tpm-tis-device` for (armv7, aarch64)
919 example = "tpm-tis-device";
920 description = "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed.";
924 virtualisation.useDefaultFilesystems =
929 If enabled, the boot disk of the virtual machine will be
930 formatted and mounted with the default filesystems for
931 testing. Swap devices and LUKS will be disabled.
933 If disabled, a root filesystem has to be specified and
934 formatted (for example in the initial ramdisk).
938 virtualisation.useSecureBoot =
943 Enable Secure Boot support in the EFI firmware.
947 virtualisation.bios =
949 type = types.nullOr types.package;
952 An alternate BIOS (such as `qboot`) with which to start the VM.
953 Should contain a file named `bios.bin`.
954 If `null`, QEMU's builtin SeaBIOS will be used.
958 virtualisation.useHostCerts =
963 If enabled, when `NIX_SSL_CERT_FILE` is set on the host,
964 pass the CA certificates from the host to the VM.
973 lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule:
975 { assertion = rule.from == "guest" -> rule.proto == "tcp";
978 Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto:
979 Guest forwarding supports only TCP connections.
982 { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
985 Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address:
986 The address must be in the default VLAN (10.0.2.0/24).
990 { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047;
992 virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
995 { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default;
998 You changed the default of `virtualisation.directBoot.initrd` but you are not
999 using QEMU direct boot. This initrd will not be used in your current
1002 Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot.
1004 If you have a more advanced usecase, please open an issue or a pull request.
1008 assertion = cfg.installBootLoader -> config.system.switch.enable;
1010 `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work.
1011 Please enable it in your configuration.
1017 optional (cfg.directBoot.enable && cfg.useBootLoader)
1019 You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering
1020 `useBootLoader` useless. You might want to disable one of those options.
1023 # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install
1024 # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs.
1025 # Otherwise, we set the proper bootloader device for this.
1026 # FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint?
1027 boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice);
1028 boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
1030 boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false);
1032 # After booting, register the closure of the paths in
1033 # `virtualisation.additionalPaths' in the Nix database in the VM. This
1034 # allows Nix operations to work in the VM. The path to the
1035 # registration file is passed through the kernel command line to
1036 # allow `system.build.toplevel' to be included. (If we had a direct
1037 # reference to ${regInfo} here, then we would get a cyclic
1039 boot.postBootCommands = lib.mkIf config.nix.enable
1041 if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then
1042 ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]}
1046 boot.initrd.availableKernelModules =
1047 optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
1048 ++ optional (cfg.tpm.enable) "tpm_tis";
1050 virtualisation.additionalPaths = [ config.system.build.toplevel ];
1052 virtualisation.sharedDirectories = {
1053 nix-store = mkIf cfg.mountHostNixStore {
1054 source = builtins.storeDir;
1055 # Always mount this to /nix/.ro-store because we never want to actually
1056 # write to the host Nix Store.
1057 target = "/nix/.ro-store";
1058 securityModel = "none";
1061 source = ''"$TMPDIR"/xchg'';
1062 securityModel = "none";
1063 target = "/tmp/xchg";
1066 source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"'';
1067 target = "/tmp/shared";
1068 securityModel = "none";
1070 certs = mkIf cfg.useHostCerts {
1071 source = ''"$TMPDIR"/certs'';
1072 target = "/etc/ssl/certs";
1073 securityModel = "none";
1077 security.pki.installCACerts = mkIf cfg.useHostCerts false;
1079 virtualisation.qemu.networkingOptions =
1081 forwardingOptions = flip concatMapStrings cfg.forwardPorts
1082 ({ proto, from, host, guest }:
1084 then "hostfwd=${proto}:${host.address}:${toString host.port}-" +
1085 "${guest.address}:${toString guest.port},"
1086 else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
1087 "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
1089 restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,";
1092 "-net nic,netdev=user.0,model=virtio"
1093 "-netdev user,id=user.0,${forwardingOptions}${restrictNetworkOption}\"$QEMU_NET_OPTS\""
1096 virtualisation.qemu.options = mkMerge [
1097 (mkIf cfg.qemu.virtioKeyboard [
1098 "-device virtio-keyboard"
1100 (mkIf pkgs.stdenv.hostPlatform.isx86 [
1101 "-usb" "-device usb-tablet,bus=usb-bus.0"
1103 (mkIf pkgs.stdenv.hostPlatform.isAarch [
1104 "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
1107 alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
1108 # Replace all non-alphanumeric characters with underscores
1109 sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s);
1110 in mkIf cfg.directBoot.enable [
1111 "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}"
1112 "-initrd ${cfg.directBoot.initrd}"
1113 ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
1115 (mkIf cfg.useEFIBoot [
1116 "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
1117 "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS"
1119 (mkIf (cfg.bios != null) [
1120 "-bios ${cfg.bios}/bios.bin"
1122 (mkIf (!cfg.graphics) [
1125 (mkIf (cfg.tpm.enable) [
1126 "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket"
1127 "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
1128 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0"
1130 (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [
1131 "-machine" "q35,smm=on"
1132 "-global" "driver=cfi.pflash01,property=secure,value=on"
1136 virtualisation.qemu.drives = mkMerge [
1137 (mkIf (cfg.diskImage != null) [{
1139 file = ''"$NIX_DISK_IMAGE"'';
1140 driveExtraOpts.cache = "writeback";
1141 driveExtraOpts.werror = "report";
1142 deviceExtraOpts.bootindex = "1";
1143 deviceExtraOpts.serial = rootDriveSerialAttr;
1145 (mkIf cfg.useNixStoreImage [{
1147 file = ''"$TMPDIR"/store.img'';
1148 deviceExtraOpts.bootindex = "2";
1149 driveExtraOpts.format = "raw";
1152 file = "$(pwd)/empty${toString idx}.qcow2";
1153 driveExtraOpts.werror = "report";
1154 }) cfg.emptyDiskImages)
1157 # By default, use mkVMOverride to enable building test VMs (e.g. via
1158 # `nixos-rebuild build-vm`) of a system configuration, where the regular
1159 # value for the `fileSystems' attribute should be disregarded (since those
1160 # filesystems don't necessarily exist in the VM). You can disable this
1161 # override by setting `virtualisation.fileSystems = lib.mkForce { };`.
1162 fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems);
1164 virtualisation.fileSystems = let
1165 mkSharedDir = tag: share:
1167 name = share.target;
1169 value.fsType = "9p";
1170 value.neededForBoot = true;
1172 [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" "x-systemd.requires=modprobe@9pnet_virtio.service" ]
1173 ++ lib.optional (tag == "nix-store") "cache=loose";
1176 (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
1178 "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then {
1182 device = cfg.rootDevice;
1185 "/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
1188 neededForBoot = true;
1189 # Sync with systemd's tmp.mount;
1190 options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ];
1192 "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then {
1194 lowerdir = [ "/nix/.ro-store" ];
1195 upperdir = "/nix/.rw-store/upper";
1196 workdir = "/nix/.rw-store/work";
1199 device = "/nix/.ro-store";
1200 options = [ "bind" ];
1202 "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
1203 device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
1205 neededForBoot = true;
1208 "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
1210 options = [ "mode=0755" ];
1211 neededForBoot = true;
1213 "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
1214 device = cfg.bootPartition;
1220 swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
1221 boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {};
1223 # Don't run ntpd in the guest. It should get the correct time from KVM.
1224 services.timesyncd.enable = false;
1226 services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
1228 system.build.vm = hostPkgs.runCommand "nixos-vm" {
1229 preferLocalBuild = true;
1230 meta.mainProgram = "run-${config.system.name}-vm";
1234 ln -s ${config.system.build.toplevel} $out/system
1235 ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
1238 # When building a regular system configuration, override whatever
1239 # video driver the host uses.
1240 services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
1241 services.xserver.defaultDepth = mkVMOverride 0;
1242 services.xserver.resolutions = mkVMOverride [ cfg.resolution ];
1243 services.xserver.monitorSection =
1245 # Set a higher refresh rate so that resolutions > 800x600 work.
1250 # Wireless won't work in the VM.
1251 networking.wireless.enable = mkVMOverride false;
1252 services.connman.enable = mkVMOverride false;
1254 # Speed up booting by not waiting for ARP.
1255 networking.dhcpcd.extraConfig = "noarp";
1257 networking.usePredictableInterfaceNames = false;
1259 system.requiredKernelConfig = with config.lib.kernelConfig;
1260 [ (isEnabled "VIRTIO_BLK")
1261 (isEnabled "VIRTIO_PCI")
1262 (isEnabled "VIRTIO_NET")
1263 (isEnabled "EXT4_FS")
1264 (isEnabled "NET_9P_VIRTIO")
1268 (isYes "NETDEVICES")
1271 (isYes "NETWORK_FILESYSTEMS")
1272 ] ++ optionals (!cfg.graphics) [
1273 (isYes "SERIAL_8250_CONSOLE")
1274 (isYes "SERIAL_8250")
1275 ] ++ optionals (cfg.writableStore) [
1276 (isEnabled "OVERLAY_FS")
1281 # uses types of services/x11/xserver.nix
1282 meta.buildDocsInSandbox = false;