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.
4 { config, pkgs, lib, utils, ... }:
7 cfg = config.image.repart;
9 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
13 storePaths = lib.mkOption {
14 type = with lib.types; listOf path;
16 description = "The store paths to include in the partition.";
19 stripNixStorePrefix = lib.mkOption {
20 type = lib.types.bool;
23 Whether to strip `/nix/store/` from the store paths. This is useful
24 when you want to build a partition that only contains store paths and
25 is mounted under `/nix/store`.
29 contents = lib.mkOption {
30 type = with lib.types; attrsOf (submodule {
32 source = lib.mkOption {
34 description = "Path of the source file.";
39 example = lib.literalExpression ''
41 "/EFI/BOOT/BOOTX64.EFI".source =
42 "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
44 "/loader/entries/nixos.conf".source = systemdBootEntry;
47 description = "The contents to end up in the filesystem image.";
50 repartConfig = lib.mkOption {
51 type = with lib.types; attrsOf (oneOf [ str int bool ]);
54 SizeMinBytes = "512M";
58 Specify the repart options for a partiton as a structural setting.
59 See <https://www.freedesktop.org/software/systemd/man/repart.d.html>
60 for all available options.
66 mkfsOptionsToEnv = opts: lib.mapAttrs' (fsType: options: {
67 name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
68 value = builtins.concatStringsSep " " options;
73 ./repart-verity-store.nix
76 options.image.repart = {
83 If this option is unset but config.system.image.id is set,
84 config.system.image.id is used as the default value.
88 version = lib.mkOption {
89 type = lib.types.nullOr lib.types.str;
90 default = config.system.image.version;
91 defaultText = lib.literalExpression "config.system.image.version";
92 description = "Version of the image";
95 imageFileBasename = lib.mkOption {
99 Basename of the image filename without any extension (e.g. `image_1`).
103 imageFile = lib.mkOption {
104 type = lib.types.str;
107 Filename of the image including all extensions (e.g `image_1.raw` or
113 enable = lib.mkEnableOption "Image compression";
115 algorithm = lib.mkOption {
116 type = lib.types.enum [ "zstd" "xz" ];
118 description = "Compression algorithm";
121 level = lib.mkOption {
122 type = lib.types.int;
124 Compression level. The available range depends on the used algorithm.
129 seed = lib.mkOption {
130 type = with lib.types; nullOr str;
131 # Generated with `uuidgen`. Random but fixed to improve reproducibility.
132 default = "0867da16-f251-457d-a9e8-c31f9a3c220b";
134 A UUID to use as a seed. You can set this to `null` to explicitly
135 randomize the partition UUIDs.
139 split = lib.mkOption {
140 type = lib.types.bool;
143 Enables generation of split artifacts from partitions. If enabled, for
144 each partition with SplitName= set, a separate output file containing
145 just the contents of that partition is generated.
149 sectorSize = lib.mkOption {
150 type = with lib.types; nullOr int;
152 example = lib.literalExpression "4096";
154 The sector size of the disk image produced by systemd-repart. This
155 value must be a power of 2 between 512 and 4096.
159 package = lib.mkPackageOption pkgs "systemd-repart" {
160 # We use buildPackages so that repart images are built with the build
161 # platform's systemd, allowing for cross-compiled systems to work.
162 default = [ "buildPackages" "systemd" ];
163 example = "pkgs.buildPackages.systemdMinimal.override { withCryptsetup = true; }";
166 partitions = lib.mkOption {
167 type = with lib.types; attrsOf (submodule partitionOptions);
169 example = lib.literalExpression ''
173 "/EFI/BOOT/BOOTX64.EFI".source =
174 "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
182 storePaths = [ config.system.build.toplevel ];
192 Specify partitions as a set of the names of the partitions with their
193 configuration as the key.
197 mkfsOptions = lib.mkOption {
198 type = with lib.types; attrsOf (listOf str);
200 example = lib.literalExpression ''
202 vfat = [ "-S 512" "-c" ];
206 Specify extra options for created file systems. The specified options
207 are converted to individual environment variables of the format
208 `SYSTEMD_REPART_MKFS_OPTIONS_<FSTYPE>`.
210 See [upstream systemd documentation](https://github.com/systemd/systemd/blob/v255/docs/ENVIRONMENT.md?plain=1#L575-L577)
211 for information about the usage of these environment variables.
213 The example would produce the following environment variable:
215 SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512 -c"
220 finalPartitions = lib.mkOption {
221 type = lib.types.attrs;
225 Convenience option to access partitions with added closures.
233 assertions = lib.mapAttrsToList (fileName: partitionConfig:
235 inherit (partitionConfig) repartConfig;
236 labelLength = builtins.stringLength repartConfig.Label;
239 assertion = repartConfig ? Label -> GPTMaxLabelLength >= labelLength;
241 The partition label '${repartConfig.Label}'
242 defined for '${fileName}' is ${toString labelLength} characters long,
243 but the maximum label length supported by UEFI is ${toString
249 warnings = lib.filter (v: v != null) (lib.mapAttrsToList (fileName: partitionConfig:
251 inherit (partitionConfig) repartConfig;
252 suggestedMaxLabelLength = GPTMaxLabelLength - 2;
253 labelLength = builtins.stringLength repartConfig.Label;
255 if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then ''
256 The partition label '${repartConfig.Label}'
257 defined for '${fileName}' is ${toString labelLength} characters long.
258 The suggested maximum label length is ${toString
259 suggestedMaxLabelLength}.
261 If you use sytemd-sysupdate style A/B updates, this might
262 not leave enough space to increment the version number included in
263 the label in a future release. For example, if your label is
264 ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
265 you're at version 9, you cannot increment this to 10.
271 version = config.image.repart.version;
272 versionInfix = if version != null then "_${version}" else "";
273 compressionSuffix = lib.optionalString cfg.compression.enable
277 }."${cfg.compression.algorithm}";
279 makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
281 # Add the closure of the provided Nix store paths to cfg.partitions so
282 # that amend-repart-definitions.py can read it.
283 addClosure = _name: partitionConfig: partitionConfig // (
285 (partitionConfig.storePaths or [ ] != [ ])
286 { closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
290 name = lib.mkIf (config.system.image.id != null) (lib.mkOptionDefault config.system.image.id);
291 imageFileBasename = cfg.name + versionInfix;
292 imageFile = cfg.imageFileBasename + ".raw" + compressionSuffix;
295 # Generally default to slightly faster than default compression
296 # levels under the assumption that most of the building will be done
297 # for development and release builds will be customized.
298 level = lib.mkOptionDefault {
301 }."${cfg.compression.algorithm}";
304 finalPartitions = lib.mapAttrs addClosure cfg.partitions;
309 fileSystems = lib.filter
311 (lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
314 format = pkgs.formats.ini { };
316 definitionsDirectory = utils.systemdUtils.lib.definitions
319 (lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions);
321 mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
323 pkgs.callPackage ./repart-image.nix {
324 systemd = cfg.package;
325 inherit (cfg) name version imageFileBasename compression split seed sectorSize finalPartitions;
326 inherit fileSystems definitionsDirectory mkfsEnv;
329 meta.maintainers = with lib.maintainers; [ nikstur willibutz ];