1 { config, lib, pkgs, utils, ... }:
4 inherit (lib) mkIf mkOption types;
6 randomEncryptionCoerce = enable: { inherit enable; };
8 randomEncryptionOpts = { ... }: {
16 Encrypt swap device with a random key. This way you won't have a persistent swap device.
18 WARNING: Don't try to hibernate when you have at least one swap partition with
19 this option enabled! We have no way to set the partition into which hibernation image
20 is saved, so if your image ends up on an encrypted one you would lose it!
22 WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
23 when using randomEncryption as the UUIDs and labels will get erased on every boot when
24 the partition is encrypted. Best to use /dev/disk/by-partuuid/…
29 default = "aes-xts-plain64";
30 example = "serpent-xts-plain64";
33 Use specified cipher for randomEncryption.
35 Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
42 type = types.nullOr types.int;
44 Set the encryption key size for the plain device.
46 If not specified, the amount of data to read from `source` will be
47 determined by cryptsetup.
49 See `cryptsetup-open(8)` for details.
53 sectorSize = mkOption {
56 type = types.nullOr types.int;
58 Set the sector size for the plain encrypted device type.
60 If not specified, the default sector size is determined from the
61 underlying block device.
63 See `cryptsetup-open(8)` for details.
68 default = "/dev/urandom";
69 example = "/dev/random";
72 Define the source of randomness to obtain a random key for encryption.
76 allowDiscards = mkOption {
80 Whether to allow TRIM requests to the underlying device. This option
81 has security implications; please read the LUKS documentation before
89 swapCfg = {config, options, ...}: {
94 example = "/dev/sda3";
95 type = types.nonEmptyStr;
96 description = "Path of the device or swap file.";
103 Label of the device. Can be used instead of {var}`device`.
110 type = types.nullOr types.int;
112 If this option is set, ‘device’ is interpreted as the
113 path of a swapfile that will be created automatically
114 with the indicated size (in megabytes).
118 priority = mkOption {
121 type = types.nullOr types.int;
123 Specify the priority of the swap device. Priority is a value between 0 and 32767.
124 Higher numbers indicate higher priority.
125 null lets the kernel choose a priority, which will show up as a negative value.
129 randomEncryption = mkOption {
133 cipher = "serpent-xts-plain64";
134 source = "/dev/random";
136 type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
138 Encrypt swap device with a random key. This way you won't have a persistent swap device.
140 HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
142 WARNING: Don't try to hibernate when you have at least one swap partition with
143 this option enabled! We have no way to set the partition into which hibernation image
144 is saved, so if your image ends up on an encrypted one you would lose it!
146 WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
147 when using randomEncryption as the UUIDs and labels will get erased on every boot when
148 the partition is encrypted. Best to use /dev/disk/by-partuuid/…
152 discardPolicy = mkOption {
155 type = types.nullOr (types.enum ["once" "pages" "both" ]);
157 Specify the discard policy for the swap device. If "once", then the
158 whole swap space is discarded at swapon invocation. If "pages",
159 asynchronous discard on freed pages is performed, before returning to
160 the available pages pool. With "both", both policies are activated.
161 See swapon(8) for more information.
166 default = [ "defaults" ];
167 example = [ "nofail" ];
168 type = types.listOf types.nonEmptyStr;
170 Options used to mount the swap.
174 deviceName = mkOption {
179 realDevice = mkOption {
187 device = mkIf options.label.isDefined
188 "/dev/disk/by-label/${config.label}";
189 deviceName = lib.replaceStrings ["\\"] [""] (utils.escapeSystemdPath config.device);
190 realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
203 swapDevices = mkOption {
206 { device = "/dev/hda7"; }
207 { device = "/var/swapfile"; }
208 { label = "bigswap"; }
211 The swap devices and swap files. These must have been
212 initialised using {command}`mkswap`. Each element
213 should be an attribute set specifying either the path of the
214 swap device or file (`device`) or the label
215 of the swap device (`label`, see
216 {command}`mkswap -L`). Using a label is
220 type = types.listOf (types.submodule swapCfg);
225 config = mkIf ((lib.length config.swapDevices) != 0) {
226 assertions = lib.map (sw: {
227 assertion = sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
229 You cannot use swap device "${sw.device}" with randomEncryption enabled.
230 The UUIDs and labels will get erased on every boot when the partition is encrypted.
231 Use /dev/disk/by-partuuid/… instead.
233 }) config.swapDevices;
237 if sw.size != null && lib.hasPrefix "/dev/" sw.device
238 then [ "Setting the swap size of block device ${sw.device} has no effect" ]
242 system.requiredKernelConfig = [
243 (config.lib.kernelConfig.isYes "SWAP")
246 # Create missing swapfiles.
249 createSwapDevice = sw:
250 let realDevice' = utils.escapeSystemdPath sw.realDevice;
251 in lib.nameValuePair "mkswap-${sw.deviceName}"
252 { description = "Initialisation of swap device ${sw.device}";
253 # The mkswap service fails for file-backed swap devices if the
254 # loop module has not been loaded before the service runs.
255 # We add an ordering constraint to run after systemd-modules-load to
256 # avoid this race condition.
257 after = [ "systemd-modules-load.service" ];
258 wantedBy = [ "${realDevice'}.swap" ];
259 before = [ "${realDevice'}.swap" "shutdown.target"];
260 conflicts = [ "shutdown.target" ];
261 path = [ pkgs.util-linux pkgs.e2fsprogs ]
262 ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup;
264 environment.DEVICE = sw.device;
268 ${lib.optionalString (sw.size != null) ''
269 currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
270 if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
271 # Disable CoW for CoW based filesystems like BTRFS.
272 truncate --size 0 "$DEVICE"
273 chattr +C "$DEVICE" 2>/dev/null || true
275 echo "Creating swap file using dd and mkswap."
276 dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress
277 ${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
280 ${lib.optionalString sw.randomEncryption.enable ''
281 cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
282 ${lib.concatStringsSep " \\\n" (lib.flatten [
283 (lib.optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
284 (lib.optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
285 (lib.optional sw.randomEncryption.allowDiscards "--allow-discards")
286 ])} ${sw.device} ${sw.deviceName}
287 mkswap ${sw.realDevice}
291 unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ];
292 unitConfig.DefaultDependencies = false; # needed to prevent a cycle
295 RemainAfterExit = sw.randomEncryption.enable;
297 ExecStop = lib.optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
299 restartIfChanged = false;
302 in lib.listToAttrs (lib.map createSwapDevice (lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));