at 23.11-beta 6.5 kB view raw
1# This module exposes options to build a disk image with a GUID Partition Table 2# (GPT). It uses systemd-repart to build the image. 3 4{ config, pkgs, lib, utils, ... }: 5 6let 7 cfg = config.image.repart; 8 9 partitionOptions = { 10 options = { 11 storePaths = lib.mkOption { 12 type = with lib.types; listOf path; 13 default = [ ]; 14 description = lib.mdDoc "The store paths to include in the partition."; 15 }; 16 17 stripNixStorePrefix = lib.mkOption { 18 type = lib.types.bool; 19 default = false; 20 description = lib.mdDoc '' 21 Whether to strip `/nix/store/` from the store paths. This is useful 22 when you want to build a partition that only contains store paths and 23 is mounted under `/nix/store`. 24 ''; 25 }; 26 27 contents = lib.mkOption { 28 type = with lib.types; attrsOf (submodule { 29 options = { 30 source = lib.mkOption { 31 type = types.path; 32 description = lib.mdDoc "Path of the source file."; 33 }; 34 }; 35 }); 36 default = { }; 37 example = lib.literalExpression '' 38 { 39 "/EFI/BOOT/BOOTX64.EFI".source = 40 "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi"; 41 42 "/loader/entries/nixos.conf".source = systemdBootEntry; 43 } 44 ''; 45 description = lib.mdDoc "The contents to end up in the filesystem image."; 46 }; 47 48 repartConfig = lib.mkOption { 49 type = with lib.types; attrsOf (oneOf [ str int bool ]); 50 example = { 51 Type = "home"; 52 SizeMinBytes = "512M"; 53 SizeMaxBytes = "2G"; 54 }; 55 description = lib.mdDoc '' 56 Specify the repart options for a partiton as a structural setting. 57 See <https://www.freedesktop.org/software/systemd/man/repart.d.html> 58 for all available options. 59 ''; 60 }; 61 }; 62 }; 63in 64{ 65 options.image.repart = { 66 67 name = lib.mkOption { 68 type = lib.types.str; 69 description = lib.mdDoc "The name of the image."; 70 }; 71 72 seed = lib.mkOption { 73 type = with lib.types; nullOr str; 74 # Generated with `uuidgen`. Random but fixed to improve reproducibility. 75 default = "0867da16-f251-457d-a9e8-c31f9a3c220b"; 76 description = lib.mdDoc '' 77 A UUID to use as a seed. You can set this to `null` to explicitly 78 randomize the partition UUIDs. 79 ''; 80 }; 81 82 split = lib.mkOption { 83 type = lib.types.bool; 84 default = false; 85 description = lib.mdDoc '' 86 Enables generation of split artifacts from partitions. If enabled, for 87 each partition with SplitName= set, a separate output file containing 88 just the contents of that partition is generated. 89 ''; 90 }; 91 92 package = lib.mkPackageOption pkgs "systemd-repart" { 93 default = "systemd"; 94 example = "pkgs.systemdMinimal.override { withCryptsetup = true; }"; 95 }; 96 97 partitions = lib.mkOption { 98 type = with lib.types; attrsOf (submodule partitionOptions); 99 default = { }; 100 example = lib.literalExpression '' 101 { 102 "10-esp" = { 103 contents = { 104 "/EFI/BOOT/BOOTX64.EFI".source = 105 "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi"; 106 } 107 repartConfig = { 108 Type = "esp"; 109 Format = "fat"; 110 }; 111 }; 112 "20-root" = { 113 storePaths = [ config.system.build.toplevel ]; 114 repartConfig = { 115 Type = "root"; 116 Format = "ext4"; 117 Minimize = "guess"; 118 }; 119 }; 120 }; 121 ''; 122 description = lib.mdDoc '' 123 Specify partitions as a set of the names of the partitions with their 124 configuration as the key. 125 ''; 126 }; 127 128 }; 129 130 config = { 131 132 system.build.image = 133 let 134 fileSystemToolMapping = with pkgs; { 135 "vfat" = [ dosfstools mtools ]; 136 "ext4" = [ e2fsprogs.bin ]; 137 "squashfs" = [ squashfsTools ]; 138 "erofs" = [ erofs-utils ]; 139 "btrfs" = [ btrfs-progs ]; 140 "xfs" = [ xfsprogs ]; 141 }; 142 143 fileSystems = lib.filter 144 (f: f != null) 145 (lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions); 146 147 fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems; 148 149 150 makeClosure = paths: pkgs.closureInfo { rootPaths = paths; }; 151 152 # Add the closure of the provided Nix store paths to cfg.partitions so 153 # that amend-repart-definitions.py can read it. 154 addClosure = _name: partitionConfig: partitionConfig // ( 155 lib.optionalAttrs 156 (partitionConfig.storePaths or [ ] != [ ]) 157 { closure = "${makeClosure partitionConfig.storePaths}/store-paths"; } 158 ); 159 160 161 finalPartitions = lib.mapAttrs addClosure cfg.partitions; 162 163 164 amendRepartDefinitions = pkgs.runCommand "amend-repart-definitions.py" 165 { 166 nativeBuildInputs = with pkgs; [ black ruff mypy ]; 167 buildInputs = [ pkgs.python3 ]; 168 } '' 169 install ${./amend-repart-definitions.py} $out 170 patchShebangs --host $out 171 172 black --check --diff $out 173 ruff --line-length 88 $out 174 mypy --strict $out 175 ''; 176 177 format = pkgs.formats.ini { }; 178 179 definitionsDirectory = utils.systemdUtils.lib.definitions 180 "repart.d" 181 format 182 (lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) finalPartitions); 183 184 partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions); 185 in 186 pkgs.runCommand cfg.name 187 { 188 nativeBuildInputs = [ 189 cfg.package 190 pkgs.fakeroot 191 pkgs.util-linux 192 ] ++ fileSystemTools; 193 } '' 194 amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory}) 195 196 mkdir -p $out 197 cd $out 198 199 unshare --map-root-user fakeroot systemd-repart \ 200 --dry-run=no \ 201 --empty=create \ 202 --size=auto \ 203 --seed="${cfg.seed}" \ 204 --definitions="$amendedRepartDefinitions" \ 205 --split="${lib.boolToString cfg.split}" \ 206 --json=pretty \ 207 image.raw \ 208 | tee repart-output.json 209 ''; 210 211 meta.maintainers = with lib.maintainers; [ nikstur ]; 212 213 }; 214}