at 21.11-pre 9.4 kB view raw
1# This module creates a bootable SD card image containing the given NixOS 2# configuration. The generated image is MBR partitioned, with a FAT 3# /boot/firmware partition, and ext4 root partition. The generated image 4# is sized to fit its contents, and a boot script automatically resizes 5# the root partition to fit the device on the first boot. 6# 7# The firmware partition is built with expectation to hold the Raspberry 8# Pi firmware and bootloader, and be removed and replaced with a firmware 9# build for the target SoC for other board families. 10# 11# The derivation for the SD image will be placed in 12# config.system.build.sdImage 13 14{ config, lib, pkgs, ... }: 15 16with lib; 17 18let 19 rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({ 20 inherit (config.sdImage) storePaths; 21 compressImage = true; 22 populateImageCommands = config.sdImage.populateRootCommands; 23 volumeLabel = "NIXOS_SD"; 24 } // optionalAttrs (config.sdImage.rootPartitionUUID != null) { 25 uuid = config.sdImage.rootPartitionUUID; 26 }); 27in 28{ 29 imports = [ 30 (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.") 31 (mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.") 32 ../../profiles/all-hardware.nix 33 ]; 34 35 options.sdImage = { 36 imageName = mkOption { 37 default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img"; 38 description = '' 39 Name of the generated image file. 40 ''; 41 }; 42 43 imageBaseName = mkOption { 44 default = "nixos-sd-image"; 45 description = '' 46 Prefix of the name of the generated image file. 47 ''; 48 }; 49 50 storePaths = mkOption { 51 type = with types; listOf package; 52 example = literalExample "[ pkgs.stdenv ]"; 53 description = '' 54 Derivations to be included in the Nix store in the generated SD image. 55 ''; 56 }; 57 58 firmwarePartitionID = mkOption { 59 type = types.str; 60 default = "0x2178694e"; 61 description = '' 62 Volume ID for the /boot/firmware partition on the SD card. This value 63 must be a 32-bit hexadecimal number. 64 ''; 65 }; 66 67 firmwarePartitionName = mkOption { 68 type = types.str; 69 default = "FIRMWARE"; 70 description = '' 71 Name of the filesystem which holds the boot firmware. 72 ''; 73 }; 74 75 rootPartitionUUID = mkOption { 76 type = types.nullOr types.str; 77 default = null; 78 example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7"; 79 description = '' 80 UUID for the filesystem on the main NixOS partition on the SD card. 81 ''; 82 }; 83 84 firmwareSize = mkOption { 85 type = types.int; 86 # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB 87 default = 30; 88 description = '' 89 Size of the /boot/firmware partition, in megabytes. 90 ''; 91 }; 92 93 populateFirmwareCommands = mkOption { 94 example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''"; 95 description = '' 96 Shell commands to populate the ./firmware directory. 97 All files in that directory are copied to the 98 /boot/firmware partition on the SD image. 99 ''; 100 }; 101 102 populateRootCommands = mkOption { 103 example = literalExample "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''"; 104 description = '' 105 Shell commands to populate the ./files directory. 106 All files in that directory are copied to the 107 root (/) partition on the SD image. Use this to 108 populate the ./files/boot (/boot) directory. 109 ''; 110 }; 111 112 postBuildCommands = mkOption { 113 example = literalExample "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''"; 114 default = ""; 115 description = '' 116 Shell commands to run after the image is built. 117 Can be used for boards requiring to dd u-boot SPL before actual partitions. 118 ''; 119 }; 120 121 compressImage = mkOption { 122 type = types.bool; 123 default = true; 124 description = '' 125 Whether the SD image should be compressed using 126 <command>zstd</command>. 127 ''; 128 }; 129 130 expandOnBoot = mkOption { 131 type = types.bool; 132 default = true; 133 description = '' 134 Whether to configure the sd image to expand it's partition on boot. 135 ''; 136 }; 137 }; 138 139 config = { 140 fileSystems = { 141 "/boot/firmware" = { 142 device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}"; 143 fsType = "vfat"; 144 # Alternatively, this could be removed from the configuration. 145 # The filesystem is not needed at runtime, it could be treated 146 # as an opaque blob instead of a discrete FAT32 filesystem. 147 options = [ "nofail" "noauto" ]; 148 }; 149 "/" = { 150 device = "/dev/disk/by-label/NIXOS_SD"; 151 fsType = "ext4"; 152 }; 153 }; 154 155 sdImage.storePaths = [ config.system.build.toplevel ]; 156 157 system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, 158 mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation { 159 name = config.sdImage.imageName; 160 161 nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime util-linux zstd ]; 162 163 inherit (config.sdImage) compressImage; 164 165 buildCommand = '' 166 mkdir -p $out/nix-support $out/sd-image 167 export img=$out/sd-image/${config.sdImage.imageName} 168 169 echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system 170 if test -n "$compressImage"; then 171 echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products 172 else 173 echo "file sd-image $img" >> $out/nix-support/hydra-build-products 174 fi 175 176 echo "Decompressing rootfs image" 177 zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img 178 179 # Gap in front of the first partition, in MiB 180 gap=8 181 182 # Create the image file sized to fit /boot/firmware and /, plus slack for the gap. 183 rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }') 184 firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512)) 185 imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024)) 186 truncate -s $imageSize $img 187 188 # type=b is 'W95 FAT32', type=83 is 'Linux'. 189 # The "bootable" partition is where u-boot will look file for the bootloader 190 # information (dtbs, extlinux.conf file). 191 sfdisk $img <<EOF 192 label: dos 193 label-id: ${config.sdImage.firmwarePartitionID} 194 195 start=''${gap}M, size=$firmwareSizeBlocks, type=b 196 start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable 197 EOF 198 199 # Copy the rootfs into the SD image 200 eval $(partx $img -o START,SECTORS --nr 2 --pairs) 201 dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS 202 203 # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img 204 eval $(partx $img -o START,SECTORS --nr 1 --pairs) 205 truncate -s $((SECTORS * 512)) firmware_part.img 206 faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img 207 208 # Populate the files intended for /boot/firmware 209 mkdir firmware 210 ${config.sdImage.populateFirmwareCommands} 211 212 # Copy the populated /boot/firmware into the SD image 213 (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::) 214 # Verify the FAT partition before copying it. 215 fsck.vfat -vn firmware_part.img 216 dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS 217 218 ${config.sdImage.postBuildCommands} 219 220 if test -n "$compressImage"; then 221 zstd -T$NIX_BUILD_CORES --rm $img 222 fi 223 ''; 224 }) {}; 225 226 boot.postBootCommands = lib.mkIf config.sdImage.expandOnBoot '' 227 # On the first boot do some maintenance tasks 228 if [ -f /nix-path-registration ]; then 229 set -euo pipefail 230 set -x 231 # Figure out device names for the boot device and root filesystem. 232 rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /) 233 bootDevice=$(lsblk -npo PKNAME $rootPart) 234 partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}') 235 236 # Resize the root partition and the filesystem to fit the disk 237 echo ",+," | sfdisk -N$partNum --no-reread $bootDevice 238 ${pkgs.parted}/bin/partprobe 239 ${pkgs.e2fsprogs}/bin/resize2fs $rootPart 240 241 # Register the contents of the initial Nix store 242 ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration 243 244 # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag. 245 touch /etc/NIXOS 246 ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system 247 248 # Prevents this from running on later boots. 249 rm -f /nix-path-registration 250 fi 251 ''; 252 }; 253}