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