grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / lib / make-disk-image.nix
bloba943edb7b574eb82d4a0542e1c81b07eec33f91c
1 /* Technical details
3 `make-disk-image` has a bit of magic to minimize the amount of work to do in a virtual machine.
5 It relies on the [LKL (Linux Kernel Library) project](https://github.com/lkl/linux) which provides Linux kernel as userspace library.
7 The Nix-store only image only need to run LKL tools to produce an image and will never spawn a virtual machine, whereas full images will always require a virtual machine, but also use LKL.
9 ### Image preparation phase
11 Image preparation phase will produce the initial image layout in a folder:
13 - devise a root folder based on `$PWD`
14 - prepare the contents by copying and restoring ACLs in this root folder
15 - load in the Nix store database all additional paths computed by `pkgs.closureInfo` in a temporary Nix store
16 - run `nixos-install` in a temporary folder
17 - transfer from the temporary store the additional paths registered to the installed NixOS
18 - compute the size of the disk image based on the apparent size of the root folder
19 - partition the disk image using the corresponding script according to the partition table type
20 - format the partitions if needed
21 - use `cptofs` (LKL tool) to copy the root folder inside the disk image
23 At this step, the disk image already contains the Nix store, it now only needs to be converted to the desired format to be used.
25 ### Image conversion phase
27 Using `qemu-img`, the disk image is converted from a raw format to the desired format: qcow2(-compressed), vdi, vpc.
29 ### Image Partitioning
31 #### `none`
33 No partition table layout is written. The image is a bare filesystem image.
35 #### `legacy`
37 The image is partitioned using MBR. There is one primary ext4 partition starting at 1 MiB that fills the rest of the disk image.
39 This partition layout is unsuitable for UEFI.
41 #### `legacy+gpt`
43 This partition table type uses GPT and:
45 - create a "no filesystem" partition from 1MiB to 2MiB ;
46 - set `bios_grub` flag on this "no filesystem" partition, which marks it as a [GRUB BIOS partition](https://www.gnu.org/software/parted/manual/html_node/set.html) ;
47 - create a primary ext4 partition starting at 2MiB and extending to the full disk image ;
48 - perform optimal alignments checks on each partition
50 This partition layout is unsuitable for UEFI boot, because it has no ESP (EFI System Partition) partition. It can work with CSM (Compatibility Support Module) which emulates legacy (BIOS) boot for UEFI.
52 #### `efi`
54 This partition table type uses GPT and:
56 - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
57 - creates an primary ext4 partition starting after the boot partition and extending to the full disk image
59 #### `efixbootldr`
61 This partition table type uses GPT and:
63 - creates an FAT32 ESP partition from 8MiB to 100MiB, set it bootable ;
64 - creates an FAT32 BOOT partition from 100MiB to specified `bootSize` parameter (256MiB by default), set `bls_boot` flag ;
65 - creates an primary ext4 partition starting after the boot partition and extending to the full disk image
67 #### `hybrid`
69 This partition table type uses GPT and:
71 - creates a "no filesystem" partition from 0 to 1MiB, set `bios_grub` flag on it ;
72 - creates an FAT32 ESP partition from 8MiB to specified `bootSize` parameter (256MiB by default), set it bootable ;
73 - creates a primary ext4 partition starting after the boot one and extending to the full disk image
75 This partition could be booted by a BIOS able to understand GPT layouts and recognizing the MBR at the start.
77 ### How to run determinism analysis on results?
79 Build your derivation with `--check` to rebuild it and verify it is the same.
81 If it fails, you will be left with two folders with one having `.check`.
83 You can use `diffoscope` to see the differences between the folders.
85 However, `diffoscope` is currently not able to diff two QCOW2 filesystems, thus, it is advised to use raw format.
87 Even if you use raw disks, `diffoscope` cannot diff the partition table and partitions recursively.
89 To solve this, you can run `fdisk -l $image` and generate `dd if=$image of=$image-p$i.raw skip=$start count=$sectors` for each `(start, sectors)` listed in the `fdisk` output. Now, you will have each partition as a separate file and you can compare them in pairs.
91 { pkgs
92 , lib
94 , # The NixOS configuration to be installed onto the disk image.
95   config
97 , # The size of the disk, in megabytes.
98   # if "auto" size is calculated based on the contents copied to it and
99   #   additionalSpace is taken into account.
100   diskSize ? "auto"
102 , # additional disk space to be added to the image if diskSize "auto"
103   # is used
104   additionalSpace ? "512M"
106 , # size of the boot partition, is only used if partitionTableType is
107   # either "efi" or "hybrid"
108   # This will be undersized slightly, as this is actually the offset of
109   # the end of the partition. Generally it will be 1MiB smaller.
110   bootSize ? "256M"
112 , # The files and directories to be placed in the target file system.
113   # This is a list of attribute sets {source, target, mode, user, group} where
114   # `source' is the file system object (regular file or directory) to be
115   # grafted in the file system at path `target', `mode' is a string containing
116   # the permissions that will be set (ex. "755"), `user' and `group' are the
117   # user and group name that will be set as owner of the files.
118   # `mode', `user', and `group' are optional.
119   # When setting one of `user' or `group', the other needs to be set too.
120   contents ? []
122 , # Type of partition table to use; described in the `Image Partitioning` section above.
123   partitionTableType ? "legacy"
125 , # Whether to invoke `switch-to-configuration boot` during image creation
126   installBootLoader ? true
128 , # Whether to output have EFIVARS available in $out/efi-vars.fd and use it during disk creation
129   touchEFIVars ? false
131 , # OVMF firmware derivation
132   OVMF ? pkgs.OVMF.fd
134 , # EFI firmware
135   efiFirmware ? OVMF.firmware
137 , # EFI variables
138   efiVariables ? OVMF.variables
140 , # The root file system type.
141   fsType ? "ext4"
143 , # Filesystem label
144   label ? if onlyNixStore then "nix-store" else "nixos"
146 , # The initial NixOS configuration file to be copied to
147   # /etc/nixos/configuration.nix.
148   configFile ? null
150 , # Shell code executed after the VM has finished.
151   postVM ? ""
153 , # Guest memory size
154   memSize ? 1024
156 , # Copy the contents of the Nix store to the root of the image and
157   # skip further setup. Incompatible with `contents`,
158   # `installBootLoader` and `configFile`.
159   onlyNixStore ? false
161 , name ? "nixos-disk-image"
163 , # Disk image format, one of qcow2, qcow2-compressed, vdi, vpc, raw.
164   format ? "raw"
166   # Whether to fix:
167   #   - GPT Disk Unique Identifier (diskGUID)
168   #   - GPT Partition Unique Identifier: depends on the layout, root partition UUID can be controlled through `rootGPUID` option
169   #   - GPT Partition Type Identifier: fixed according to the layout, e.g. ESP partition, etc. through `parted` invocation.
170   #   - Filesystem Unique Identifier when fsType = ext4 for *root partition*.
171   # BIOS/MBR support is "best effort" at the moment.
172   # Boot partitions may not be deterministic.
173   # Also, to fix last time checked of the ext4 partition if fsType = ext4.
174 , deterministic ? true
176   # GPT Partition Unique Identifier for root partition.
177 , rootGPUID ? "F222513B-DED1-49FA-B591-20CE86A2FE7F"
178   # When fsType = ext4, this is the root Filesystem Unique Identifier.
179   # TODO: support other filesystems someday.
180 , rootFSUID ? (if fsType == "ext4" then rootGPUID else null)
182 , # Whether a nix channel based on the current source tree should be
183   # made available inside the image. Useful for interactive use of nix
184   # utils, but changes the hash of the image when the sources are
185   # updated.
186   copyChannel ? true
188 , # Additional store paths to copy to the image's store.
189   additionalPaths ? []
192 assert (lib.assertOneOf "partitionTableType" partitionTableType [ "legacy" "legacy+gpt" "efi" "efixbootldr" "hybrid" "none" ]);
193 assert (lib.assertMsg (fsType == "ext4" && deterministic -> rootFSUID != null) "In deterministic mode with a ext4 partition, rootFSUID must be non-null, by default, it is equal to rootGPUID.");
194   # We use -E offset=X below, which is only supported by e2fsprogs
195 assert (lib.assertMsg (partitionTableType != "none" -> fsType == "ext4") "to produce a partition table, we need to use -E offset flag which is support only for fsType = ext4");
196 assert (lib.assertMsg (touchEFIVars -> partitionTableType == "hybrid" || partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "legacy+gpt") "EFI variables can be used only with a partition table of type: hybrid, efi, efixbootldr, or legacy+gpt.");
197   # If only Nix store image, then: contents must be empty, configFile must be unset, and we should no install bootloader.
198 assert (lib.assertMsg (onlyNixStore -> contents == [] && configFile == null && !installBootLoader) "In a only Nix store image, the contents must be empty, no configuration must be provided and no bootloader should be installed.");
199 # Either both or none of {user,group} need to be set
200 assert (lib.assertMsg (lib.all
201          (attrs: ((attrs.user  or null) == null)
202               == ((attrs.group or null) == null))
203         contents) "Contents of the disk image should set none of {user, group} or both at the same time.");
205 let format' = format; in let
207   format = if format' == "qcow2-compressed" then "qcow2" else format';
209   compress = lib.optionalString (format' == "qcow2-compressed") "-c";
211   filename = "nixos." + {
212     qcow2 = "qcow2";
213     vdi   = "vdi";
214     vpc   = "vhd";
215     raw   = "img";
216   }.${format} or format;
218   rootPartition = { # switch-case
219     legacy = "1";
220     "legacy+gpt" = "2";
221     efi = "2";
222     efixbootldr = "3";
223     hybrid = "3";
224   }.${partitionTableType};
226   partitionDiskScript = { # switch-case
227     legacy = ''
228       parted --script $diskImage -- \
229         mklabel msdos \
230         mkpart primary ext4 1MiB -1
231     '';
232     "legacy+gpt" = ''
233       parted --script $diskImage -- \
234         mklabel gpt \
235         mkpart no-fs 1MB 2MB \
236         set 1 bios_grub on \
237         align-check optimal 1 \
238         mkpart primary ext4 2MB -1 \
239         align-check optimal 2 \
240         print
241       ${lib.optionalString deterministic ''
242           sgdisk \
243           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
244           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
245           --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
246           --partition-guid=3:${rootGPUID} \
247           $diskImage
248       ''}
249     '';
250     efi = ''
251       parted --script $diskImage -- \
252         mklabel gpt \
253         mkpart ESP fat32 8MiB ${bootSize} \
254         set 1 boot on \
255         mkpart primary ext4 ${bootSize} -1
256       ${lib.optionalString deterministic ''
257           sgdisk \
258           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
259           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
260           --partition-guid=2:${rootGPUID} \
261           $diskImage
262       ''}
263     '';
264     efixbootldr = ''
265       parted --script $diskImage -- \
266         mklabel gpt \
267         mkpart ESP fat32 8MiB 100MiB \
268         set 1 boot on \
269         mkpart BOOT fat32 100MiB ${bootSize} \
270         set 2 bls_boot on \
271         mkpart ROOT ext4 ${bootSize} -1
272       ${lib.optionalString deterministic ''
273           sgdisk \
274           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
275           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC  \
276           --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F  \
277           --partition-guid=3:${rootGPUID} \
278           $diskImage
279       ''}
280     '';
281     hybrid = ''
282       parted --script $diskImage -- \
283         mklabel gpt \
284         mkpart ESP fat32 8MiB ${bootSize} \
285         set 1 boot on \
286         mkpart no-fs 0 1024KiB \
287         set 2 bios_grub on \
288         mkpart primary ext4 ${bootSize} -1
289       ${lib.optionalString deterministic ''
290           sgdisk \
291           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
292           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
293           --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
294           --partition-guid=3:${rootGPUID} \
295           $diskImage
296       ''}
297     '';
298     none = "";
299   }.${partitionTableType};
301   useEFIBoot = touchEFIVars;
303   nixpkgs = lib.cleanSource pkgs.path;
305   # FIXME: merge with channel.nix / make-channel.nix.
306   channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
307     mkdir -p $out
308     cp -prd ${nixpkgs.outPath} $out/nixos
309     chmod -R u+w $out/nixos
310     if [ ! -e $out/nixos/nixpkgs ]; then
311       ln -s . $out/nixos/nixpkgs
312     fi
313     rm -rf $out/nixos/.git
314     echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
315   '';
317   binPath = lib.makeBinPath (with pkgs; [
318       rsync
319       util-linux
320       parted
321       e2fsprogs
322       lkl
323       config.system.build.nixos-install
324       nixos-enter
325       nix
326       systemdMinimal
327     ]
328     ++ lib.optional deterministic gptfdisk
329     ++ stdenv.initialPath);
331   # I'm preserving the line below because I'm going to search for it across nixpkgs to consolidate
332   # image building logic. The comment right below this now appears in 4 different places in nixpkgs :)
333   # !!! should use XML.
334   sources = map (x: x.source) contents;
335   targets = map (x: x.target) contents;
336   modes   = map (x: x.mode  or "''") contents;
337   users   = map (x: x.user  or "''") contents;
338   groups  = map (x: x.group or "''") contents;
340   basePaths = [ config.system.build.toplevel ]
341     ++ lib.optional copyChannel channelSources;
343   additionalPaths' = lib.subtractLists basePaths additionalPaths;
345   closureInfo = pkgs.closureInfo {
346     rootPaths = basePaths ++ additionalPaths';
347   };
349   blockSize = toString (4 * 1024); # ext4fs block size (not block device sector size)
351   prepareImage = ''
352     export PATH=${binPath}
354     # Yes, mkfs.ext4 takes different units in different contexts. Fun.
355     sectorsToKilobytes() {
356       echo $(( ( "$1" * 512 ) / 1024 ))
357     }
359     sectorsToBytes() {
360       echo $(( "$1" * 512  ))
361     }
363     # Given lines of numbers, adds them together
364     sum_lines() {
365       local acc=0
366       while read -r number; do
367         acc=$((acc+number))
368       done
369       echo "$acc"
370     }
372     mebibyte=$(( 1024 * 1024 ))
374     # Approximative percentage of reserved space in an ext4 fs over 512MiB.
375     # 0.05208587646484375
376     #  Ã— 1000, integer part: 52
377     compute_fudge() {
378       echo $(( $1 * 52 / 1000 ))
379     }
381     mkdir $out
383     root="$PWD/root"
384     mkdir -p $root
386     # Copy arbitrary other files into the image
387     # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
388     # https://github.com/NixOS/nixpkgs/issues/23052.
389     set -f
390     sources_=(${lib.concatStringsSep " " sources})
391     targets_=(${lib.concatStringsSep " " targets})
392     modes_=(${lib.concatStringsSep " " modes})
393     set +f
395     for ((i = 0; i < ''${#targets_[@]}; i++)); do
396       source="''${sources_[$i]}"
397       target="''${targets_[$i]}"
398       mode="''${modes_[$i]}"
400       if [ -n "$mode" ]; then
401         rsync_chmod_flags="--chmod=$mode"
402       else
403         rsync_chmod_flags=""
404       fi
405       # Unfortunately cptofs only supports modes, not ownership, so we can't use
406       # rsync's --chown option. Instead, we change the ownerships in the
407       # VM script with chown.
408       rsync_flags="-a --no-o --no-g $rsync_chmod_flags"
409       if [[ "$source" =~ '*' ]]; then
410         # If the source name contains '*', perform globbing.
411         mkdir -p $root/$target
412         for fn in $source; do
413           rsync $rsync_flags "$fn" $root/$target/
414         done
415       else
416         mkdir -p $root/$(dirname $target)
417         if [ -e $root/$target ]; then
418           echo "duplicate entry $target -> $source"
419           exit 1
420         elif [ -d $source ]; then
421           # Append a slash to the end of source to get rsync to copy the
422           # directory _to_ the target instead of _inside_ the target.
423           # (See `man rsync`'s note on a trailing slash.)
424           rsync $rsync_flags $source/ $root/$target
425         else
426           rsync $rsync_flags $source $root/$target
427         fi
428       fi
429     done
431     export HOME=$TMPDIR
433     # Provide a Nix database so that nixos-install can copy closures.
434     export NIX_STATE_DIR=$TMPDIR/state
435     nix-store --load-db < ${closureInfo}/registration
437     chmod 755 "$TMPDIR"
438     echo "running nixos-install..."
439     nixos-install --root $root --no-bootloader --no-root-passwd \
440       --system ${config.system.build.toplevel} \
441       ${if copyChannel then "--channel ${channelSources}" else "--no-channel-copy"} \
442       --substituters ""
444     ${lib.optionalString (additionalPaths' != []) ''
445       nix --extra-experimental-features nix-command copy --to $root --no-check-sigs ${lib.concatStringsSep " " additionalPaths'}
446     ''}
448     diskImage=nixos.raw
450     ${if diskSize == "auto" then ''
451       ${if partitionTableType == "efi" || partitionTableType == "efixbootldr" || partitionTableType == "hybrid" then ''
452         # Add the GPT at the end
453         gptSpace=$(( 512 * 34 * 1 ))
454         # Normally we'd need to account for alignment and things, if bootSize
455         # represented the actual size of the boot partition. But it instead
456         # represents the offset at which it ends.
457         # So we know bootSize is the reserved space in front of the partition.
458         reservedSpace=$(( gptSpace + $(numfmt --from=iec '${bootSize}') ))
459       '' else if partitionTableType == "legacy+gpt" then ''
460         # Add the GPT at the end
461         gptSpace=$(( 512 * 34 * 1 ))
462         # And include the bios_grub partition; the ext4 partition starts at 2MB exactly.
463         reservedSpace=$(( gptSpace + 2 * mebibyte ))
464       '' else if partitionTableType == "legacy" then ''
465         # Add the 1MiB aligned reserved space (includes MBR)
466         reservedSpace=$(( mebibyte ))
467       '' else ''
468         reservedSpace=0
469       ''}
470       additionalSpace=$(( $(numfmt --from=iec '${additionalSpace}') + reservedSpace ))
472       # Compute required space in filesystem blocks
473       diskUsage=$(find . ! -type d -print0 | du --files0-from=- --apparent-size --block-size "${blockSize}" | cut -f1 | sum_lines)
474       # Each inode takes space!
475       numInodes=$(find . | wc -l)
476       # Convert to bytes, inodes take two blocks each!
477       diskUsage=$(( (diskUsage + 2 * numInodes) * ${blockSize} ))
478       # Then increase the required space to account for the reserved blocks.
479       fudge=$(compute_fudge $diskUsage)
480       requiredFilesystemSpace=$(( diskUsage + fudge ))
482       diskSize=$(( requiredFilesystemSpace  + additionalSpace ))
484       # Round up to the nearest mebibyte.
485       # This ensures whole 512 bytes sector sizes in the disk image
486       # and helps towards aligning partitions optimally.
487       if (( diskSize % mebibyte )); then
488         diskSize=$(( ( diskSize / mebibyte + 1) * mebibyte ))
489       fi
491       truncate -s "$diskSize" $diskImage
493       printf "Automatic disk size...\n"
494       printf "  Closure space use: %d bytes\n" $diskUsage
495       printf "  fudge: %d bytes\n" $fudge
496       printf "  Filesystem size needed: %d bytes\n" $requiredFilesystemSpace
497       printf "  Additional space: %d bytes\n" $additionalSpace
498       printf "  Disk image size: %d bytes\n" $diskSize
499     '' else ''
500       truncate -s ${toString diskSize}M $diskImage
501     ''}
503     ${partitionDiskScript}
505     ${if partitionTableType != "none" then ''
506       # Get start & length of the root partition in sectors to $START and $SECTORS.
507       eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
509       mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
510     '' else ''
511       mkfs.${fsType} -b ${blockSize} -F -L ${label} $diskImage
512     ''}
514     echo "copying staging root to image..."
515     cptofs -p ${lib.optionalString (partitionTableType != "none") "-P ${rootPartition}"} \
516            -t ${fsType} \
517            -i $diskImage \
518            $root${lib.optionalString onlyNixStore builtins.storeDir}/* / ||
519       (echo >&2 "ERROR: cptofs failed. diskSize might be too small for closure."; exit 1)
520   '';
522   moveOrConvertImage = ''
523     ${if format == "raw" then ''
524       mv $diskImage $out/${filename}
525     '' else ''
526       ${pkgs.qemu-utils}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
527     ''}
528     diskImage=$out/${filename}
529   '';
531   createEFIVars = ''
532     efiVars=$out/efi-vars.fd
533     cp ${efiVariables} $efiVars
534     chmod 0644 $efiVars
535   '';
537   createHydraBuildProducts = ''
538     mkdir -p $out/nix-support
539     echo "file ${format}-image $out/${filename}" >> $out/nix-support/hydra-build-products
540   '';
542   buildImage = pkgs.vmTools.runInLinuxVM (
543     pkgs.runCommand name {
544       preVM = prepareImage + lib.optionalString touchEFIVars createEFIVars;
545       buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ];
546       postVM = moveOrConvertImage + createHydraBuildProducts + postVM;
547       QEMU_OPTS =
548         lib.concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
549         ++ lib.optionals touchEFIVars [
550           "-drive if=pflash,format=raw,unit=1,file=$efiVars"
551         ] ++ lib.optionals (OVMF.systemManagementModeRequired or false) [
552           "-machine" "q35,smm=on"
553           "-global" "driver=cfi.pflash01,property=secure,value=on"
554         ]
555       );
556       inherit memSize;
557     } ''
558       export PATH=${binPath}:$PATH
560       rootDisk=${if partitionTableType != "none" then "/dev/vda${rootPartition}" else "/dev/vda"}
562       # It is necessary to set root filesystem unique identifier in advance, otherwise
563       # bootloader might get the wrong one and fail to boot.
564       # At the end, we reset again because we want deterministic timestamps.
565       ${lib.optionalString (fsType == "ext4" && deterministic) ''
566         tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
567       ''}
568       # make systemd-boot find ESP without udev
569       mkdir /dev/block
570       ln -s /dev/vda1 /dev/block/254:1
572       mountPoint=/mnt
573       mkdir $mountPoint
574       mount $rootDisk $mountPoint
576       # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
577       # '-E offset=X' option, so we can't do this outside the VM.
578       ${lib.optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") ''
579         mkdir -p /mnt/boot
580         mkfs.vfat -n ESP /dev/vda1
581         mount /dev/vda1 /mnt/boot
583         ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
584       ''}
585       ${lib.optionalString (partitionTableType == "efixbootldr") ''
586         mkdir -p /mnt/{boot,efi}
587         mkfs.vfat -n ESP /dev/vda1
588         mkfs.vfat -n BOOT /dev/vda2
589         mount /dev/vda1 /mnt/efi
590         mount /dev/vda2 /mnt/boot
592         ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
593       ''}
595       # Install a configuration.nix
596       mkdir -p /mnt/etc/nixos
597       ${lib.optionalString (configFile != null) ''
598         cp ${configFile} /mnt/etc/nixos/configuration.nix
599       ''}
601       ${lib.optionalString installBootLoader ''
602         # In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
603         # Use this option to create a symlink from vda to any arbitrary device you want.
604         ${lib.optionalString (config.boot.loader.grub.enable) (lib.concatMapStringsSep " " (device:
605           lib.optionalString (device != "/dev/vda") ''
606             mkdir -p "$(dirname ${device})"
607             ln -s /dev/vda ${device}
608           '') config.boot.loader.grub.devices)}
610         # Set up core system link, bootloader (sd-boot, GRUB, uboot, etc.), etc.
612         # NOTE: systemd-boot-builder.py calls nix-env --list-generations which
613         # clobbers $HOME/.nix-defexpr/channels/nixos This would cause a  folder
614         # /homeless-shelter to show up in the final image which  in turn breaks
615         # nix builds in the target image if sandboxing is turned off (through
616         # __noChroot for example).
617         export HOME=$TMPDIR
618         NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root $mountPoint -- /nix/var/nix/profiles/system/bin/switch-to-configuration boot
620         # The above scripts will generate a random machine-id and we don't want to bake a single ID into all our images
621         rm -f $mountPoint/etc/machine-id
622       ''}
624       # Set the ownerships of the contents. The modes are set in preVM.
625       # No globbing on targets, so no need to set -f
626       targets_=(${lib.concatStringsSep " " targets})
627       users_=(${lib.concatStringsSep " " users})
628       groups_=(${lib.concatStringsSep " " groups})
629       for ((i = 0; i < ''${#targets_[@]}; i++)); do
630         target="''${targets_[$i]}"
631         user="''${users_[$i]}"
632         group="''${groups_[$i]}"
633         if [ -n "$user$group" ]; then
634           # We have to nixos-enter since we need to use the user and group of the VM
635           nixos-enter --root $mountPoint -- chown -R "$user:$group" "$target"
636         fi
637       done
639       umount -R /mnt
641       # Make sure resize2fs works. Note that resize2fs has stricter criteria for resizing than a normal
642       # mount, so the `-c 0` and `-i 0` don't affect it. Setting it to `now` doesn't produce deterministic
643       # output, of course, but we can fix that when/if we start making images deterministic.
644       # In deterministic mode, this is fixed to 1970-01-01 (UNIX timestamp 0).
645       # This two-step approach is necessary otherwise `tune2fs` will want a fresher filesystem to perform
646       # some changes.
647       ${lib.optionalString (fsType == "ext4") ''
648         tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
649         ${lib.optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"}
650       ''}
651     ''
652   );
654   if onlyNixStore then
655     pkgs.runCommand name {}
656       (prepareImage + moveOrConvertImage + createHydraBuildProducts + postVM)
657   else buildImage