···
drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives);
85
-
# Creates a device name from a 1-based a numerical index, e.g.
86
-
# * `driveDeviceName 1` -> `/dev/vda`
87
-
# * `driveDeviceName 2` -> `/dev/vdb`
88
-
driveDeviceName = idx:
89
-
let letter = elemAt lowerChars (idx - 1);
90
-
in if cfg.qemu.diskInterface == "scsi" then
95
-
lookupDriveDeviceName = driveName: driveList:
96
-
(findSingle (drive: drive.name == driveName)
97
-
(throw "Drive ${driveName} not found")
98
-
(throw "Multiple drives named ${driveName}") driveList).device;
101
-
imap1 (idx: drive: drive // { device = driveDeviceName idx; });
# Shell script to start the VM.
···
93
+
# Create an empty ext4 filesystem image. A filesystem image does not
94
+
# contain a partition table but just a filesystem.
95
+
createEmptyFilesystemImage() {
98
+
local temp=$(mktemp)
99
+
${qemu}/bin/qemu-img create -f raw "$temp" "$size"
100
+
${pkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp"
101
+
${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name"
NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
echo "Disk image do not exist, creating the virtualisation disk image..."
116
-
# If we are using a bootloader and default filesystems layout.
117
-
# We have to reuse the system image layout as a backing image format (CoW)
118
-
# So we can write on the top of it.
120
-
# If we are not using the default FS layout, potentially, we are interested into
121
-
# performing operations in postDeviceCommands or at early boot on the raw device.
122
-
# We can still boot through QEMU direct kernel boot feature.
110
+
${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then ''
111
+
# Create a writable qcow2 image using the systemImage as a backing
124
-
# CoW prevent size to be attributed to an image.
125
-
# FIXME: raise this issue to upstream.
126
-
${qemu}/bin/qemu-img create \
127
-
${concatStringsSep " \\\n" ([ "-f qcow2" ]
128
-
++ optional (cfg.useBootLoader && cfg.useDefaultFilesystems) "-F qcow2 -b ${systemImage}/nixos.qcow2"
129
-
++ optional (!(cfg.useBootLoader && cfg.useDefaultFilesystems)) "-o size=${toString config.virtualisation.diskSize}M"
130
-
++ [ ''"$NIX_DISK_IMAGE"'' ])}
114
+
# CoW prevent size to be attributed to an image.
115
+
# FIXME: raise this issue to upstream.
116
+
${qemu}/bin/qemu-img create \
118
+
-b ${systemImage}/nixos.qcow2 \
121
+
'' else if cfg.useDefaultFilesystems then ''
122
+
createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
124
+
# Create an empty disk image without a filesystem.
125
+
${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M"
echo "Virtualisation disk image created."
···
${pkgs.erofs-utils}/bin/mkfs.erofs \
148
+
-L ${nixStoreFilesystemLabel} \
-U eb176051-bd15-49b7-9e6b-462e0b467019 \
···
regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
219
+
# Use well-defined and persistent filesystem labels to identify block devices.
220
+
rootFilesystemLabel = "nixos";
221
+
espFilesystemLabel = "ESP"; # Hard-coded by make-disk-image.nix
222
+
nixStoreFilesystemLabel = "nix-store";
224
+
# The root drive is a raw disk which does not necessarily contain a
225
+
# filesystem or partition table. It thus cannot be identified via the typical
226
+
# persistent naming schemes (e.g. /dev/disk/by-{label, uuid, partlabel,
227
+
# partuuid}. Instead, supply a well-defined and persistent serial attribute
228
+
# via QEMU. Inside the running system, the disk can then be identified via
229
+
# the /dev/disk/by-id scheme.
230
+
rootDriveSerialAttr = "root";
# System image is akin to a complete NixOS install with
# a boot partition and root partition.
systemImage = import ../../lib/make-disk-image.nix {
···
additionalPaths = [ regInfo ];
239
+
label = rootFilesystemLabel;
partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; };
# Bootloader should be installed on the system image only if we are booting through bootloaders.
# Though, if a user is not using our default filesystems, it is possible to not have any ESP
···
additionalPaths = [ regInfo ];
262
+
label = nixStoreFilesystemLabel;
partitionTableType = "none";
installBootLoader = false;
···
258
-
bootConfiguration =
259
-
if cfg.useDefaultFilesystems
261
-
if cfg.useBootLoader
263
-
if cfg.useEFIBoot then "efi_bootloading_with_default_fs"
264
-
else "legacy_bootloading_with_default_fs"
266
-
if cfg.directBoot.enable then "direct_boot_with_default_fs"
270
-
suggestedRootDevice = {
271
-
"efi_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}2";
272
-
"legacy_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}1";
273
-
"direct_boot_with_default_fs" = cfg.bootLoaderDevice;
274
-
# This will enforce a NixOS module type checking error
275
-
# to ask explicitly the user to set a rootDevice.
276
-
# As it will look like `rootDevice = lib.mkDefault null;` after
277
-
# all "computations".
279
-
}.${bootConfiguration};
···
virtualisation.bootLoaderDevice =
346
-
default = lookupDriveDeviceName "root" cfg.qemu.drives;
347
-
defaultText = literalExpression ''lookupDriveDeviceName "root" cfg.qemu.drives'';
348
-
example = "/dev/vda";
337
+
default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}";
338
+
defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}'';
339
+
example = "/dev/disk/by-id/virtio-boot-loader-device";
351
-
The disk to be used for the boot filesystem.
352
-
By default, it is the same disk as the root filesystem.
342
+
The path (inside th VM) to the device to boot from when legacy booting.
virtualisation.bootPartition =
type = types.nullOr types.path;
359
-
default = if cfg.useEFIBoot then "${cfg.bootLoaderDevice}1" else null;
360
-
defaultText = literalExpression ''if cfg.useEFIBoot then "''${cfg.bootLoaderDevice}1" else null'';
361
-
example = "/dev/vda1";
349
+
default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null;
350
+
defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null'';
351
+
example = "/dev/disk/by-label/esp";
364
-
The boot partition to be used to mount /boot filesystem.
365
-
In legacy boots, this should be null.
366
-
By default, in EFI boot, it is the first partition of the boot device.
354
+
The path (inside the VM) to the device containing the EFI System Partition (ESP).
356
+
If you are *not* booting from a UEFI firmware, this value is, by
357
+
default, `null`. The ESP is mounted under `/boot`.
virtualisation.rootDevice =
type = types.nullOr types.path;
373
-
example = "/dev/vda2";
364
+
default = "/dev/disk/by-label/${rootFilesystemLabel}";
365
+
defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}'';
366
+
example = "/dev/disk/by-label/nixos";
376
-
The disk or partition to be used for the root filesystem.
377
-
By default (read the source code for more details):
379
-
- under EFI with a bootloader: 2nd partition of the boot disk
380
-
- in legacy boot with a bootloader: 1st partition of the boot disk
381
-
- in direct boot (i.e. without a bootloader): whole disk
383
-
In case you are not using a default boot device or a default filesystem, you have to set explicitly your root device.
369
+
The path (inside the VM) to the device containing the root filesystem.
···
type = types.listOf (types.submodule driveOpts);
description = lib.mdDoc "Drives passed to qemu.";
714
-
apply = addDeviceNames;
···
# FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint?
boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice);
boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
978
-
virtualisation.rootDevice = mkDefault suggestedRootDevice;
boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ];
boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false);
984
-
boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
986
-
# We need mke2fs in the initrd.
987
-
copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs
990
-
boot.initrd.postDeviceCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
992
-
# If the disk image appears to be empty, run mke2fs to
994
-
FSTYPE=$(blkid -o value -s TYPE ${cfg.rootDevice} || true)
995
-
PARTTYPE=$(blkid -o value -s PTTYPE ${cfg.rootDevice} || true)
996
-
if test -z "$FSTYPE" -a -z "$PARTTYPE"; then
997
-
mke2fs -t ext4 ${cfg.rootDevice}
boot.initrd.postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
# Mark this as a NixOS machine.
···
driveExtraOpts.cache = "writeback";
driveExtraOpts.werror = "report";
deviceExtraOpts.bootindex = "1";
1082
+
deviceExtraOpts.serial = rootDriveSerialAttr;
(mkIf cfg.useNixStoreImage [{
···
1157
-
autoFormat = true;
"/tmp" = lib.mkIf config.boot.tmp.useTmpfs {
···
options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ];
"/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage {
1167
-
device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
1134
+
device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
···
"/boot" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
1177
-
device = cfg.bootPartition; # 1 for e.g. `vda1`, as created in `systemImage`
1144
+
device = cfg.bootPartition;
noCheck = true; # fsck fails on a r/o filesystem