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