1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8
9let
10 cfg = config.systemd.repart;
11 initrdCfg = config.boot.initrd.systemd.repart;
12
13 format = pkgs.formats.ini { listsAsDuplicateKeys = true; };
14
15 definitionsDirectory = utils.systemdUtils.lib.definitions "repart.d" format (
16 lib.mapAttrs (_n: v: { Partition = v; }) cfg.partitions
17 );
18
19 partitionAssertions = lib.mapAttrsToList (
20 fileName: definition:
21 let
22 inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
23 labelLength = builtins.stringLength definition.Label;
24 in
25 {
26 assertion = definition ? Label -> GPTMaxLabelLength >= labelLength;
27 message = ''
28 The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength}
29 characters long, but the maximum label length supported by systemd is ${toString GPTMaxLabelLength}.
30 '';
31 }
32 ) cfg.partitions;
33in
34{
35 options = {
36 boot.initrd.systemd.repart = {
37 enable = lib.mkEnableOption "systemd-repart" // {
38 description = ''
39 Grow and add partitions to a partition table at boot time in the initrd.
40 systemd-repart only works with GPT partition tables.
41
42 To run systemd-repart after the initrd, see
43 `options.systemd.repart.enable`.
44 '';
45 };
46
47 device = lib.mkOption {
48 type = with lib.types; nullOr str;
49 description = ''
50 The device to operate on.
51
52 If `device == null`, systemd-repart will operate on the device
53 backing the root partition. So in order to dynamically *create* the
54 root partition in the initrd you need to set a device.
55 '';
56 default = null;
57 example = "/dev/vda";
58 };
59
60 empty = lib.mkOption {
61 type = lib.types.enum [
62 "refuse"
63 "allow"
64 "require"
65 "force"
66 "create"
67 ];
68 description = ''
69 Controls how to operate on empty devices that contain no partition table yet.
70 See {manpage}`systemd-repart(8)` for details.
71 '';
72 example = "require";
73 default = "refuse";
74 };
75
76 discard = lib.mkOption {
77 type = lib.types.bool;
78 description = ''
79 Controls whether to issue the BLKDISCARD I/O control command on the
80 space taken up by any added partitions or on the space in between them.
81 Usually, it's a good idea to issue this request since it tells the underlying
82 hardware that the covered blocks shall be considered empty, improving performance.
83
84 See {manpage}`systemd-repart(8)` for details.
85 '';
86 default = true;
87 };
88
89 extraArgs = lib.mkOption {
90 description = ''
91 Extra command-line arguments to pass to systemd-repart.
92
93 See {manpage}`systemd-repart(8)` for all available options.
94 '';
95 type = lib.types.listOf lib.types.str;
96 default = [ ];
97 };
98 };
99
100 systemd.repart = {
101 enable = lib.mkEnableOption "systemd-repart" // {
102 description = ''
103 Grow and add partitions to a partition table.
104 systemd-repart only works with GPT partition tables.
105
106 To run systemd-repart while in the initrd, see
107 `options.boot.initrd.systemd.repart.enable`.
108 '';
109 };
110
111 partitions = lib.mkOption {
112 type =
113 with lib.types;
114 attrsOf (
115 attrsOf (oneOf [
116 str
117 int
118 bool
119 (listOf str)
120 ])
121 );
122 default = { };
123 example = {
124 "10-root" = {
125 Type = "root";
126 };
127 "20-home" = {
128 Type = "home";
129 SizeMinBytes = "512M";
130 SizeMaxBytes = "2G";
131 };
132 };
133 description = ''
134 Specify partitions as a set of the names of the definition files as the
135 key and the partition configuration as its value. The partition
136 configuration can use all upstream options. See {manpage}`repart.d(5)`
137 for all available options.
138 '';
139 };
140 };
141 };
142
143 config = lib.mkIf (cfg.enable || initrdCfg.enable) {
144 assertions = [
145 {
146 assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable;
147 message = ''
148 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled.
149 '';
150 }
151 ]
152 ++ partitionAssertions;
153
154 # systemd-repart uses loopback devices for partition creation
155 boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop";
156
157 boot.initrd.systemd = lib.mkIf initrdCfg.enable {
158 additionalUpstreamUnits = [
159 "systemd-repart.service"
160 ];
161
162 storePaths = [
163 "${config.boot.initrd.systemd.package}/bin/systemd-repart"
164 ];
165
166 contents."/etc/repart.d".source = definitionsDirectory;
167
168 # Override defaults in upstream unit.
169 services.systemd-repart =
170 let
171 deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device";
172 in
173 {
174 # systemd-repart tries to create directories in /var/tmp by default to
175 # store large temporary files that benefit from persistence on disk. In
176 # the initrd, however, /var/tmp does not provide more persistence than
177 # /tmp, so we re-use it here.
178 environment."TMPDIR" = "/tmp";
179 serviceConfig = {
180 ExecStart = [
181 " " # required to unset the previous value.
182 # When running in the initrd, systemd-repart by default searches
183 # for definition files in /sysroot or /sysusr. We tell it to look
184 # in the initrd itself.
185 ''
186 ${config.boot.initrd.systemd.package}/bin/systemd-repart \
187 --definitions=/etc/repart.d \
188 --dry-run=no \
189 --empty=${initrdCfg.empty} \
190 --discard=${lib.boolToString initrdCfg.discard} \
191 ${utils.escapeSystemdExecArgs initrdCfg.extraArgs} \
192 ${lib.optionalString (initrdCfg.device != null) initrdCfg.device}
193 ''
194 ];
195 };
196 # systemd-repart needs to run after /sysroot (or /sysuser, but we
197 # don't have it) has been mounted because otherwise it cannot
198 # determine the device (i.e disk) to operate on. If you want to run
199 # systemd-repart without /sysroot (i.e. to create the root
200 # partition), you have to explicitly tell it which device to operate
201 # on. The service then needs to be ordered to run after this device
202 # is available.
203 requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ];
204 after = if initrdCfg.device == null then [ "sysroot.mount" ] else [ deviceUnit ];
205 };
206 };
207
208 environment.etc = lib.mkIf cfg.enable {
209 "repart.d".source = definitionsDirectory;
210 };
211
212 systemd = lib.mkIf cfg.enable {
213 additionalUpstreamSystemUnits = [
214 "systemd-repart.service"
215 ];
216 };
217 };
218
219 meta.maintainers = with lib.maintainers; [ nikstur ];
220}