1{ config, lib, pkgs, utils, ... }:
2
3with utils;
4with lib;
5
6let
7
8 randomEncryptionCoerce = enable: { inherit enable; };
9
10 randomEncryptionOpts = { ... }: {
11
12 options = {
13
14 enable = mkOption {
15 default = false;
16 type = types.bool;
17 description = lib.mdDoc ''
18 Encrypt swap device with a random key. This way you won't have a persistent swap device.
19
20 WARNING: Don't try to hibernate when you have at least one swap partition with
21 this option enabled! We have no way to set the partition into which hibernation image
22 is saved, so if your image ends up on an encrypted one you would lose it!
23
24 WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
25 when using randomEncryption as the UUIDs and labels will get erased on every boot when
26 the partition is encrypted. Best to use /dev/disk/by-partuuid/…
27 '';
28 };
29
30 cipher = mkOption {
31 default = "aes-xts-plain64";
32 example = "serpent-xts-plain64";
33 type = types.str;
34 description = lib.mdDoc ''
35 Use specified cipher for randomEncryption.
36
37 Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
38 '';
39 };
40
41 source = mkOption {
42 default = "/dev/urandom";
43 example = "/dev/random";
44 type = types.str;
45 description = lib.mdDoc ''
46 Define the source of randomness to obtain a random key for encryption.
47 '';
48 };
49
50 allowDiscards = mkOption {
51 default = false;
52 type = types.bool;
53 description = lib.mdDoc ''
54 Whether to allow TRIM requests to the underlying device. This option
55 has security implications; please read the LUKS documentation before
56 activating it.
57 '';
58 };
59 };
60
61 };
62
63 swapCfg = {config, options, ...}: {
64
65 options = {
66
67 device = mkOption {
68 example = "/dev/sda3";
69 type = types.str;
70 description = lib.mdDoc "Path of the device or swap file.";
71 };
72
73 label = mkOption {
74 example = "swap";
75 type = types.str;
76 description = lib.mdDoc ''
77 Label of the device. Can be used instead of {var}`device`.
78 '';
79 };
80
81 size = mkOption {
82 default = null;
83 example = 2048;
84 type = types.nullOr types.int;
85 description = lib.mdDoc ''
86 If this option is set, ‘device’ is interpreted as the
87 path of a swapfile that will be created automatically
88 with the indicated size (in megabytes).
89 '';
90 };
91
92 priority = mkOption {
93 default = null;
94 example = 2048;
95 type = types.nullOr types.int;
96 description = lib.mdDoc ''
97 Specify the priority of the swap device. Priority is a value between 0 and 32767.
98 Higher numbers indicate higher priority.
99 null lets the kernel choose a priority, which will show up as a negative value.
100 '';
101 };
102
103 randomEncryption = mkOption {
104 default = false;
105 example = {
106 enable = true;
107 cipher = "serpent-xts-plain64";
108 source = "/dev/random";
109 };
110 type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
111 description = lib.mdDoc ''
112 Encrypt swap device with a random key. This way you won't have a persistent swap device.
113
114 HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
115
116 WARNING: Don't try to hibernate when you have at least one swap partition with
117 this option enabled! We have no way to set the partition into which hibernation image
118 is saved, so if your image ends up on an encrypted one you would lose it!
119
120 WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
121 when using randomEncryption as the UUIDs and labels will get erased on every boot when
122 the partition is encrypted. Best to use /dev/disk/by-partuuid/…
123 '';
124 };
125
126 discardPolicy = mkOption {
127 default = null;
128 example = "once";
129 type = types.nullOr (types.enum ["once" "pages" "both" ]);
130 description = lib.mdDoc ''
131 Specify the discard policy for the swap device. If "once", then the
132 whole swap space is discarded at swapon invocation. If "pages",
133 asynchronous discard on freed pages is performed, before returning to
134 the available pages pool. With "both", both policies are activated.
135 See swapon(8) for more information.
136 '';
137 };
138
139 options = mkOption {
140 default = [ "defaults" ];
141 example = [ "nofail" ];
142 type = types.listOf types.nonEmptyStr;
143 description = lib.mdDoc ''
144 Options used to mount the swap.
145 '';
146 };
147
148 deviceName = mkOption {
149 type = types.str;
150 internal = true;
151 };
152
153 realDevice = mkOption {
154 type = types.path;
155 internal = true;
156 };
157
158 };
159
160 config = rec {
161 device = mkIf options.label.isDefined
162 "/dev/disk/by-label/${config.label}";
163 deviceName = lib.replaceChars ["\\"] [""] (escapeSystemdPath config.device);
164 realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
165 };
166
167 };
168
169in
170
171{
172
173 ###### interface
174
175 options = {
176
177 swapDevices = mkOption {
178 default = [];
179 example = [
180 { device = "/dev/hda7"; }
181 { device = "/var/swapfile"; }
182 { label = "bigswap"; }
183 ];
184 description = lib.mdDoc ''
185 The swap devices and swap files. These must have been
186 initialised using {command}`mkswap`. Each element
187 should be an attribute set specifying either the path of the
188 swap device or file (`device`) or the label
189 of the swap device (`label`, see
190 {command}`mkswap -L`). Using a label is
191 recommended.
192 '';
193
194 type = types.listOf (types.submodule swapCfg);
195 };
196
197 };
198
199 config = mkIf ((length config.swapDevices) != 0) {
200
201 system.requiredKernelConfig = with config.lib.kernelConfig; [
202 (isYes "SWAP")
203 ];
204
205 # Create missing swapfiles.
206 systemd.services =
207 let
208
209 createSwapDevice = sw:
210 assert sw.device != "";
211 assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-uuid" sw.device);
212 assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-label" sw.device);
213 let realDevice' = escapeSystemdPath sw.realDevice;
214 in nameValuePair "mkswap-${sw.deviceName}"
215 { description = "Initialisation of swap device ${sw.device}";
216 wantedBy = [ "${realDevice'}.swap" ];
217 before = [ "${realDevice'}.swap" ];
218 path = [ pkgs.util-linux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
219
220 script =
221 ''
222 ${optionalString (sw.size != null) ''
223 currentSize=$(( $(stat -c "%s" "${sw.device}" 2>/dev/null || echo 0) / 1024 / 1024 ))
224 if [ "${toString sw.size}" != "$currentSize" ]; then
225 dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
226 chmod 0600 ${sw.device}
227 ${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
228 fi
229 ''}
230 ${optionalString sw.randomEncryption.enable ''
231 cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
232 ${optionalString sw.randomEncryption.allowDiscards "--allow-discards"} ${sw.device} ${sw.deviceName}
233 mkswap ${sw.realDevice}
234 ''}
235 '';
236
237 unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ];
238 unitConfig.DefaultDependencies = false; # needed to prevent a cycle
239 serviceConfig.Type = "oneshot";
240 serviceConfig.RemainAfterExit = sw.randomEncryption.enable;
241 serviceConfig.ExecStop = optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
242 restartIfChanged = false;
243 };
244
245 in listToAttrs (map createSwapDevice (filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));
246
247 };
248
249}