summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
authorTimothy DeHerrera <tim.deh@pm.me>2021-09-28 21:35:23 -0600
committerGitHub <noreply@github.com>2021-09-28 21:35:23 -0600
commit895f3956d266e2e90b66d03ea97ebc082bffd12c (patch)
treeb25ecd3db061bada8aba317bc8ef7ac370da7587 /nixos/modules
parent8a587c79f6c970059b98a9e30e10bb33674b6340 (diff)
parentbd3cb03c6f7993050fe21b787e6f6a8595204be2 (diff)
downloadnixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.gz
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.bz2
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.lz
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.xz
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.zst
nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.zip
Merge pull request #127933 from rnhmjoj/qemu-restoration
Qemu restoration
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/misc/documentation.nix6
-rw-r--r--nixos/modules/testing/test-instrumentation.nix17
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix290
3 files changed, 241 insertions, 72 deletions
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 1d23b9b7244..ec6b2ad3b88 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -6,7 +6,11 @@ let
 
   cfg = config.documentation;
 
-  manualModules = baseModules ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
+  manualModules =
+    baseModules
+    # Modules for which to show options even when not imported
+    ++ [ ../virtualisation/qemu-vm.nix ]
+    ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules);
 
   /* For the purpose of generating docs, evaluate options with each derivation
     in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index be5fa88b8ad..a7011be7e04 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -4,7 +4,10 @@
 { options, config, lib, pkgs, ... }:
 
 with lib;
-with import ../../lib/qemu-flags.nix { inherit pkgs; };
+
+let
+  qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+in
 
 {
 
@@ -12,8 +15,8 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
     systemd.services.backdoor =
       { wantedBy = [ "multi-user.target" ];
-        requires = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
-        after = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ];
+        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
         script =
           ''
             export USER=root
@@ -30,7 +33,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
             cd /tmp
             exec < /dev/hvc0 > /dev/hvc0
-            while ! exec 2> /dev/${qemuSerialDevice}; do sleep 0.1; done
+            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
             echo "connecting to host..." >&2
             stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
             echo
@@ -42,7 +45,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     # Prevent agetty from being instantiated on the serial device, since it
     # interferes with the backdoor (writes to it will randomly fail
     # with EIO).  Likewise for hvc0.
-    systemd.services."serial-getty@${qemuSerialDevice}".enable = false;
+    systemd.services."serial-getty@${qemu-common.qemuSerialDevice}".enable = false;
     systemd.services."serial-getty@hvc0".enable = false;
 
     # Only set these settings when the options exist. Some tests (e.g. those
@@ -57,7 +60,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
         #       we avoid defining consoles if not possible.
         # TODO: refactor such that test-instrumentation can import qemu-vm
         #       or declare virtualisation.qemu.console option in a module that's always imported
-        consoles = [ qemuSerialDevice ];
+        consoles = [ qemu-common.qemuSerialDevice ];
         package  = lib.mkDefault pkgs.qemu_test;
       };
     };
@@ -88,7 +91,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     # Panic if an error occurs in stage 1 (rather than waiting for
     # user intervention).
     boot.kernelParams =
-      [ "console=${qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
+      [ "console=${qemu-common.qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
 
     # `xwininfo' is used by the test driver to query open windows.
     environment.systemPackages = [ pkgs.xorg.xwininfo ];
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index b51c29f83d6..f7b6b4eac39 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -10,10 +10,10 @@
 { config, lib, pkgs, options, ... }:
 
 with lib;
-with import ../../lib/qemu-flags.nix { inherit pkgs; };
 
 let
 
+  qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
 
   cfg = config.virtualisation;
 
@@ -75,7 +75,7 @@ let
     in
       "-drive ${driveOpts} ${device}";
 
-  drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
+  drivesCmdLine = drives: concatStringsSep "\\\n    " (imap1 driveCmdline drives);
 
 
   # Creates a device name from a 1-based a numerical index, e.g.
@@ -108,7 +108,7 @@ let
     ''
       #! ${pkgs.runtimeShell}
 
-      NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}})
+      NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}")
 
       if ! test -e "$NIX_DISK_IMAGE"; then
           ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
@@ -121,26 +121,29 @@ let
       fi
 
       # Create a directory for exchanging data with the VM.
-      mkdir -p $TMPDIR/xchg
+      mkdir -p "$TMPDIR/xchg"
 
-      ${if cfg.useBootLoader then ''
+      ${lib.optionalString cfg.useBootLoader
+      ''
         # Create a writable copy/snapshot of the boot disk.
         # A writable boot disk can be booted from automatically.
-        ${qemu}/bin/qemu-img create -f qcow2 -b ${bootDisk}/disk.img $TMPDIR/disk.img || exit 1
+        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img" || exit 1
 
-        NIX_EFI_VARS=$(readlink -f ''${NIX_EFI_VARS:-${cfg.efiVars}})
+        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
 
-        ${if cfg.useEFIBoot then ''
+        ${lib.optionalString cfg.useEFIBoot
+        ''
           # VM needs writable EFI vars
           if ! test -e "$NIX_EFI_VARS"; then
             cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS" || exit 1
             chmod 0644 "$NIX_EFI_VARS" || exit 1
           fi
-        '' else ""}
-      '' else ""}
+        ''}
+      ''}
 
-      cd $TMPDIR
-      idx=0
+      cd "$TMPDIR" || exit 1
+
+      ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
       ${flip concatMapStrings cfg.emptyDiskImages (size: ''
         if ! test -e "empty$idx.qcow2"; then
             ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
@@ -149,17 +152,18 @@ let
       '')}
 
       # Start QEMU.
-      exec ${qemuBinary qemu} \
+      exec ${qemu-common.qemuBinary qemu} \
           -name ${config.system.name} \
           -m ${toString config.virtualisation.memorySize} \
           -smp ${toString config.virtualisation.cores} \
           -device virtio-rng-pci \
           ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
-          -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
-          -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
-          -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
+          ${concatStringsSep " \\\n    "
+            (mapAttrsToList
+              (tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}")
+              config.virtualisation.sharedDirectories)} \
           ${drivesCmdLine config.virtualisation.qemu.drives} \
-          ${toString config.virtualisation.qemu.options} \
+          ${concatStringsSep " \\\n    " config.virtualisation.qemu.options} \
           $QEMU_OPTS \
           "$@"
     '';
@@ -270,20 +274,21 @@ in
 
     virtualisation.memorySize =
       mkOption {
+        type = types.ints.positive;
         default = 384;
         description =
           ''
-            Memory size (M) of virtual machine.
+            The memory size in megabytes of the virtual machine.
           '';
       };
 
     virtualisation.msize =
       mkOption {
-        default = null;
-        type = types.nullOr types.ints.unsigned;
+        type = types.ints.positive;
+        default = 16384;
         description =
           ''
-            msize (maximum packet size) option passed to 9p file systems, in
+            The msize (maximum packet size) option passed to 9p file systems, in
             bytes. Increasing this should increase performance significantly,
             at the cost of higher RAM usage.
           '';
@@ -291,15 +296,17 @@ in
 
     virtualisation.diskSize =
       mkOption {
+        type = types.nullOr types.ints.positive;
         default = 512;
         description =
           ''
-            Disk size (M) of virtual machine.
+            The disk size in megabytes of the virtual machine.
           '';
       };
 
     virtualisation.diskImage =
       mkOption {
+        type = types.str;
         default = "./${config.system.name}.qcow2";
         description =
           ''
@@ -311,7 +318,7 @@ in
 
     virtualisation.bootDevice =
       mkOption {
-        type = types.str;
+        type = types.path;
         example = "/dev/vda";
         description =
           ''
@@ -321,8 +328,8 @@ in
 
     virtualisation.emptyDiskImages =
       mkOption {
+        type = types.listOf types.ints.positive;
         default = [];
-        type = types.listOf types.int;
         description =
           ''
             Additional disk images to provide to the VM. The value is
@@ -333,6 +340,7 @@ in
 
     virtualisation.graphics =
       mkOption {
+        type = types.bool;
         default = true;
         description =
           ''
@@ -342,10 +350,20 @@ in
             '';
       };
 
+    virtualisation.resolution =
+      mkOption {
+        type = options.services.xserver.resolutions.type.nestedTypes.elemType;
+        default = { x = 1024; y = 768; };
+        description =
+          ''
+            The resolution of the virtual machine display.
+          '';
+      };
+
     virtualisation.cores =
       mkOption {
+        type = types.ints.positive;
         default = 1;
-        type = types.int;
         description =
           ''
             Specify the number of cores the guest is permitted to use.
@@ -354,8 +372,34 @@ in
           '';
       };
 
+    virtualisation.sharedDirectories =
+      mkOption {
+        type = types.attrsOf
+          (types.submodule {
+            options.source = mkOption {
+              type = types.str;
+              description = "The path of the directory to share, can be a shell variable";
+            };
+            options.target = mkOption {
+              type = types.path;
+              description = "The mount point of the directory inside the virtual machine";
+            };
+          });
+        default = { };
+        example = {
+          my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
+        };
+        description =
+          ''
+            An attributes set of directories that will be shared with the
+            virtual machine using VirtFS (9P filesystem over VirtIO).
+            The attribute name will be used as the 9P mount tag.
+          '';
+      };
+
     virtualisation.pathsInNixDB =
       mkOption {
+        type = types.listOf types.path;
         default = [];
         description =
           ''
@@ -367,8 +411,78 @@ in
           '';
       };
 
+    virtualisation.forwardPorts = mkOption {
+      type = types.listOf
+        (types.submodule {
+          options.from = mkOption {
+            type = types.enum [ "host" "guest" ];
+            default = "host";
+            description =
+              ''
+                Controls the direction in which the ports are mapped:
+
+                - <literal>"host"</literal> means traffic from the host ports
+                is forwarded to the given guest port.
+
+                - <literal>"guest"</literal> means traffic from the guest ports
+                is forwarded to the given host port.
+              '';
+          };
+          options.proto = mkOption {
+            type = types.enum [ "tcp" "udp" ];
+            default = "tcp";
+            description = "The protocol to forward.";
+          };
+          options.host.address = mkOption {
+            type = types.str;
+            default = "";
+            description = "The IPv4 address of the host.";
+          };
+          options.host.port = mkOption {
+            type = types.port;
+            description = "The host port to be mapped.";
+          };
+          options.guest.address = mkOption {
+            type = types.str;
+            default = "";
+            description = "The IPv4 address on the guest VLAN.";
+          };
+          options.guest.port = mkOption {
+            type = types.port;
+            description = "The guest port to be mapped.";
+          };
+        });
+      default = [];
+      example = lib.literalExample
+        ''
+        [ # forward local port 2222 -> 22, to ssh into the VM
+          { from = "host"; host.port = 2222; guest.port = 22; }
+
+          # forward local port 80 -> 10.0.2.10:80 in the VLAN
+          { from = "guest";
+            guest.address = "10.0.2.10"; guest.port = 80;
+            host.address = "127.0.0.1"; host.port = 80;
+          }
+        ]
+        '';
+      description =
+        ''
+          When using the SLiRP user networking (default), this option allows to
+          forward ports to/from the host/guest.
+
+          <warning><para>
+            If the NixOS firewall on the virtual machine is enabled, you also
+            have to open the guest ports to enable the traffic between host and
+            guest.
+          </para></warning>
+
+          <note><para>Currently QEMU supports only IPv4 forwarding.</para></note>
+        '';
+    };
+
     virtualisation.vlans =
       mkOption {
+        type = types.listOf types.ints.unsigned;
         default = [ 1 ];
         example = [ 1 2 ];
         description =
@@ -386,6 +500,7 @@ in
 
     virtualisation.writableStore =
       mkOption {
+        type = types.bool;
         default = true; # FIXME
         description =
           ''
@@ -397,6 +512,7 @@ in
 
     virtualisation.writableStoreUseTmpfs =
       mkOption {
+        type = types.bool;
         default = true;
         description =
           ''
@@ -407,6 +523,7 @@ in
 
     networking.primaryIPAddress =
       mkOption {
+        type = types.str;
         default = "";
         internal = true;
         description = "Primary IP address used in /etc/hosts.";
@@ -423,7 +540,7 @@ in
 
       options =
         mkOption {
-          type = types.listOf types.unspecified;
+          type = types.listOf types.str;
           default = [];
           example = [ "-vga std" ];
           description = "Options passed to QEMU.";
@@ -432,7 +549,7 @@ in
       consoles = mkOption {
         type = types.listOf types.str;
         default = let
-          consoles = [ "${qemuSerialDevice},115200n8" "tty0" ];
+          consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
         in if cfg.graphics then consoles else reverseList consoles;
         example = [ "console=tty1" ];
         description = ''
@@ -448,17 +565,18 @@ in
 
       networkingOptions =
         mkOption {
-          default = [
+          type = types.listOf types.str;
+          default = [ ];
+          example = [
             "-net nic,netdev=user.0,model=virtio"
-            "-netdev user,id=user.0\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
+            "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
           ];
-          type = types.listOf types.str;
           description = ''
             Networking-related command-line options that should be passed to qemu.
-            The default is to use userspace networking (slirp).
+            The default is to use userspace networking (SLiRP).
 
             If you override this option, be advised to keep
-            ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the default)
+            ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the example)
             to keep the default runtime behaviour.
           '';
         };
@@ -472,16 +590,16 @@ in
 
       diskInterface =
         mkOption {
+          type = types.enum [ "virtio" "scsi" "ide" ];
           default = "virtio";
           example = "scsi";
-          type = types.enum [ "virtio" "scsi" "ide" ];
           description = "The interface used for the virtual hard disks.";
         };
 
       guestAgent.enable =
         mkOption {
-          default = true;
           type = types.bool;
+          default = true;
           description = ''
             Enable the Qemu guest agent.
           '';
@@ -490,6 +608,7 @@ in
 
     virtualisation.useBootLoader =
       mkOption {
+        type = types.bool;
         default = false;
         description =
           ''
@@ -504,6 +623,7 @@ in
 
     virtualisation.useEFIBoot =
       mkOption {
+        type = types.bool;
         default = false;
         description =
           ''
@@ -515,6 +635,7 @@ in
 
     virtualisation.efiVars =
       mkOption {
+        type = types.str;
         default = "./${config.system.name}-efi-vars.fd";
         description =
           ''
@@ -525,8 +646,8 @@ in
 
     virtualisation.bios =
       mkOption {
-        default = null;
         type = types.nullOr types.package;
+        default = null;
         description =
           ''
             An alternate BIOS (such as <package>qboot</package>) with which to start the VM.
@@ -539,6 +660,25 @@ in
 
   config = {
 
+    assertions =
+      lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule:
+        [
+          { assertion = rule.from == "guest" -> rule.proto == "tcp";
+            message =
+              ''
+                Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto:
+                  Guest forwarding supports only TCP connections.
+              '';
+          }
+          { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
+            message =
+              ''
+                Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address:
+                  The address must be in the default VLAN (10.0.2.0/24).
+              '';
+          }
+        ]));
+
     # Note [Disk layout with `useBootLoader`]
     #
     # If `useBootLoader = true`, we configure 2 drives:
@@ -560,6 +700,7 @@ in
         then driveDeviceName 2 # second disk
         else cfg.bootDevice
     );
+    boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
 
     boot.initrd.extraUtilsCommands =
       ''
@@ -618,6 +759,28 @@ in
 
     virtualisation.pathsInNixDB = [ config.system.build.toplevel ];
 
+    virtualisation.sharedDirectories = {
+      nix-store = { source = "/nix/store"; target = "/nix/store"; };
+      xchg      = { source = ''"$TMPDIR"/xchg''; target = "/tmp/xchg"; };
+      shared    = { source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; target = "/tmp/shared"; };
+    };
+
+    virtualisation.qemu.networkingOptions =
+      let
+        forwardingOptions = flip concatMapStrings cfg.forwardPorts
+          ({ proto, from, host, guest }:
+            if from == "host"
+              then "hostfwd=${proto}:${host.address}:${toString host.port}-" +
+                   "${guest.address}:${toString guest.port},"
+              else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
+                   "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
+          );
+      in
+      [
+        "-net nic,netdev=user.0,model=virtio"
+        "-netdev user,id=user.0,${forwardingOptions}\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
+      ];
+
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
       (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
@@ -646,7 +809,7 @@ in
     virtualisation.qemu.drives = mkMerge [
       [{
         name = "root";
-        file = "$NIX_DISK_IMAGE";
+        file = ''"$NIX_DISK_IMAGE"'';
         driveExtraOpts.cache = "writeback";
         driveExtraOpts.werror = "report";
       }]
@@ -655,7 +818,7 @@ in
         # note [Disk layout with `useBootLoader`].
         {
           name = "boot";
-          file = "$TMPDIR/disk.img";
+          file = ''"$TMPDIR"/disk.img'';
           driveExtraOpts.media = "disk";
           deviceExtraOpts.bootindex = "1";
         }
@@ -672,15 +835,26 @@ in
     # configuration, where the regular value for the `fileSystems'
     # attribute should be disregarded for the purpose of building a VM
     # test image (since those filesystems don't exist in the VM).
-    fileSystems = mkVMOverride (
-      cfg.fileSystems //
-      { "/".device = cfg.bootDevice;
-        ${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} =
-          { device = "store";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
-          };
+    fileSystems =
+    let
+      mkSharedDir = tag: share:
+        {
+          name =
+            if tag == "nix-store" && cfg.writableStore
+              then "/nix/.ro-store"
+              else share.target;
+          value.device = tag;
+          value.fsType = "9p";
+          value.neededForBoot = true;
+          value.options =
+            [ "trans=virtio" "version=9p2000.L"  "msize=${toString cfg.msize}" ]
+            ++ lib.optional (tag == "nix-store") "cache=loose";
+        };
+    in
+      mkVMOverride (cfg.fileSystems //
+      {
+        "/".device = cfg.bootDevice;
+
         "/tmp" = mkIf config.boot.tmpOnTmpfs
           { device = "tmpfs";
             fsType = "tmpfs";
@@ -688,32 +862,20 @@ in
             # Sync with systemd's tmp.mount;
             options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
           };
-        "/tmp/xchg" =
-          { device = "xchg";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
-          };
-        "/tmp/shared" =
-          { device = "shared";
-            fsType = "9p";
-            options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}";
-            neededForBoot = true;
-          };
-      } // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs)
-      { "/nix/.rw-store" =
+
+        "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs)
           { fsType = "tmpfs";
             options = [ "mode=0755" ];
             neededForBoot = true;
           };
-      } // optionalAttrs cfg.useBootLoader
-      { "/boot" =
+
+        "/boot" = mkIf cfg.useBootLoader
           # see note [Disk layout with `useBootLoader`]
           { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
             fsType = "vfat";
             noCheck = true; # fsck fails on a r/o filesystem
           };
-      });
+      } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
 
     swapDevices = mkVMOverride [ ];
     boot.initrd.luks.devices = mkVMOverride {};
@@ -734,7 +896,7 @@ in
     # video driver the host uses.
     services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
     services.xserver.defaultDepth = mkVMOverride 0;
-    services.xserver.resolutions = mkVMOverride [ { x = 1024; y = 768; } ];
+    services.xserver.resolutions = mkVMOverride [ cfg.resolution ];
     services.xserver.monitorSection =
       ''
         # Set a higher refresh rate so that resolutions > 800x600 work.