# This module was lifted from nixpkgs installer code. It is modified # so as to not import all-hardware. The goal here is to write the # nixos image for a raspberry pi to an sd-card in a way so that we can # pop it in and go. We don't need to support many possible hardware # targets since we know we are targeting raspberry pi products. # This module creates a bootable SD card image containing the given NixOS # configuration. The generated image is MBR partitioned, with a FAT # /boot/firmware partition, and ext4 root partition. The generated image # is sized to fit its contents, and a boot script automatically resizes # the root partition to fit the device on the first boot. # # The firmware partition is built with expectation to hold the Raspberry # Pi firmware and bootloader, and be removed and replaced with a firmware # build for the target SoC for other board families. # # The derivation for the SD image will be placed in # config.system.build.sdImage { modulesPath, config, lib, pkgs, ... }: with lib; let rootfsImage = pkgs.callPackage "${modulesPath}/../lib/make-ext4-fs.nix" ({ inherit (config.sdImage) storePaths; compressImage = false; populateImageCommands = config.sdImage.populateRootCommands; volumeLabel = "NIXOS_SD"; } // optionalAttrs (config.sdImage.rootPartitionUUID != null) { uuid = config.sdImage.rootPartitionUUID; }); in { imports = [ ]; options.sdImage = { imageName = mkOption { default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img"; description = '' Name of the generated image file. ''; }; imageBaseName = mkOption { default = "nixos-sd-image"; description = '' Prefix of the name of the generated image file. ''; }; storePaths = mkOption { type = with types; listOf package; example = literalExpression "[ pkgs.stdenv ]"; description = '' Derivations to be included in the Nix store in the generated SD image. ''; }; firmwarePartitionOffset = mkOption { type = types.int; default = 8; description = '' Gap in front of the /boot/firmware partition, in mebibytes (1024×1024 bytes). Can be increased to make more space for boards requiring to dd u-boot SPL before actual partitions. Unless you are building your own images pre-configured with an installed U-Boot, you can instead opt to delete the existing `FIRMWARE` partition, which is used **only** for the Raspberry Pi family of hardware. ''; }; firmwarePartitionID = mkOption { type = types.str; default = "0x2178694e"; description = '' Volume ID for the /boot/firmware partition on the SD card. This value must be a 32-bit hexadecimal number. ''; }; rootPartitionUUID = mkOption { type = types.nullOr types.str; default = null; example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7"; description = '' UUID for the filesystem on the main NixOS partition on the SD card. ''; }; firmwareSize = mkOption { type = types.int; # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB default = 128; description = '' Size of the /boot/firmware partition, in megabytes. ''; }; populateFirmwareCommands = mkOption { example = literalExpression "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''"; description = '' Shell commands to populate the ./firmware directory. All files in that directory are copied to the /boot/firmware partition on the SD image. ''; }; populateRootCommands = mkOption { example = literalExpression "''\${config.boot.loader.generic-extlinux-compatible.populateCmd} -c \${config.system.build.toplevel} -d ./files/boot''"; description = '' Shell commands to populate the ./files directory. All files in that directory are copied to the root (/) partition on the SD image. Use this to populate the ./files/boot (/boot) directory. ''; }; postBuildCommands = mkOption { example = literalExpression "'' dd if=\${pkgs.myBootLoader}/SPL of=$img bs=1024 seek=1 conv=notrunc ''"; default = ""; description = '' Shell commands to run after the image is built. Can be used for boards requiring to dd u-boot SPL before actual partitions. ''; }; compressImage = mkOption { type = types.bool; default = false; description = '' Whether the SD image should be compressed using zstd. ''; }; expandOnBoot = mkOption { type = types.bool; default = true; description = '' Whether to configure the sd image to expand it's partition on boot. ''; }; }; config = { fileSystems = { "/boot/firmware" = { device = "/dev/disk/by-label/${config.raspberry-pi-nix.firmware-partition-label}"; fsType = "vfat"; }; "/" = { device = "/dev/disk/by-label/NIXOS_SD"; fsType = "ext4"; }; }; sdImage.storePaths = [ config.system.build.toplevel ]; system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation { name = config.sdImage.imageName; nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime util-linux zstd ]; inherit (config.sdImage) compressImage; buildCommand = '' mkdir -p $out/nix-support $out/sd-image export img=$out/sd-image/${config.sdImage.imageName} echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system if test -n "$compressImage"; then echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products else echo "file sd-image $img" >> $out/nix-support/hydra-build-products fi echo "Decompressing rootfs image" cp "${rootfsImage}" ./root-fs.img # Gap in front of the first partition, in MiB gap=${toString config.sdImage.firmwarePartitionOffset} # Create the image file sized to fit /boot/firmware and /, plus slack for the gap. rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }') firmwareSizeBlocks=$((${ toString config.sdImage.firmwareSize } * 1024 * 1024 / 512)) imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024)) truncate -s $imageSize $img # type=b is 'W95 FAT32', type=83 is 'Linux'. # The "bootable" partition is where u-boot will look file for the bootloader # information (dtbs, extlinux.conf file). sfdisk $img <