dxvk_1: fix build compatibility with GCC 14 (#360918)
[NixPkgs.git] / nixos / modules / image / repart.nix
blob544779cd6e9b398a4cd916ecc92f06c3b57e29ad
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, ... }:
6 let
7   cfg = config.image.repart;
9   inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
11   partitionOptions = {
12     options = {
13       storePaths = lib.mkOption {
14         type = with lib.types; listOf path;
15         default = [ ];
16         description = "The store paths to include in the partition.";
17       };
19       stripNixStorePrefix = lib.mkOption {
20         type = lib.types.bool;
21         default = false;
22         description = ''
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`.
26         '';
27       };
29       contents = lib.mkOption {
30         type = with lib.types; attrsOf (submodule {
31           options = {
32             source = lib.mkOption {
33               type = types.path;
34               description = "Path of the source file.";
35             };
36           };
37         });
38         default = { };
39         example = lib.literalExpression ''
40           {
41             "/EFI/BOOT/BOOTX64.EFI".source =
42               "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
44             "/loader/entries/nixos.conf".source = systemdBootEntry;
45           }
46         '';
47         description = "The contents to end up in the filesystem image.";
48       };
50       repartConfig = lib.mkOption {
51         type = with lib.types; attrsOf (oneOf [ str int bool ]);
52         example = {
53           Type = "home";
54           SizeMinBytes = "512M";
55           SizeMaxBytes = "2G";
56         };
57         description = ''
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.
61         '';
62       };
63     };
64   };
66   mkfsOptionsToEnv = opts: lib.mapAttrs' (fsType: options: {
67     name = "SYSTEMD_REPART_MKFS_OPTIONS_${lib.toUpper fsType}";
68     value = builtins.concatStringsSep " " options;
69   }) opts;
72   imports = [
73     ./repart-verity-store.nix
74   ];
76   options.image.repart = {
78     name = lib.mkOption {
79       type = lib.types.str;
80       description = ''
81         Name of the image.
83         If this option is unset but config.system.image.id is set,
84         config.system.image.id is used as the default value.
85       '';
86     };
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";
93     };
95     imageFileBasename = lib.mkOption {
96       type = lib.types.str;
97       readOnly = true;
98       description = ''
99         Basename of the image filename without any extension (e.g. `image_1`).
100       '';
101     };
103     imageFile = lib.mkOption {
104       type = lib.types.str;
105       readOnly = true;
106       description = ''
107         Filename of the image including all extensions (e.g `image_1.raw` or
108         `image_1.raw.zst`).
109       '';
110     };
112     compression = {
113       enable = lib.mkEnableOption "Image compression";
115       algorithm = lib.mkOption {
116         type = lib.types.enum [ "zstd" "xz" ];
117         default = "zstd";
118         description = "Compression algorithm";
119       };
121       level = lib.mkOption {
122         type = lib.types.int;
123         description = ''
124           Compression level. The available range depends on the used algorithm.
125         '';
126       };
127     };
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";
133       description = ''
134         A UUID to use as a seed. You can set this to `null` to explicitly
135         randomize the partition UUIDs.
136       '';
137     };
139     split = lib.mkOption {
140       type = lib.types.bool;
141       default = false;
142       description = ''
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.
146       '';
147     };
149     sectorSize = lib.mkOption {
150       type = with lib.types; nullOr int;
151       default = 512;
152       example = lib.literalExpression "4096";
153       description = ''
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.
156       '';
157     };
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; }";
164     };
166     partitions = lib.mkOption {
167       type = with lib.types; attrsOf (submodule partitionOptions);
168       default = { };
169       example = lib.literalExpression ''
170         {
171           "10-esp" = {
172             contents = {
173               "/EFI/BOOT/BOOTX64.EFI".source =
174                 "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
175             }
176             repartConfig = {
177               Type = "esp";
178               Format = "fat";
179             };
180           };
181           "20-root" = {
182             storePaths = [ config.system.build.toplevel ];
183             repartConfig = {
184               Type = "root";
185               Format = "ext4";
186               Minimize = "guess";
187             };
188           };
189         };
190       '';
191       description = ''
192         Specify partitions as a set of the names of the partitions with their
193         configuration as the key.
194       '';
195     };
197     mkfsOptions = lib.mkOption {
198       type = with lib.types; attrsOf (listOf str);
199       default = {};
200       example = lib.literalExpression ''
201         {
202           vfat = [ "-S 512" "-c" ];
203         }
204       '';
205       description = ''
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:
214         ```
215         SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512 -c"
216         ```
217       '';
218     };
220     finalPartitions = lib.mkOption {
221       type = lib.types.attrs;
222       internal = true;
223       readOnly = true;
224       description = ''
225         Convenience option to access partitions with added closures.
226       '';
227     };
229   };
231   config = {
233     assertions = lib.mapAttrsToList (fileName: partitionConfig:
234       let
235         inherit (partitionConfig) repartConfig;
236         labelLength = builtins.stringLength repartConfig.Label;
237       in
238       {
239         assertion = repartConfig ? Label -> GPTMaxLabelLength >= labelLength;
240         message = ''
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
244           GPTMaxLabelLength}.
245         '';
246       }
247     ) cfg.partitions;
249     warnings = lib.filter (v: v != null) (lib.mapAttrsToList (fileName: partitionConfig:
250       let
251         inherit (partitionConfig) repartConfig;
252         suggestedMaxLabelLength = GPTMaxLabelLength - 2;
253         labelLength = builtins.stringLength repartConfig.Label;
254       in
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.
266         '' else null
267     ) cfg.partitions);
269     image.repart =
270       let
271         version = config.image.repart.version;
272         versionInfix = if version != null then "_${version}" else "";
273         compressionSuffix = lib.optionalString cfg.compression.enable
274           {
275             "zstd" = ".zst";
276             "xz" = ".xz";
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 // (
284           lib.optionalAttrs
285             (partitionConfig.storePaths or [ ] != [ ])
286             { closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
287         );
288       in
289       {
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;
294         compression = {
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 {
299             "zstd" = 3;
300             "xz" = 3;
301           }."${cfg.compression.algorithm}";
302         };
304         finalPartitions = lib.mapAttrs addClosure cfg.partitions;
305       };
307     system.build.image =
308       let
309         fileSystems = lib.filter
310           (f: f != null)
311           (lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
314         format = pkgs.formats.ini { };
316         definitionsDirectory = utils.systemdUtils.lib.definitions
317           "repart.d"
318           format
319           (lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) cfg.finalPartitions);
321         mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
322       in
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;
327       };
329     meta.maintainers = with lib.maintainers; [ nikstur willibutz ];
331   };