1{ config, lib, pkgs, utils, ... }:
2
3let
4 cfg = config.systemd.repart;
5 initrdCfg = config.boot.initrd.systemd.repart;
6
7 format = pkgs.formats.ini { };
8
9 definitionsDirectory = utils.systemdUtils.lib.definitions
10 "repart.d"
11 format
12 (lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions);
13
14 partitionAssertions = lib.mapAttrsToList (fileName: definition:
15 let
16 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
17 labelLength = builtins.stringLength definition.Label;
18 in
19 {
20 assertion = definition ? Label -> GPTMaxLabelLength >= labelLength;
21 message = ''
22 The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength}
23 characters long, but the maximum label length supported by systemd is ${toString GPTMaxLabelLength}.
24 '';
25 }
26 ) cfg.partitions;
27in
28{
29 options = {
30 boot.initrd.systemd.repart = {
31 enable = lib.mkEnableOption "systemd-repart" // {
32 description = ''
33 Grow and add partitions to a partition table at boot time in the initrd.
34 systemd-repart only works with GPT partition tables.
35
36 To run systemd-repart after the initrd, see
37 `options.systemd.repart.enable`.
38 '';
39 };
40
41 device = lib.mkOption {
42 type = with lib.types; nullOr str;
43 description = ''
44 The device to operate on.
45
46 If `device == null`, systemd-repart will operate on the device
47 backing the root partition. So in order to dynamically *create* the
48 root partition in the initrd you need to set a device.
49 '';
50 default = null;
51 example = "/dev/vda";
52 };
53 };
54
55 systemd.repart = {
56 enable = lib.mkEnableOption "systemd-repart" // {
57 description = ''
58 Grow and add partitions to a partition table.
59 systemd-repart only works with GPT partition tables.
60
61 To run systemd-repart while in the initrd, see
62 `options.boot.initrd.systemd.repart.enable`.
63 '';
64 };
65
66 partitions = lib.mkOption {
67 type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
68 default = { };
69 example = {
70 "10-root" = {
71 Type = "root";
72 };
73 "20-home" = {
74 Type = "home";
75 SizeMinBytes = "512M";
76 SizeMaxBytes = "2G";
77 };
78 };
79 description = ''
80 Specify partitions as a set of the names of the definition files as the
81 key and the partition configuration as its value. The partition
82 configuration can use all upstream options. See <link
83 xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
84 for all available options.
85 '';
86 };
87 };
88 };
89
90 config = lib.mkIf (cfg.enable || initrdCfg.enable) {
91 assertions = [
92 {
93 assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable;
94 message = ''
95 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled.
96 '';
97 }
98 ] ++ partitionAssertions;
99
100 # systemd-repart uses loopback devices for partition creation
101 boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop";
102
103 boot.initrd.systemd = lib.mkIf initrdCfg.enable {
104 additionalUpstreamUnits = [
105 "systemd-repart.service"
106 ];
107
108 storePaths = [
109 "${config.boot.initrd.systemd.package}/bin/systemd-repart"
110 ];
111
112 contents."/etc/repart.d".source = definitionsDirectory;
113
114 # Override defaults in upstream unit.
115 services.systemd-repart =
116 let
117 deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device";
118 in
119 {
120 # systemd-repart tries to create directories in /var/tmp by default to
121 # store large temporary files that benefit from persistence on disk. In
122 # the initrd, however, /var/tmp does not provide more persistence than
123 # /tmp, so we re-use it here.
124 environment."TMPDIR" = "/tmp";
125 serviceConfig = {
126 ExecStart = [
127 " " # required to unset the previous value.
128 # When running in the initrd, systemd-repart by default searches
129 # for definition files in /sysroot or /sysusr. We tell it to look
130 # in the initrd itself.
131 ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
132 --definitions=/etc/repart.d \
133 --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device}
134 ''
135 ];
136 };
137 # systemd-repart needs to run after /sysroot (or /sysuser, but we
138 # don't have it) has been mounted because otherwise it cannot
139 # determine the device (i.e disk) to operate on. If you want to run
140 # systemd-repart without /sysroot (i.e. to create the root
141 # partition), you have to explicitly tell it which device to operate
142 # on. The service then needs to be ordered to run after this device
143 # is available.
144 requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ];
145 after =
146 if initrdCfg.device == null then
147 [ "sysroot.mount" ]
148 else
149 [ deviceUnit ];
150 };
151 };
152
153 environment.etc = lib.mkIf cfg.enable {
154 "repart.d".source = definitionsDirectory;
155 };
156
157 systemd = lib.mkIf cfg.enable {
158 additionalUpstreamSystemUnits = [
159 "systemd-repart.service"
160 ];
161 };
162 };
163
164 meta.maintainers = with lib.maintainers; [ nikstur ];
165}