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