···44# `config'. By default, the Nix store is shared read-only with the
55# host, which makes (re)building VMs very efficient.
6677-{ config, lib, pkgs, options, ... }:
77+{
88+ config,
99+ lib,
1010+ pkgs,
1111+ options,
1212+ ...
1313+}:
814915with lib;
1016···22282329 consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
24302525- driveOpts = { ... }: {
3131+ driveOpts =
3232+ { ... }:
3333+ {
26342727- options = {
3535+ options = {
28362929- file = mkOption {
3030- type = types.str;
3131- description = "The file image used for this drive.";
3232- };
3737+ file = mkOption {
3838+ type = types.str;
3939+ description = "The file image used for this drive.";
4040+ };
4141+4242+ driveExtraOpts = mkOption {
4343+ type = types.attrsOf types.str;
4444+ default = { };
4545+ description = "Extra options passed to drive flag.";
4646+ };
33473434- driveExtraOpts = mkOption {
3535- type = types.attrsOf types.str;
3636- default = {};
3737- description = "Extra options passed to drive flag.";
3838- };
4848+ deviceExtraOpts = mkOption {
4949+ type = types.attrsOf types.str;
5050+ default = { };
5151+ description = "Extra options passed to device flag.";
5252+ };
39534040- deviceExtraOpts = mkOption {
4141- type = types.attrsOf types.str;
4242- default = {};
4343- description = "Extra options passed to device flag.";
4444- };
5454+ name = mkOption {
5555+ type = types.nullOr types.str;
5656+ default = null;
5757+ description = "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
5858+ };
45594646- name = mkOption {
4747- type = types.nullOr types.str;
4848- default = null;
4949- description = "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
5060 };
51615262 };
53635454- };
6464+ selectPartitionTableLayout =
6565+ { useEFIBoot, useDefaultFilesystems }:
6666+ if useDefaultFilesystems then if useEFIBoot then "efi" else "legacy" else "none";
55675656- selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }:
5757- if useDefaultFilesystems then
5858- if useEFIBoot then "efi" else "legacy"
5959- else "none";
6060-6161- driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
6868+ driveCmdline =
6969+ idx:
7070+ {
7171+ file,
7272+ driveExtraOpts,
7373+ deviceExtraOpts,
7474+ ...
7575+ }:
6276 let
6377 drvId = "drive${toString idx}";
6464- mkKeyValue = generators.mkKeyValueDefault {} "=";
7878+ mkKeyValue = generators.mkKeyValueDefault { } "=";
6579 mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts);
6666- driveOpts = mkOpts (driveExtraOpts // {
6767- index = idx;
6868- id = drvId;
6969- "if" = "none";
7070- inherit file;
7171- });
7272- deviceOpts = mkOpts (deviceExtraOpts // {
7373- drive = drvId;
7474- });
8080+ driveOpts = mkOpts (
8181+ driveExtraOpts
8282+ // {
8383+ index = idx;
8484+ id = drvId;
8585+ "if" = "none";
8686+ inherit file;
8787+ }
8888+ );
8989+ deviceOpts = mkOpts (
9090+ deviceExtraOpts
9191+ // {
9292+ drive = drvId;
9393+ }
9494+ );
7595 device =
7696 if cfg.qemu.diskInterface == "scsi" then
7797 "-device lsi53c895a -device scsi-hd,${deviceOpts}"
7898 else
7999 "-device virtio-blk-pci,${deviceOpts}";
80100 in
8181- "-drive ${driveOpts} ${device}";
101101+ "-drive ${driveOpts} ${device}";
8210283103 drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives);
8410485105 # Shell script to start the VM.
8686- startVM =
8787- ''
8888- #! ${hostPkgs.runtimeShell}
106106+ startVM = ''
107107+ #! ${hostPkgs.runtimeShell}
891089090- export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH
9191-9292- set -e
109109+ export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH
931109494- # Create an empty ext4 filesystem image. A filesystem image does not
9595- # contain a partition table but just a filesystem.
9696- createEmptyFilesystemImage() {
9797- local name=$1
9898- local size=$2
9999- local temp=$(mktemp)
100100- ${qemu}/bin/qemu-img create -f raw "$temp" "$size"
101101- ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp"
102102- ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name"
103103- rm "$temp"
104104- }
111111+ set -e
105112106106- NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
113113+ # Create an empty ext4 filesystem image. A filesystem image does not
114114+ # contain a partition table but just a filesystem.
115115+ createEmptyFilesystemImage() {
116116+ local name=$1
117117+ local size=$2
118118+ local temp=$(mktemp)
119119+ ${qemu}/bin/qemu-img create -f raw "$temp" "$size"
120120+ ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp"
121121+ ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name"
122122+ rm "$temp"
123123+ }
107124108108- if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
109109- echo "Disk image do not exist, creating the virtualisation disk image..."
125125+ NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
110126111111- ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then ''
112112- # Create a writable qcow2 image using the systemImage as a backing
113113- # image.
127127+ if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
128128+ echo "Disk image do not exist, creating the virtualisation disk image..."
114129115115- # CoW prevent size to be attributed to an image.
116116- # FIXME: raise this issue to upstream.
117117- ${qemu}/bin/qemu-img create \
118118- -f qcow2 \
119119- -b ${systemImage}/nixos.qcow2 \
120120- -F qcow2 \
121121- "$NIX_DISK_IMAGE"
122122- '' else if cfg.useDefaultFilesystems then ''
123123- createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
124124- '' else ''
125125- # Create an empty disk image without a filesystem.
126126- ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
127127- ''
128128- }
129129- echo "Virtualisation disk image created."
130130- fi
130130+ ${
131131+ if (cfg.useBootLoader && cfg.useDefaultFilesystems) then
132132+ ''
133133+ # Create a writable qcow2 image using the systemImage as a backing
134134+ # image.
131135132132- # Create a directory for storing temporary data of the running VM.
133133- if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then
134134- TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
135135- fi
136136+ # CoW prevent size to be attributed to an image.
137137+ # FIXME: raise this issue to upstream.
138138+ ${qemu}/bin/qemu-img create \
139139+ -f qcow2 \
140140+ -b ${systemImage}/nixos.qcow2 \
141141+ -F qcow2 \
142142+ "$NIX_DISK_IMAGE"
143143+ ''
144144+ else if cfg.useDefaultFilesystems then
145145+ ''
146146+ createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
147147+ ''
148148+ else
149149+ ''
150150+ # Create an empty disk image without a filesystem.
151151+ ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
152152+ ''
153153+ }
154154+ echo "Virtualisation disk image created."
155155+ fi
136156137137- ${lib.optionalString (cfg.useNixStoreImage) ''
138138- echo "Creating Nix store image..."
157157+ # Create a directory for storing temporary data of the running VM.
158158+ if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then
159159+ TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
160160+ fi
139161140140- ${hostPkgs.gnutar}/bin/tar --create \
141141- --absolute-names \
142142- --verbatim-files-from \
143143- --transform 'flags=rSh;s|/nix/store/||' \
144144- --transform 'flags=rSh;s|~nix~case~hack~[[:digit:]]\+||g' \
145145- --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \
146146- | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \
147147- --quiet \
148148- --force-uid=0 \
149149- --force-gid=0 \
150150- -L ${nixStoreFilesystemLabel} \
151151- -U eb176051-bd15-49b7-9e6b-462e0b467019 \
152152- -T 0 \
153153- --tar=f \
154154- "$TMPDIR"/store.img
162162+ ${lib.optionalString (cfg.useNixStoreImage) ''
163163+ echo "Creating Nix store image..."
155164156156- echo "Created Nix store image."
157157- ''
158158- }
165165+ ${hostPkgs.gnutar}/bin/tar --create \
166166+ --absolute-names \
167167+ --verbatim-files-from \
168168+ --transform 'flags=rSh;s|/nix/store/||' \
169169+ --transform 'flags=rSh;s|~nix~case~hack~[[:digit:]]\+||g' \
170170+ --files-from ${
171171+ hostPkgs.closureInfo {
172172+ rootPaths = [
173173+ config.system.build.toplevel
174174+ regInfo
175175+ ];
176176+ }
177177+ }/store-paths \
178178+ | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \
179179+ --quiet \
180180+ --force-uid=0 \
181181+ --force-gid=0 \
182182+ -L ${nixStoreFilesystemLabel} \
183183+ -U eb176051-bd15-49b7-9e6b-462e0b467019 \
184184+ -T 0 \
185185+ --tar=f \
186186+ "$TMPDIR"/store.img
159187160160- # Create a directory for exchanging data with the VM.
161161- mkdir -p "$TMPDIR/xchg"
188188+ echo "Created Nix store image."
189189+ ''}
162190163163- ${lib.optionalString cfg.useHostCerts
164164- ''
165165- mkdir -p "$TMPDIR/certs"
166166- if [ -e "$NIX_SSL_CERT_FILE" ]; then
167167- cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt
168168- else
169169- echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled.
170170- fi
171171- ''}
191191+ # Create a directory for exchanging data with the VM.
192192+ mkdir -p "$TMPDIR/xchg"
172193173173- ${lib.optionalString cfg.useEFIBoot
174174- ''
175175- # Expose EFI variables, it's useful even when we are not using a bootloader (!).
176176- # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence
177177- # no guard against `useBootLoader`. Examples:
178178- # - testing PXE boot or other EFI applications
179179- # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows
180180- NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
181181- # VM needs writable EFI vars
182182- if ! test -e "$NIX_EFI_VARS"; then
183183- ${if cfg.efi.keepVariables then
184184- # We still need the EFI var from the make-disk-image derivation
185185- # because our "switch-to-configuration" process might
186186- # write into it and we want to keep this data.
187187- ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"''
188188- else
189189- ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"''
190190- }
191191- chmod 0644 "$NIX_EFI_VARS"
192192- fi
193193- ''}
194194+ ${lib.optionalString cfg.useHostCerts ''
195195+ mkdir -p "$TMPDIR/certs"
196196+ if [ -e "$NIX_SSL_CERT_FILE" ]; then
197197+ cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt
198198+ else
199199+ echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled.
200200+ fi
201201+ ''}
194202195195- ${lib.optionalString cfg.tpm.enable ''
196196- NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}")
197197- mkdir -p "$NIX_SWTPM_DIR"
198198- ${lib.getExe cfg.tpm.package} \
199199- socket \
200200- --tpmstate dir="$NIX_SWTPM_DIR" \
201201- --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \
202202- --pid file="$NIX_SWTPM_DIR"/pid --daemon \
203203- --tpm2 \
204204- --log file="$NIX_SWTPM_DIR"/stdout,level=6
203203+ ${lib.optionalString cfg.useEFIBoot ''
204204+ # Expose EFI variables, it's useful even when we are not using a bootloader (!).
205205+ # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence
206206+ # no guard against `useBootLoader`. Examples:
207207+ # - testing PXE boot or other EFI applications
208208+ # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows
209209+ NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
210210+ # VM needs writable EFI vars
211211+ if ! test -e "$NIX_EFI_VARS"; then
212212+ ${
213213+ if cfg.efi.keepVariables then
214214+ # We still need the EFI var from the make-disk-image derivation
215215+ # because our "switch-to-configuration" process might
216216+ # write into it and we want to keep this data.
217217+ ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"''
218218+ else
219219+ ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"''
220220+ }
221221+ chmod 0644 "$NIX_EFI_VARS"
222222+ fi
223223+ ''}
205224206206- # Enable `fdflags` builtin in Bash
207207- # We will need it to perform surgical modification of the file descriptor
208208- # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor
209209- # on exec.
210210- # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec`
211211- # at the end of this script. To work around that, we will just clear
212212- # the `FD_CLOEXEC` bits as a first step.
213213- enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags
214214- # leave a dangling subprocess because the swtpm ctrl socket has
215215- # "terminate" when the last connection disconnects, it stops swtpm.
216216- # When qemu stops, or if the main shell process ends, the coproc will
217217- # get signaled by virtue of the pipe between main and coproc ending.
218218- # Which in turns triggers a socat connect-disconnect to swtpm which
219219- # will stop it.
220220- coproc waitingswtpm {
221221- read || :
222222- echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket
223223- }
224224- # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin.
225225- fdflags -s-cloexec ''${waitingswtpm[1]}
226226- ''}
225225+ ${lib.optionalString cfg.tpm.enable ''
226226+ NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}")
227227+ mkdir -p "$NIX_SWTPM_DIR"
228228+ ${lib.getExe cfg.tpm.package} \
229229+ socket \
230230+ --tpmstate dir="$NIX_SWTPM_DIR" \
231231+ --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \
232232+ --pid file="$NIX_SWTPM_DIR"/pid --daemon \
233233+ --tpm2 \
234234+ --log file="$NIX_SWTPM_DIR"/stdout,level=6
227235228228- cd "$TMPDIR"
236236+ # Enable `fdflags` builtin in Bash
237237+ # We will need it to perform surgical modification of the file descriptor
238238+ # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor
239239+ # on exec.
240240+ # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec`
241241+ # at the end of this script. To work around that, we will just clear
242242+ # the `FD_CLOEXEC` bits as a first step.
243243+ enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags
244244+ # leave a dangling subprocess because the swtpm ctrl socket has
245245+ # "terminate" when the last connection disconnects, it stops swtpm.
246246+ # When qemu stops, or if the main shell process ends, the coproc will
247247+ # get signaled by virtue of the pipe between main and coproc ending.
248248+ # Which in turns triggers a socat connect-disconnect to swtpm which
249249+ # will stop it.
250250+ coproc waitingswtpm {
251251+ read || :
252252+ echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket
253253+ }
254254+ # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin.
255255+ fdflags -s-cloexec ''${waitingswtpm[1]}
256256+ ''}
229257230230- ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
231231- ${flip concatMapStrings cfg.emptyDiskImages (size: ''
232232- if ! test -e "empty$idx.qcow2"; then
233233- ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
234234- fi
235235- idx=$((idx + 1))
236236- '')}
258258+ cd "$TMPDIR"
237259238238- # Start QEMU.
239239- exec ${qemu-common.qemuBinary qemu} \
240240- -name ${config.system.name} \
241241- -m ${toString config.virtualisation.memorySize} \
242242- -smp ${toString config.virtualisation.cores} \
243243- -device virtio-rng-pci \
244244- ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
245245- ${concatStringsSep " \\\n "
246246- (mapAttrsToList
247247- (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}")
248248- config.virtualisation.sharedDirectories)} \
249249- ${drivesCmdLine config.virtualisation.qemu.drives} \
250250- ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \
251251- $QEMU_OPTS \
252252- "$@"
253253- '';
260260+ ${lib.optionalString (cfg.emptyDiskImages != [ ]) "idx=0"}
261261+ ${flip concatMapStrings cfg.emptyDiskImages (size: ''
262262+ if ! test -e "empty$idx.qcow2"; then
263263+ ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
264264+ fi
265265+ idx=$((idx + 1))
266266+ '')}
254267268268+ # Start QEMU.
269269+ exec ${qemu-common.qemuBinary qemu} \
270270+ -name ${config.system.name} \
271271+ -m ${toString config.virtualisation.memorySize} \
272272+ -smp ${toString config.virtualisation.cores} \
273273+ -device virtio-rng-pci \
274274+ ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \
275275+ ${
276276+ concatStringsSep " \\\n " (
277277+ mapAttrsToList (
278278+ tag: share:
279279+ "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}"
280280+ ) config.virtualisation.sharedDirectories
281281+ )
282282+ } \
283283+ ${drivesCmdLine config.virtualisation.qemu.drives} \
284284+ ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \
285285+ $QEMU_OPTS \
286286+ "$@"
287287+ '';
255288256289 regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
257290···284317 copyChannel = false;
285318 OVMF = cfg.efi.OVMF;
286319 };
287287-288320in
289289-290321{
291322 imports = [
292323 ../profiles/qemu-guest.nix
293293- (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ])
294294- (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.")
295295- (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")
296296- (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.`")
324324+ ./disk-size-option.nix
325325+ (mkRenamedOptionModule
326326+ [
327327+ "virtualisation"
328328+ "pathsInNixDB"
329329+ ]
330330+ [
331331+ "virtualisation"
332332+ "additionalPaths"
333333+ ]
334334+ )
335335+ (mkRemovedOptionModule
336336+ [
337337+ "virtualisation"
338338+ "bootDevice"
339339+ ]
340340+ "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."
341341+ )
342342+ (mkRemovedOptionModule
343343+ [
344344+ "virtualisation"
345345+ "efiVars"
346346+ ]
347347+ "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"
348348+ )
349349+ (mkRemovedOptionModule
350350+ [
351351+ "virtualisation"
352352+ "persistBootDevice"
353353+ ]
354354+ "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.`"
355355+ )
297356 ];
298357299358 options = {
300359301360 virtualisation.fileSystems = options.fileSystems;
302361303303- virtualisation.memorySize =
304304- mkOption {
305305- type = types.ints.positive;
306306- default = 1024;
307307- description = ''
308308- The memory size in megabytes of the virtual machine.
309309- '';
310310- };
362362+ virtualisation.memorySize = mkOption {
363363+ type = types.ints.positive;
364364+ default = 1024;
365365+ description = ''
366366+ The memory size in megabytes of the virtual machine.
367367+ '';
368368+ };
311369312312- virtualisation.msize =
313313- mkOption {
314314- type = types.ints.positive;
315315- default = 16384;
316316- description = ''
317317- The msize (maximum packet size) option passed to 9p file systems, in
318318- bytes. Increasing this should increase performance significantly,
319319- at the cost of higher RAM usage.
320320- '';
321321- };
370370+ virtualisation.msize = mkOption {
371371+ type = types.ints.positive;
372372+ default = 16384;
373373+ description = ''
374374+ The msize (maximum packet size) option passed to 9p file systems, in
375375+ bytes. Increasing this should increase performance significantly,
376376+ at the cost of higher RAM usage.
377377+ '';
378378+ };
322379323323- virtualisation.diskSize =
324324- mkOption {
325325- type = types.ints.positive;
326326- default = 1024;
327327- description = ''
328328- The disk size in megabytes of the virtual machine.
329329- '';
330330- };
380380+ virtualisation.diskImage = mkOption {
381381+ type = types.nullOr types.str;
382382+ default = "./${config.system.name}.qcow2";
383383+ defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
384384+ description = ''
385385+ Path to the disk image containing the root filesystem.
386386+ The image will be created on startup if it does not
387387+ exist.
331388332332- virtualisation.diskImage =
333333- mkOption {
334334- type = types.nullOr types.str;
335335- default = "./${config.system.name}.qcow2";
336336- defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
337337- description = ''
338338- Path to the disk image containing the root filesystem.
339339- The image will be created on startup if it does not
340340- exist.
389389+ If null, a tmpfs will be used as the root filesystem and
390390+ the VM's state will not be persistent.
391391+ '';
392392+ };
341393342342- If null, a tmpfs will be used as the root filesystem and
343343- the VM's state will not be persistent.
344344- '';
345345- };
346346-347347- virtualisation.bootLoaderDevice =
348348- mkOption {
349349- type = types.path;
350350- default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}";
351351- defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}'';
352352- example = "/dev/disk/by-id/virtio-boot-loader-device";
353353- description = ''
354354- The path (inside th VM) to the device to boot from when legacy booting.
355355- '';
356356- };
394394+ virtualisation.bootLoaderDevice = mkOption {
395395+ type = types.path;
396396+ default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}";
397397+ defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}'';
398398+ example = "/dev/disk/by-id/virtio-boot-loader-device";
399399+ description = ''
400400+ The path (inside th VM) to the device to boot from when legacy booting.
401401+ '';
402402+ };
357403358358- virtualisation.bootPartition =
359359- mkOption {
360360- type = types.nullOr types.path;
361361- default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null;
362362- defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null'';
363363- example = "/dev/disk/by-label/esp";
364364- description = ''
365365- The path (inside the VM) to the device containing the EFI System Partition (ESP).
404404+ virtualisation.bootPartition = mkOption {
405405+ type = types.nullOr types.path;
406406+ default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null;
407407+ defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null'';
408408+ example = "/dev/disk/by-label/esp";
409409+ description = ''
410410+ The path (inside the VM) to the device containing the EFI System Partition (ESP).
366411367367- If you are *not* booting from a UEFI firmware, this value is, by
368368- default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`.
369369- '';
370370- };
412412+ If you are *not* booting from a UEFI firmware, this value is, by
413413+ default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`.
414414+ '';
415415+ };
371416372372- virtualisation.rootDevice =
373373- mkOption {
374374- type = types.nullOr types.path;
375375- default = "/dev/disk/by-label/${rootFilesystemLabel}";
376376- defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}'';
377377- example = "/dev/disk/by-label/nixos";
378378- description = ''
379379- The path (inside the VM) to the device containing the root filesystem.
380380- '';
381381- };
417417+ virtualisation.rootDevice = mkOption {
418418+ type = types.nullOr types.path;
419419+ default = "/dev/disk/by-label/${rootFilesystemLabel}";
420420+ defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}'';
421421+ example = "/dev/disk/by-label/nixos";
422422+ description = ''
423423+ The path (inside the VM) to the device containing the root filesystem.
424424+ '';
425425+ };
382426383383- virtualisation.emptyDiskImages =
384384- mkOption {
385385- type = types.listOf types.ints.positive;
386386- default = [];
387387- description = ''
388388- Additional disk images to provide to the VM. The value is
389389- a list of size in megabytes of each disk. These disks are
390390- writeable by the VM.
391391- '';
392392- };
427427+ virtualisation.emptyDiskImages = mkOption {
428428+ type = types.listOf types.ints.positive;
429429+ default = [ ];
430430+ description = ''
431431+ Additional disk images to provide to the VM. The value is
432432+ a list of size in megabytes of each disk. These disks are
433433+ writeable by the VM.
434434+ '';
435435+ };
393436394394- virtualisation.graphics =
395395- mkOption {
396396- type = types.bool;
397397- default = true;
398398- description = ''
399399- Whether to run QEMU with a graphics window, or in nographic mode.
400400- Serial console will be enabled on both settings, but this will
401401- change the preferred console.
402402- '';
403403- };
437437+ virtualisation.graphics = mkOption {
438438+ type = types.bool;
439439+ default = true;
440440+ description = ''
441441+ Whether to run QEMU with a graphics window, or in nographic mode.
442442+ Serial console will be enabled on both settings, but this will
443443+ change the preferred console.
444444+ '';
445445+ };
404446405405- virtualisation.resolution =
406406- mkOption {
407407- type = options.services.xserver.resolutions.type.nestedTypes.elemType;
408408- default = { x = 1024; y = 768; };
409409- description = ''
410410- The resolution of the virtual machine display.
411411- '';
447447+ virtualisation.resolution = mkOption {
448448+ type = options.services.xserver.resolutions.type.nestedTypes.elemType;
449449+ default = {
450450+ x = 1024;
451451+ y = 768;
412452 };
453453+ description = ''
454454+ The resolution of the virtual machine display.
455455+ '';
456456+ };
413457414414- virtualisation.cores =
415415- mkOption {
416416- type = types.ints.positive;
417417- default = 1;
418418- description = ''
419419- Specify the number of cores the guest is permitted to use.
420420- The number can be higher than the available cores on the
421421- host system.
422422- '';
423423- };
458458+ virtualisation.cores = mkOption {
459459+ type = types.ints.positive;
460460+ default = 1;
461461+ description = ''
462462+ Specify the number of cores the guest is permitted to use.
463463+ The number can be higher than the available cores on the
464464+ host system.
465465+ '';
466466+ };
424467425425- virtualisation.sharedDirectories =
426426- mkOption {
427427- type = types.attrsOf
428428- (types.submodule {
429429- options.source = mkOption {
430430- type = types.str;
431431- description = "The path of the directory to share, can be a shell variable";
432432- };
433433- options.target = mkOption {
434434- type = types.path;
435435- description = "The mount point of the directory inside the virtual machine";
436436- };
437437- options.securityModel = mkOption {
438438- type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ];
439439- default = "mapped-xattr";
440440- description = ''
441441- The security model to use for this share:
468468+ virtualisation.sharedDirectories = mkOption {
469469+ type = types.attrsOf (
470470+ types.submodule {
471471+ options.source = mkOption {
472472+ type = types.str;
473473+ description = "The path of the directory to share, can be a shell variable";
474474+ };
475475+ options.target = mkOption {
476476+ type = types.path;
477477+ description = "The mount point of the directory inside the virtual machine";
478478+ };
479479+ options.securityModel = mkOption {
480480+ type = types.enum [
481481+ "passthrough"
482482+ "mapped-xattr"
483483+ "mapped-file"
484484+ "none"
485485+ ];
486486+ default = "mapped-xattr";
487487+ description = ''
488488+ The security model to use for this share:
442489443443- - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root)
444444- - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes
445445- - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools
446446- - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership
447447- '';
448448- };
449449- });
450450- default = { };
451451- example = {
452452- my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
490490+ - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root)
491491+ - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes
492492+ - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools
493493+ - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership
494494+ '';
495495+ };
496496+ }
497497+ );
498498+ default = { };
499499+ example = {
500500+ my-share = {
501501+ source = "/path/to/be/shared";
502502+ target = "/mnt/shared";
453503 };
454454- description = ''
455455- An attributes set of directories that will be shared with the
456456- virtual machine using VirtFS (9P filesystem over VirtIO).
457457- The attribute name will be used as the 9P mount tag.
458458- '';
459504 };
505505+ description = ''
506506+ An attributes set of directories that will be shared with the
507507+ virtual machine using VirtFS (9P filesystem over VirtIO).
508508+ The attribute name will be used as the 9P mount tag.
509509+ '';
510510+ };
460511461461- virtualisation.additionalPaths =
462462- mkOption {
463463- type = types.listOf types.path;
464464- default = [];
465465- description = ''
466466- A list of paths whose closure should be made available to
467467- the VM.
512512+ virtualisation.additionalPaths = mkOption {
513513+ type = types.listOf types.path;
514514+ default = [ ];
515515+ description = ''
516516+ A list of paths whose closure should be made available to
517517+ the VM.
468518469469- When 9p is used, the closure is registered in the Nix
470470- database in the VM. All other paths in the host Nix store
471471- appear in the guest Nix store as well, but are considered
472472- garbage (because they are not registered in the Nix
473473- database of the guest).
519519+ When 9p is used, the closure is registered in the Nix
520520+ database in the VM. All other paths in the host Nix store
521521+ appear in the guest Nix store as well, but are considered
522522+ garbage (because they are not registered in the Nix
523523+ database of the guest).
474524475475- When {option}`virtualisation.useNixStoreImage` is
476476- set, the closure is copied to the Nix store image.
477477- '';
478478- };
525525+ When {option}`virtualisation.useNixStoreImage` is
526526+ set, the closure is copied to the Nix store image.
527527+ '';
528528+ };
479529480530 virtualisation.forwardPorts = mkOption {
481481- type = types.listOf
482482- (types.submodule {
531531+ type = types.listOf (
532532+ types.submodule {
483533 options.from = mkOption {
484484- type = types.enum [ "host" "guest" ];
534534+ type = types.enum [
535535+ "host"
536536+ "guest"
537537+ ];
485538 default = "host";
486539 description = ''
487487- Controls the direction in which the ports are mapped:
540540+ Controls the direction in which the ports are mapped:
488541489489- - `"host"` means traffic from the host ports
490490- is forwarded to the given guest port.
491491- - `"guest"` means traffic from the guest ports
492492- is forwarded to the given host port.
493493- '';
542542+ - `"host"` means traffic from the host ports
543543+ is forwarded to the given guest port.
544544+ - `"guest"` means traffic from the guest ports
545545+ is forwarded to the given host port.
546546+ '';
494547 };
495548 options.proto = mkOption {
496496- type = types.enum [ "tcp" "udp" ];
549549+ type = types.enum [
550550+ "tcp"
551551+ "udp"
552552+ ];
497553 default = "tcp";
498554 description = "The protocol to forward.";
499555 };
···515571 type = types.port;
516572 description = "The guest port to be mapped.";
517573 };
518518- });
519519- default = [];
520520- example = lib.literalExpression
521521- ''
574574+ }
575575+ );
576576+ default = [ ];
577577+ example = lib.literalExpression ''
522578 [ # forward local port 2222 -> 22, to ssh into the VM
523579 { from = "host"; host.port = 2222; guest.port = 22; }
524580···528584 host.address = "127.0.0.1"; host.port = 80;
529585 }
530586 ]
531531- '';
587587+ '';
532588 description = ''
533533- When using the SLiRP user networking (default), this option allows to
534534- forward ports to/from the host/guest.
589589+ When using the SLiRP user networking (default), this option allows to
590590+ forward ports to/from the host/guest.
535591536536- ::: {.warning}
537537- If the NixOS firewall on the virtual machine is enabled, you also
538538- have to open the guest ports to enable the traffic between host and
539539- guest.
540540- :::
592592+ ::: {.warning}
593593+ If the NixOS firewall on the virtual machine is enabled, you also
594594+ have to open the guest ports to enable the traffic between host and
595595+ guest.
596596+ :::
541597542542- ::: {.note}
543543- Currently QEMU supports only IPv4 forwarding.
544544- :::
545545- '';
598598+ ::: {.note}
599599+ Currently QEMU supports only IPv4 forwarding.
600600+ :::
601601+ '';
546602 };
547603548548- virtualisation.restrictNetwork =
549549- mkOption {
550550- type = types.bool;
551551- default = false;
552552- example = true;
553553- description = ''
554554- If this option is enabled, the guest will be isolated, i.e. it will
555555- not be able to contact the host and no guest IP packets will be
556556- routed over the host to the outside. This option does not affect
557557- any explicitly set forwarding rules.
558558- '';
559559- };
604604+ virtualisation.restrictNetwork = mkOption {
605605+ type = types.bool;
606606+ default = false;
607607+ example = true;
608608+ description = ''
609609+ If this option is enabled, the guest will be isolated, i.e. it will
610610+ not be able to contact the host and no guest IP packets will be
611611+ routed over the host to the outside. This option does not affect
612612+ any explicitly set forwarding rules.
613613+ '';
614614+ };
560615561561- virtualisation.vlans =
562562- mkOption {
563563- type = types.listOf types.ints.unsigned;
564564- default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ];
565565- defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]'';
566566- example = [ 1 2 ];
567567- description = ''
568568- Virtual networks to which the VM is connected. Each
569569- number «N» in this list causes
570570- the VM to have a virtual Ethernet interface attached to a
571571- separate virtual network on which it will be assigned IP
572572- address
573573- `192.168.«N».«M»`,
574574- where «M» is the index of this VM
575575- in the list of VMs.
576576- '';
577577- };
616616+ virtualisation.vlans = mkOption {
617617+ type = types.listOf types.ints.unsigned;
618618+ default = if config.virtualisation.interfaces == { } then [ 1 ] else [ ];
619619+ defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]'';
620620+ example = [
621621+ 1
622622+ 2
623623+ ];
624624+ description = ''
625625+ Virtual networks to which the VM is connected. Each
626626+ number «N» in this list causes
627627+ the VM to have a virtual Ethernet interface attached to a
628628+ separate virtual network on which it will be assigned IP
629629+ address
630630+ `192.168.«N».«M»`,
631631+ where «M» is the index of this VM
632632+ in the list of VMs.
633633+ '';
634634+ };
578635579636 virtualisation.interfaces = mkOption {
580580- default = {};
637637+ default = { };
581638 example = {
582639 enp1s0.vlan = 1;
583640 };
584641 description = ''
585642 Network interfaces to add to the VM.
586643 '';
587587- type = with types; attrsOf (submodule {
588588- options = {
589589- vlan = mkOption {
590590- type = types.ints.unsigned;
591591- description = ''
592592- VLAN to which the network interface is connected.
593593- '';
594594- };
644644+ type =
645645+ with types;
646646+ attrsOf (submodule {
647647+ options = {
648648+ vlan = mkOption {
649649+ type = types.ints.unsigned;
650650+ description = ''
651651+ VLAN to which the network interface is connected.
652652+ '';
653653+ };
595654596596- assignIP = mkOption {
597597- type = types.bool;
598598- default = false;
599599- description = ''
600600- Automatically assign an IP address to the network interface using the same scheme as
601601- virtualisation.vlans.
602602- '';
655655+ assignIP = mkOption {
656656+ type = types.bool;
657657+ default = false;
658658+ description = ''
659659+ Automatically assign an IP address to the network interface using the same scheme as
660660+ virtualisation.vlans.
661661+ '';
662662+ };
603663 };
604604- };
605605- });
664664+ });
606665 };
607666608608- virtualisation.writableStore =
609609- mkOption {
610610- type = types.bool;
611611- default = cfg.mountHostNixStore;
612612- defaultText = literalExpression "cfg.mountHostNixStore";
613613- description = ''
614614- If enabled, the Nix store in the VM is made writable by
615615- layering an overlay filesystem on top of the host's Nix
616616- store.
667667+ virtualisation.writableStore = mkOption {
668668+ type = types.bool;
669669+ default = cfg.mountHostNixStore;
670670+ defaultText = literalExpression "cfg.mountHostNixStore";
671671+ description = ''
672672+ If enabled, the Nix store in the VM is made writable by
673673+ layering an overlay filesystem on top of the host's Nix
674674+ store.
617675618618- By default, this is enabled if you mount a host Nix store.
619619- '';
620620- };
676676+ By default, this is enabled if you mount a host Nix store.
677677+ '';
678678+ };
621679622622- virtualisation.writableStoreUseTmpfs =
623623- mkOption {
624624- type = types.bool;
625625- default = true;
626626- description = ''
627627- Use a tmpfs for the writable store instead of writing to the VM's
628628- own filesystem.
629629- '';
630630- };
680680+ virtualisation.writableStoreUseTmpfs = mkOption {
681681+ type = types.bool;
682682+ default = true;
683683+ description = ''
684684+ Use a tmpfs for the writable store instead of writing to the VM's
685685+ own filesystem.
686686+ '';
687687+ };
631688632632- networking.primaryIPAddress =
633633- mkOption {
634634- type = types.str;
635635- default = "";
636636- internal = true;
637637- description = "Primary IP address used in /etc/hosts.";
638638- };
689689+ networking.primaryIPAddress = mkOption {
690690+ type = types.str;
691691+ default = "";
692692+ internal = true;
693693+ description = "Primary IP address used in /etc/hosts.";
694694+ };
639695640640- networking.primaryIPv6Address =
641641- mkOption {
642642- type = types.str;
643643- default = "";
644644- internal = true;
645645- description = "Primary IPv6 address used in /etc/hosts.";
646646- };
696696+ networking.primaryIPv6Address = mkOption {
697697+ type = types.str;
698698+ default = "";
699699+ internal = true;
700700+ description = "Primary IPv6 address used in /etc/hosts.";
701701+ };
647702648703 virtualisation.host.pkgs = mkOption {
649704 type = options.nixpkgs.pkgs.type;
···659714 };
660715661716 virtualisation.qemu = {
662662- package =
663663- mkOption {
664664- type = types.package;
665665- default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu;
666666- defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu";
667667- example = literalExpression "pkgs.qemu_test";
668668- description = "QEMU package to use.";
669669- };
717717+ package = mkOption {
718718+ type = types.package;
719719+ default =
720720+ if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then
721721+ hostPkgs.qemu_kvm
722722+ else
723723+ hostPkgs.qemu;
724724+ defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu";
725725+ example = literalExpression "pkgs.qemu_test";
726726+ description = "QEMU package to use.";
727727+ };
670728671671- options =
672672- mkOption {
673673- type = types.listOf types.str;
674674- default = [];
675675- example = [ "-vga std" ];
676676- description = ''
677677- Options passed to QEMU.
678678- See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list.
679679- '';
680680- };
729729+ options = mkOption {
730730+ type = types.listOf types.str;
731731+ default = [ ];
732732+ example = [ "-vga std" ];
733733+ description = ''
734734+ Options passed to QEMU.
735735+ See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list.
736736+ '';
737737+ };
681738682739 consoles = mkOption {
683740 type = types.listOf types.str;
684684- default = let
685685- consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
686686- in if cfg.graphics then consoles else reverseList consoles;
741741+ default =
742742+ let
743743+ consoles = [
744744+ "${qemu-common.qemuSerialDevice},115200n8"
745745+ "tty0"
746746+ ];
747747+ in
748748+ if cfg.graphics then consoles else reverseList consoles;
687749 example = [ "console=tty1" ];
688750 description = ''
689751 The output console devices to pass to the kernel command line via the
···696758 '';
697759 };
698760699699- networkingOptions =
700700- mkOption {
701701- type = types.listOf types.str;
702702- default = [ ];
703703- example = [
704704- "-net nic,netdev=user.0,model=virtio"
705705- "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
706706- ];
707707- description = ''
708708- Networking-related command-line options that should be passed to qemu.
709709- The default is to use userspace networking (SLiRP).
710710- See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details.
761761+ networkingOptions = mkOption {
762762+ type = types.listOf types.str;
763763+ default = [ ];
764764+ example = [
765765+ "-net nic,netdev=user.0,model=virtio"
766766+ "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
767767+ ];
768768+ description = ''
769769+ Networking-related command-line options that should be passed to qemu.
770770+ The default is to use userspace networking (SLiRP).
771771+ See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details.
711772712712- If you override this option, be advised to keep
713713- `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example)
714714- to keep the default runtime behaviour.
715715- '';
716716- };
717717-718718- drives =
719719- mkOption {
720720- type = types.listOf (types.submodule driveOpts);
721721- description = "Drives passed to qemu.";
722722- };
723723-724724- diskInterface =
725725- mkOption {
726726- type = types.enum [ "virtio" "scsi" "ide" ];
727727- default = "virtio";
728728- example = "scsi";
729729- description = "The interface used for the virtual hard disks.";
730730- };
773773+ If you override this option, be advised to keep
774774+ `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example)
775775+ to keep the default runtime behaviour.
776776+ '';
777777+ };
731778732732- guestAgent.enable =
733733- mkOption {
734734- type = types.bool;
735735- default = true;
736736- description = ''
737737- Enable the Qemu guest agent.
738738- '';
739739- };
779779+ drives = mkOption {
780780+ type = types.listOf (types.submodule driveOpts);
781781+ description = "Drives passed to qemu.";
782782+ };
740783741741- virtioKeyboard =
742742- mkOption {
743743- type = types.bool;
744744- default = true;
745745- description = ''
746746- Enable the virtio-keyboard device.
747747- '';
748748- };
749749- };
784784+ diskInterface = mkOption {
785785+ type = types.enum [
786786+ "virtio"
787787+ "scsi"
788788+ "ide"
789789+ ];
790790+ default = "virtio";
791791+ example = "scsi";
792792+ description = "The interface used for the virtual hard disks.";
793793+ };
750794751751- virtualisation.useNixStoreImage =
752752- mkOption {
795795+ guestAgent.enable = mkOption {
753796 type = types.bool;
754754- default = false;
797797+ default = true;
755798 description = ''
756756- Build and use a disk image for the Nix store, instead of
757757- accessing the host's one through 9p.
758758-759759- For applications which do a lot of reads from the store,
760760- this can drastically improve performance, but at the cost of
761761- disk space and image build time.
762762-763763- The Nix store image is built just-in-time right before the VM is
764764- started. Because it does not produce another derivation, the image is
765765- not cached between invocations and never lands in the store or binary
766766- cache.
767767-768768- If you want a full disk image with a partition table and a root
769769- filesystem instead of only a store image, enable
770770- {option}`virtualisation.useBootLoader` instead.
799799+ Enable the Qemu guest agent.
771800 '';
772801 };
773802774774- virtualisation.mountHostNixStore =
775775- mkOption {
803803+ virtioKeyboard = mkOption {
776804 type = types.bool;
777777- default = !cfg.useNixStoreImage && !cfg.useBootLoader;
778778- defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
805805+ default = true;
779806 description = ''
780780- Mount the host Nix store as a 9p mount.
807807+ Enable the virtio-keyboard device.
781808 '';
782809 };
810810+ };
783811784784- virtualisation.directBoot = {
785785- enable =
786786- mkOption {
787787- type = types.bool;
788788- default = !cfg.useBootLoader;
789789- defaultText = "!cfg.useBootLoader";
790790- description = ''
791791- If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader.
792792- Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html)
812812+ virtualisation.useNixStoreImage = mkOption {
813813+ type = types.bool;
814814+ default = false;
815815+ description = ''
816816+ Build and use a disk image for the Nix store, instead of
817817+ accessing the host's one through 9p.
793818794794- This is enabled by default.
795795- If you want to test netboot, consider disabling this option.
796796- Enable a bootloader with {option}`virtualisation.useBootLoader` if you need.
819819+ For applications which do a lot of reads from the store,
820820+ this can drastically improve performance, but at the cost of
821821+ disk space and image build time.
797822798798- Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU.
799799- Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`.
800800- They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details
801801- For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`.
823823+ The Nix store image is built just-in-time right before the VM is
824824+ started. Because it does not produce another derivation, the image is
825825+ not cached between invocations and never lands in the store or binary
826826+ cache.
802827803803- This will not (re-)boot correctly into a system that has switched to a different configuration on disk.
804804- '';
805805- };
806806- initrd =
807807- mkOption {
808808- type = types.str;
809809- default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
810810- defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}";
811811- description = ''
812812- In direct boot situations, you may want to influence the initrd to load
813813- to use your own customized payload.
828828+ If you want a full disk image with a partition table and a root
829829+ filesystem instead of only a store image, enable
830830+ {option}`virtualisation.useBootLoader` instead.
831831+ '';
832832+ };
814833815815- This is useful if you want to test the netboot image without
816816- testing the firmware or the loading part.
817817- '';
818818- };
834834+ virtualisation.mountHostNixStore = mkOption {
835835+ type = types.bool;
836836+ default = !cfg.useNixStoreImage && !cfg.useBootLoader;
837837+ defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
838838+ description = ''
839839+ Mount the host Nix store as a 9p mount.
840840+ '';
819841 };
820842821821- virtualisation.useBootLoader =
822822- mkOption {
843843+ virtualisation.directBoot = {
844844+ enable = mkOption {
823845 type = types.bool;
824824- default = false;
846846+ default = !cfg.useBootLoader;
847847+ defaultText = "!cfg.useBootLoader";
825848 description = ''
826826- Use a boot loader to boot the system.
827827- This allows, among other things, testing the boot loader.
849849+ If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader.
850850+ Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html)
828851829829- If disabled, the kernel and initrd are directly booted,
830830- forgoing any bootloader.
852852+ This is enabled by default.
853853+ If you want to test netboot, consider disabling this option.
854854+ Enable a bootloader with {option}`virtualisation.useBootLoader` if you need.
831855832832- Check the documentation on {option}`virtualisation.directBoot.enable` for details.
833833- '';
834834- };
856856+ Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU.
857857+ Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`.
858858+ They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details
859859+ For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`.
835860836836- virtualisation.installBootLoader =
837837- mkOption {
838838- type = types.bool;
839839- default = cfg.useBootLoader && cfg.useDefaultFilesystems;
840840- defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems";
861861+ This will not (re-)boot correctly into a system that has switched to a different configuration on disk.
862862+ '';
863863+ };
864864+ initrd = mkOption {
865865+ type = types.str;
866866+ default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
867867+ defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}";
841868 description = ''
842842- Install boot loader to target image.
869869+ In direct boot situations, you may want to influence the initrd to load
870870+ to use your own customized payload.
843871844844- This is best-effort and may break with unconventional partition setups.
845845- Use `virtualisation.useDefaultFilesystems` for a known-working configuration.
872872+ This is useful if you want to test the netboot image without
873873+ testing the firmware or the loading part.
846874 '';
847875 };
876876+ };
877877+878878+ virtualisation.useBootLoader = mkOption {
879879+ type = types.bool;
880880+ default = false;
881881+ description = ''
882882+ Use a boot loader to boot the system.
883883+ This allows, among other things, testing the boot loader.
884884+885885+ If disabled, the kernel and initrd are directly booted,
886886+ forgoing any bootloader.
848887849849- virtualisation.useEFIBoot =
850850- mkOption {
851851- type = types.bool;
852852- default = false;
853853- description = ''
854854- If enabled, the virtual machine will provide a EFI boot
855855- manager.
856856- useEFIBoot is ignored if useBootLoader == false.
857857- '';
858858- };
888888+ Check the documentation on {option}`virtualisation.directBoot.enable` for details.
889889+ '';
890890+ };
891891+892892+ virtualisation.installBootLoader = mkOption {
893893+ type = types.bool;
894894+ default = cfg.useBootLoader && cfg.useDefaultFilesystems;
895895+ defaultText = "cfg.useBootLoader && cfg.useDefaultFilesystems";
896896+ description = ''
897897+ Install boot loader to target image.
898898+899899+ This is best-effort and may break with unconventional partition setups.
900900+ Use `virtualisation.useDefaultFilesystems` for a known-working configuration.
901901+ '';
902902+ };
903903+904904+ virtualisation.useEFIBoot = mkOption {
905905+ type = types.bool;
906906+ default = false;
907907+ description = ''
908908+ If enabled, the virtual machine will provide a EFI boot
909909+ manager.
910910+ useEFIBoot is ignored if useBootLoader == false.
911911+ '';
912912+ };
859913860914 virtualisation.efi = {
861915 OVMF = mkOption {
862916 type = types.package;
863863- default = (pkgs.OVMF.override {
864864- secureBoot = cfg.useSecureBoot;
865865- }).fd;
866866- defaultText = ''(pkgs.OVMF.override {
867867- secureBoot = cfg.useSecureBoot;
868868- }).fd'';
917917+ default =
918918+ (pkgs.OVMF.override {
919919+ secureBoot = cfg.useSecureBoot;
920920+ }).fd;
921921+ defaultText = ''
922922+ (pkgs.OVMF.override {
923923+ secureBoot = cfg.useSecureBoot;
924924+ }).fd'';
869925 description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed.";
870926 };
871927···874930 default = cfg.efi.OVMF.firmware;
875931 defaultText = literalExpression "cfg.efi.OVMF.firmware";
876932 description = ''
877877- Firmware binary for EFI implementation, defaults to OVMF.
878878- '';
933933+ Firmware binary for EFI implementation, defaults to OVMF.
934934+ '';
879935 };
880936881937 variables = mkOption {
···883939 default = cfg.efi.OVMF.variables;
884940 defaultText = literalExpression "cfg.efi.OVMF.variables";
885941 description = ''
886886- Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware.
887887- Defaults to OVMF.
888888- '';
942942+ Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware.
943943+ Defaults to OVMF.
944944+ '';
889945 };
890946891947 keepVariables = mkOption {
···903959904960 deviceModel = mkOption {
905961 type = types.str;
906906- default = ({
907907- "i686-linux" = "tpm-tis";
908908- "x86_64-linux" = "tpm-tis";
909909- "ppc64-linux" = "tpm-spapr";
910910- "armv7-linux" = "tpm-tis-device";
911911- "aarch64-linux" = "tpm-tis-device";
912912- }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
962962+ default = (
963963+ {
964964+ "i686-linux" = "tpm-tis";
965965+ "x86_64-linux" = "tpm-tis";
966966+ "ppc64-linux" = "tpm-spapr";
967967+ "armv7-linux" = "tpm-tis-device";
968968+ "aarch64-linux" = "tpm-tis-device";
969969+ }
970970+ .${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")
971971+ );
913972 defaultText = ''
914973 Based on the guest platform Linux system:
915974···922981 };
923982 };
924983925925- virtualisation.useDefaultFilesystems =
926926- mkOption {
927927- type = types.bool;
928928- default = true;
929929- description = ''
930930- If enabled, the boot disk of the virtual machine will be
931931- formatted and mounted with the default filesystems for
932932- testing. Swap devices and LUKS will be disabled.
984984+ virtualisation.useDefaultFilesystems = mkOption {
985985+ type = types.bool;
986986+ default = true;
987987+ description = ''
988988+ If enabled, the boot disk of the virtual machine will be
989989+ formatted and mounted with the default filesystems for
990990+ testing. Swap devices and LUKS will be disabled.
933991934934- If disabled, a root filesystem has to be specified and
935935- formatted (for example in the initial ramdisk).
936936- '';
937937- };
992992+ If disabled, a root filesystem has to be specified and
993993+ formatted (for example in the initial ramdisk).
994994+ '';
995995+ };
938996939939- virtualisation.useSecureBoot =
940940- mkOption {
941941- type = types.bool;
942942- default = false;
943943- description = ''
944944- Enable Secure Boot support in the EFI firmware.
945945- '';
946946- };
997997+ virtualisation.useSecureBoot = mkOption {
998998+ type = types.bool;
999999+ default = false;
10001000+ description = ''
10011001+ Enable Secure Boot support in the EFI firmware.
10021002+ '';
10031003+ };
9471004948948- virtualisation.bios =
949949- mkOption {
950950- type = types.nullOr types.package;
951951- default = null;
952952- description = ''
953953- An alternate BIOS (such as `qboot`) with which to start the VM.
954954- Should contain a file named `bios.bin`.
955955- If `null`, QEMU's builtin SeaBIOS will be used.
956956- '';
957957- };
10051005+ virtualisation.bios = mkOption {
10061006+ type = types.nullOr types.package;
10071007+ default = null;
10081008+ description = ''
10091009+ An alternate BIOS (such as `qboot`) with which to start the VM.
10101010+ Should contain a file named `bios.bin`.
10111011+ If `null`, QEMU's builtin SeaBIOS will be used.
10121012+ '';
10131013+ };
9581014959959- virtualisation.useHostCerts =
960960- mkOption {
961961- type = types.bool;
962962- default = false;
963963- description = ''
964964- If enabled, when `NIX_SSL_CERT_FILE` is set on the host,
965965- pass the CA certificates from the host to the VM.
966966- '';
967967- };
10151015+ virtualisation.useHostCerts = mkOption {
10161016+ type = types.bool;
10171017+ default = false;
10181018+ description = ''
10191019+ If enabled, when `NIX_SSL_CERT_FILE` is set on the host,
10201020+ pass the CA certificates from the host to the VM.
10211021+ '';
10221022+ };
96810239691024 };
97010259711026 config = {
97210279731028 assertions =
974974- lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule:
975975- [
976976- { assertion = rule.from == "guest" -> rule.proto == "tcp";
977977- message =
978978- ''
10291029+ lib.concatLists (
10301030+ lib.flip lib.imap cfg.forwardPorts (
10311031+ i: rule: [
10321032+ {
10331033+ assertion = rule.from == "guest" -> rule.proto == "tcp";
10341034+ message = ''
9791035 Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto:
9801036 Guest forwarding supports only TCP connections.
9811037 '';
982982- }
983983- { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
984984- message =
985985- ''
10381038+ }
10391039+ {
10401040+ assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address;
10411041+ message = ''
9861042 Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address:
9871043 The address must be in the default VLAN (10.0.2.0/24).
9881044 '';
989989- }
990990- ])) ++ [
991991- { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047;
992992- message = ''
993993- virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
994994- '';
995995- }
996996- { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default;
997997- message =
998998- ''
999999- You changed the default of `virtualisation.directBoot.initrd` but you are not
10001000- using QEMU direct boot. This initrd will not be used in your current
10011001- boot configuration.
10451045+ }
10461046+ ]
10471047+ )
10481048+ )
10491049+ ++ [
10501050+ {
10511051+ assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047;
10521052+ message = ''
10531053+ virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
10541054+ '';
10551055+ }
10561056+ {
10571057+ assertion =
10581058+ cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default;
10591059+ message = ''
10601060+ You changed the default of `virtualisation.directBoot.initrd` but you are not
10611061+ using QEMU direct boot. This initrd will not be used in your current
10621062+ boot configuration.
1002106310031003- Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot.
10641064+ Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot.
1004106510051005- If you have a more advanced usecase, please open an issue or a pull request.
10061006- '';
10071007- }
10081008- {
10091009- assertion = cfg.installBootLoader -> config.system.switch.enable;
10101010- message = ''
10111011- `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work.
10121012- Please enable it in your configuration.
10131013- '';
10141014- }
10151015- ];
10661066+ If you have a more advanced usecase, please open an issue or a pull request.
10671067+ '';
10681068+ }
10691069+ {
10701070+ assertion = cfg.installBootLoader -> config.system.switch.enable;
10711071+ message = ''
10721072+ `system.switch.enable` must be enabled for `virtualisation.installBootLoader` to work.
10731073+ Please enable it in your configuration.
10741074+ '';
10751075+ }
10761076+ ];
1016107710171017- warnings =
10181018- optional (cfg.directBoot.enable && cfg.useBootLoader)
10191019- ''
10201020- You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering
10211021- `useBootLoader` useless. You might want to disable one of those options.
10221022- '';
10781078+ warnings = optional (cfg.directBoot.enable && cfg.useBootLoader) ''
10791079+ You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering
10801080+ `useBootLoader` useless. You might want to disable one of those options.
10811081+ '';
1023108210241083 # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install
10251084 # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs.
···10371096 # allow `system.build.toplevel' to be included. (If we had a direct
10381097 # reference to ${regInfo} here, then we would get a cyclic
10391098 # dependency.)
10401040- boot.postBootCommands = lib.mkIf config.nix.enable
10411041- ''
10421042- if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then
10431043- ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]}
10441044- fi
10451045- '';
10991099+ boot.postBootCommands = lib.mkIf config.nix.enable ''
11001100+ if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then
11011101+ ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]}
11021102+ fi
11031103+ '';
1046110410471105 boot.initrd.availableKernelModules =
10481106 optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
···1079113710801138 virtualisation.qemu.networkingOptions =
10811139 let
10821082- forwardingOptions = flip concatMapStrings cfg.forwardPorts
10831083- ({ proto, from, host, guest }:
10841084- if from == "host"
10851085- then "hostfwd=${proto}:${host.address}:${toString host.port}-" +
10861086- "${guest.address}:${toString guest.port},"
10871087- else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" +
10881088- "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
10891089- );
11401140+ forwardingOptions = flip concatMapStrings cfg.forwardPorts (
11411141+ {
11421142+ proto,
11431143+ from,
11441144+ host,
11451145+ guest,
11461146+ }:
11471147+ if from == "host" then
11481148+ "hostfwd=${proto}:${host.address}:${toString host.port}-"
11491149+ + "${guest.address}:${toString guest.port},"
11501150+ else
11511151+ "'guestfwd=${proto}:${guest.address}:${toString guest.port}-"
11521152+ + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}',"
11531153+ );
10901154 restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,";
10911155 in
10921156 [
···10991163 "-device virtio-keyboard"
11001164 ])
11011165 (mkIf pkgs.stdenv.hostPlatform.isx86 [
11021102- "-usb" "-device usb-tablet,bus=usb-bus.0"
11661166+ "-usb"
11671167+ "-device usb-tablet,bus=usb-bus.0"
11031168 ])
11041169 (mkIf pkgs.stdenv.hostPlatform.isAarch [
11051105- "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
11701170+ "-device virtio-gpu-pci"
11711171+ "-device usb-ehci,id=usb0"
11721172+ "-device usb-kbd"
11731173+ "-device usb-tablet"
11061174 ])
11071107- (let
11081108- alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
11091109- # Replace all non-alphanumeric characters with underscores
11101110- sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s);
11111111- in mkIf cfg.directBoot.enable [
11121112- "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}"
11131113- "-initrd ${cfg.directBoot.initrd}"
11141114- ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
11151115- ])
11751175+ (
11761176+ let
11771177+ alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9));
11781178+ # Replace all non-alphanumeric characters with underscores
11791179+ sanitizeShellIdent =
11801180+ s:
11811181+ concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s);
11821182+ in
11831183+ mkIf cfg.directBoot.enable [
11841184+ "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}"
11851185+ "-initrd ${cfg.directBoot.initrd}"
11861186+ ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
11871187+ ]
11881188+ )
11161189 (mkIf cfg.useEFIBoot [
11171190 "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
11181191 "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS"
···11291202 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0"
11301203 ])
11311204 (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [
11321132- "-machine" "q35,smm=on"
11331133- "-global" "driver=cfi.pflash01,property=secure,value=on"
12051205+ "-machine"
12061206+ "q35,smm=on"
12071207+ "-global"
12081208+ "driver=cfi.pflash01,property=secure,value=on"
11341209 ])
11351210 ];
1136121111371212 virtualisation.qemu.drives = mkMerge [
11381138- (mkIf (cfg.diskImage != null) [{
11391139- name = "root";
11401140- file = ''"$NIX_DISK_IMAGE"'';
11411141- driveExtraOpts.cache = "writeback";
11421142- driveExtraOpts.werror = "report";
11431143- deviceExtraOpts.bootindex = "1";
11441144- deviceExtraOpts.serial = rootDriveSerialAttr;
11451145- }])
11461146- (mkIf cfg.useNixStoreImage [{
11471147- name = "nix-store";
11481148- file = ''"$TMPDIR"/store.img'';
11491149- deviceExtraOpts.bootindex = "2";
11501150- driveExtraOpts.format = "raw";
11511151- }])
12131213+ (mkIf (cfg.diskImage != null) [
12141214+ {
12151215+ name = "root";
12161216+ file = ''"$NIX_DISK_IMAGE"'';
12171217+ driveExtraOpts.cache = "writeback";
12181218+ driveExtraOpts.werror = "report";
12191219+ deviceExtraOpts.bootindex = "1";
12201220+ deviceExtraOpts.serial = rootDriveSerialAttr;
12211221+ }
12221222+ ])
12231223+ (mkIf cfg.useNixStoreImage [
12241224+ {
12251225+ name = "nix-store";
12261226+ file = ''"$TMPDIR"/store.img'';
12271227+ deviceExtraOpts.bootindex = "2";
12281228+ driveExtraOpts.format = "raw";
12291229+ }
12301230+ ])
11521231 (imap0 (idx: _: {
11531232 file = "$(pwd)/empty${toString idx}.qcow2";
11541233 driveExtraOpts.werror = "report";
···11621241 # override by setting `virtualisation.fileSystems = lib.mkForce { };`.
11631242 fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems);
1164124311651165- virtualisation.fileSystems = let
11661166- mkSharedDir = tag: share:
11671167- {
12441244+ virtualisation.diskSizeAutoSupported = false;
12451245+12461246+ virtualisation.fileSystems =
12471247+ let
12481248+ mkSharedDir = tag: share: {
11681249 name = share.target;
11691250 value.device = tag;
11701251 value.fsType = "9p";
11711252 value.neededForBoot = true;
11721172- value.options =
11731173- [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" "x-systemd.requires=modprobe@9pnet_virtio.service" ]
11741174- ++ lib.optional (tag == "nix-store") "cache=loose";
11751175- };
11761176- in lib.mkMerge [
11771177- (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
11781178- {
11791179- "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then {
11801180- device = "tmpfs";
11811181- fsType = "tmpfs";
11821182- } else {
11831183- device = cfg.rootDevice;
11841184- fsType = "ext4";
11851185- });
11861186- "/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
11871187- device = "tmpfs";
11881188- fsType = "tmpfs";
11891189- neededForBoot = true;
11901190- # Sync with systemd's tmp.mount;
11911191- options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ];
12531253+ value.options = [
12541254+ "trans=virtio"
12551255+ "version=9p2000.L"
12561256+ "msize=${toString cfg.msize}"
12571257+ "x-systemd.requires=modprobe@9pnet_virtio.service"
12581258+ ] ++ lib.optional (tag == "nix-store") "cache=loose";
11921259 };
11931193- "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then {
11941194- overlay = {
11951195- lowerdir = [ "/nix/.ro-store" ];
11961196- upperdir = "/nix/.rw-store/upper";
11971197- workdir = "/nix/.rw-store/work";
12601260+ in
12611261+ lib.mkMerge [
12621262+ (lib.mapAttrs' mkSharedDir cfg.sharedDirectories)
12631263+ {
12641264+ "/" = lib.mkIf cfg.useDefaultFilesystems (
12651265+ if cfg.diskImage == null then
12661266+ {
12671267+ device = "tmpfs";
12681268+ fsType = "tmpfs";
12691269+ }
12701270+ else
12711271+ {
12721272+ device = cfg.rootDevice;
12731273+ fsType = "ext4";
12741274+ }
12751275+ );
12761276+ "/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
12771277+ device = "tmpfs";
12781278+ fsType = "tmpfs";
12791279+ neededForBoot = true;
12801280+ # Sync with systemd's tmp.mount;
12811281+ options = [
12821282+ "mode=1777"
12831283+ "strictatime"
12841284+ "nosuid"
12851285+ "nodev"
12861286+ "size=${toString config.boot.tmp.tmpfsSize}"
12871287+ ];
11981288 };
11991199- } else {
12001200- device = "/nix/.ro-store";
12011201- options = [ "bind" ];
12021202- });
12031203- "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
12041204- device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
12051205- fsType = "erofs";
12061206- neededForBoot = true;
12071207- options = [ "ro" ];
12081208- };
12091209- "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
12101210- fsType = "tmpfs";
12111211- options = [ "mode=0755" ];
12121212- neededForBoot = true;
12131213- };
12141214- "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
12151215- device = cfg.bootPartition;
12161216- fsType = "vfat";
12171217- };
12181218- }
12191219- ];
12891289+ "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (
12901290+ if cfg.writableStore then
12911291+ {
12921292+ overlay = {
12931293+ lowerdir = [ "/nix/.ro-store" ];
12941294+ upperdir = "/nix/.rw-store/upper";
12951295+ workdir = "/nix/.rw-store/work";
12961296+ };
12971297+ }
12981298+ else
12991299+ {
13001300+ device = "/nix/.ro-store";
13011301+ options = [ "bind" ];
13021302+ }
13031303+ );
13041304+ "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
13051305+ device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
13061306+ fsType = "erofs";
13071307+ neededForBoot = true;
13081308+ options = [ "ro" ];
13091309+ };
13101310+ "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) {
13111311+ fsType = "tmpfs";
13121312+ options = [ "mode=0755" ];
13131313+ neededForBoot = true;
13141314+ };
13151315+ "${config.boot.loader.efi.efiSysMountPoint}" =
13161316+ lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null)
13171317+ {
13181318+ device = cfg.bootPartition;
13191319+ fsType = "vfat";
13201320+ };
13211321+ }
13221322+ ];
1220132312211324 swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
12221222- boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {};
13251325+ boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) { };
1223132612241327 # Don't run ntpd in the guest. It should get the correct time from KVM.
12251328 services.timesyncd.enable = false;
1226132912271330 services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
1228133112291229- system.build.vm = hostPkgs.runCommand "nixos-vm" {
12301230- preferLocalBuild = true;
12311231- meta.mainProgram = "run-${config.system.name}-vm";
12321232- }
12331233- ''
12341234- mkdir -p $out/bin
12351235- ln -s ${config.system.build.toplevel} $out/system
12361236- ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
12371237- '';
13321332+ system.build.vm =
13331333+ hostPkgs.runCommand "nixos-vm"
13341334+ {
13351335+ preferLocalBuild = true;
13361336+ meta.mainProgram = "run-${config.system.name}-vm";
13371337+ }
13381338+ ''
13391339+ mkdir -p $out/bin
13401340+ ln -s ${config.system.build.toplevel} $out/system
13411341+ ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm
13421342+ '';
1238134312391344 # When building a regular system configuration, override whatever
12401345 # video driver the host uses.
12411346 services.xserver.videoDrivers = mkVMOverride [ "modesetting" ];
12421347 services.xserver.defaultDepth = mkVMOverride 0;
12431348 services.xserver.resolutions = mkVMOverride [ cfg.resolution ];
12441244- services.xserver.monitorSection =
12451245- ''
12461246- # Set a higher refresh rate so that resolutions > 800x600 work.
12471247- HorizSync 30-140
12481248- VertRefresh 50-160
12491249- '';
13491349+ services.xserver.monitorSection = ''
13501350+ # Set a higher refresh rate so that resolutions > 800x600 work.
13511351+ HorizSync 30-140
13521352+ VertRefresh 50-160
13531353+ '';
1250135412511355 # Wireless won't work in the VM.
12521356 networking.wireless.enable = mkVMOverride false;
···1257136112581362 networking.usePredictableInterfaceNames = false;
1259136312601260- system.requiredKernelConfig = with config.lib.kernelConfig;
12611261- [ (isEnabled "VIRTIO_BLK")
13641364+ system.requiredKernelConfig =
13651365+ with config.lib.kernelConfig;
13661366+ [
13671367+ (isEnabled "VIRTIO_BLK")
12621368 (isEnabled "VIRTIO_PCI")
12631369 (isEnabled "VIRTIO_NET")
12641370 (isEnabled "EXT4_FS")
···12701376 (isYes "NET_CORE")
12711377 (isYes "INET")
12721378 (isYes "NETWORK_FILESYSTEMS")
12731273- ] ++ optionals (!cfg.graphics) [
13791379+ ]
13801380+ ++ optionals (!cfg.graphics) [
12741381 (isYes "SERIAL_8250_CONSOLE")
12751382 (isYes "SERIAL_8250")
12761276- ] ++ optionals (cfg.writableStore) [
13831383+ ]
13841384+ ++ optionals (cfg.writableStore) [
12771385 (isEnabled "OVERLAY_FS")
12781386 ];
12791387