at master 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8 9let 10 inherit (lib) mkIf mkOption types; 11 12 randomEncryptionCoerce = enable: { inherit enable; }; 13 14 randomEncryptionOpts = 15 { ... }: 16 { 17 18 options = { 19 20 enable = mkOption { 21 default = false; 22 type = types.bool; 23 description = '' 24 Encrypt swap device with a random key. This way you won't have a persistent swap device. 25 26 WARNING: Don't try to hibernate when you have at least one swap partition with 27 this option enabled! We have no way to set the partition into which hibernation image 28 is saved, so if your image ends up on an encrypted one you would lose it! 29 30 WARNING #2: Do not use /dev/disk/by-uuid/ or /dev/disk/by-label/ as your swap device 31 when using randomEncryption as the UUIDs and labels will get erased on every boot when 32 the partition is encrypted. Best to use /dev/disk/by-partuuid/ 33 ''; 34 }; 35 36 cipher = mkOption { 37 default = "aes-xts-plain64"; 38 example = "serpent-xts-plain64"; 39 type = types.str; 40 description = '' 41 Use specified cipher for randomEncryption. 42 43 Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine. 44 ''; 45 }; 46 47 keySize = mkOption { 48 default = null; 49 example = "512"; 50 type = types.nullOr types.int; 51 description = '' 52 Set the encryption key size for the plain device. 53 54 If not specified, the amount of data to read from `source` will be 55 determined by cryptsetup. 56 57 See {manpage}`cryptsetup-open(8)` for details. 58 ''; 59 }; 60 61 sectorSize = mkOption { 62 default = null; 63 example = "4096"; 64 type = types.nullOr types.int; 65 description = '' 66 Set the sector size for the plain encrypted device type. 67 68 If not specified, the default sector size is determined from the 69 underlying block device. 70 71 See {manpage}`cryptsetup-open(8)` for details. 72 ''; 73 }; 74 75 source = mkOption { 76 default = "/dev/urandom"; 77 example = "/dev/random"; 78 type = types.str; 79 description = '' 80 Define the source of randomness to obtain a random key for encryption. 81 ''; 82 }; 83 84 allowDiscards = mkOption { 85 default = false; 86 type = types.bool; 87 description = '' 88 Whether to allow TRIM requests to the underlying device. This option 89 has security implications; please read the LUKS documentation before 90 activating it. 91 ''; 92 }; 93 }; 94 95 }; 96 97 swapCfg = 98 { config, options, ... }: 99 { 100 101 options = { 102 103 device = mkOption { 104 example = "/dev/sda3"; 105 type = types.nonEmptyStr; 106 description = "Path of the device or swap file."; 107 }; 108 109 label = mkOption { 110 example = "swap"; 111 type = types.str; 112 description = '' 113 Label of the device. Can be used instead of {var}`device`. 114 ''; 115 }; 116 117 size = mkOption { 118 default = null; 119 example = 2048; 120 type = types.nullOr types.int; 121 description = '' 122 If this option is set, device is interpreted as the 123 path of a swapfile that will be created automatically 124 with the indicated size in MiB (1024×1024 bytes). 125 ''; 126 }; 127 128 priority = mkOption { 129 default = null; 130 example = 2048; 131 type = types.nullOr types.int; 132 description = '' 133 Specify the priority of the swap device. Priority is a value between 0 and 32767. 134 Higher numbers indicate higher priority. 135 null lets the kernel choose a priority, which will show up as a negative value. 136 ''; 137 }; 138 139 randomEncryption = mkOption { 140 default = false; 141 example = { 142 enable = true; 143 cipher = "serpent-xts-plain64"; 144 source = "/dev/random"; 145 }; 146 type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts); 147 description = '' 148 Encrypt swap device with a random key. This way you won't have a persistent swap device. 149 150 HINT: run "cryptsetup benchmark" to test cipher performance on your machine. 151 152 WARNING: Don't try to hibernate when you have at least one swap partition with 153 this option enabled! We have no way to set the partition into which hibernation image 154 is saved, so if your image ends up on an encrypted one you would lose it! 155 156 WARNING #2: Do not use /dev/disk/by-uuid/ or /dev/disk/by-label/ as your swap device 157 when using randomEncryption as the UUIDs and labels will get erased on every boot when 158 the partition is encrypted. Best to use /dev/disk/by-partuuid/ 159 ''; 160 }; 161 162 discardPolicy = mkOption { 163 default = null; 164 example = "once"; 165 type = types.nullOr ( 166 types.enum [ 167 "once" 168 "pages" 169 "both" 170 ] 171 ); 172 description = '' 173 Specify the discard policy for the swap device. If "once", then the 174 whole swap space is discarded at swapon invocation. If "pages", 175 asynchronous discard on freed pages is performed, before returning to 176 the available pages pool. With "both", both policies are activated. 177 See {manpage}`swapon(8)` for more information. 178 ''; 179 }; 180 181 options = mkOption { 182 default = [ "defaults" ]; 183 example = [ "nofail" ]; 184 type = types.listOf types.nonEmptyStr; 185 description = '' 186 Options used to mount the swap. 187 ''; 188 }; 189 190 deviceName = mkOption { 191 type = types.str; 192 internal = true; 193 }; 194 195 realDevice = mkOption { 196 type = types.path; 197 internal = true; 198 }; 199 200 }; 201 202 config = { 203 device = mkIf options.label.isDefined "/dev/disk/by-label/${config.label}"; 204 deviceName = lib.replaceStrings [ "\\" ] [ "" ] (utils.escapeSystemdPath config.device); 205 realDevice = 206 if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device; 207 }; 208 209 }; 210 211in 212 213{ 214 215 ###### interface 216 217 options = { 218 219 swapDevices = mkOption { 220 default = [ ]; 221 example = [ 222 { device = "/dev/hda7"; } 223 { device = "/var/swapfile"; } 224 { label = "bigswap"; } 225 ]; 226 description = '' 227 The swap devices and swap files. These must have been 228 initialised using {command}`mkswap`. Each element 229 should be an attribute set specifying either the path of the 230 swap device or file (`device`) or the label 231 of the swap device (`label`, see 232 {command}`mkswap -L`). Using a label is 233 recommended. 234 ''; 235 236 type = types.listOf (types.submodule swapCfg); 237 }; 238 239 }; 240 241 config = mkIf ((lib.length config.swapDevices) != 0) { 242 assertions = lib.map (sw: { 243 assertion = 244 sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null; 245 message = '' 246 You cannot use swap device "${sw.device}" with randomEncryption enabled. 247 The UUIDs and labels will get erased on every boot when the partition is encrypted. 248 Use /dev/disk/by-partuuid/ instead. 249 ''; 250 }) config.swapDevices; 251 252 warnings = lib.concatMap ( 253 sw: 254 if sw.size != null && lib.hasPrefix "/dev/" sw.device then 255 [ "Setting the swap size of block device ${sw.device} has no effect" ] 256 else 257 [ ] 258 ) config.swapDevices; 259 260 system.requiredKernelConfig = [ 261 (config.lib.kernelConfig.isYes "SWAP") 262 ]; 263 264 # Create missing swapfiles. 265 systemd.services = 266 let 267 createSwapDevice = 268 sw: 269 let 270 realDevice' = utils.escapeSystemdPath sw.realDevice; 271 btrfsInSystem = config.boot.supportedFilesystems.btrfs or false; 272 in 273 lib.nameValuePair "mkswap-${sw.deviceName}" { 274 description = "Initialisation of swap device ${sw.device}"; 275 # The mkswap service fails for file-backed swap devices if the 276 # loop module has not been loaded before the service runs. 277 # We add an ordering constraint to run after systemd-modules-load to 278 # avoid this race condition. 279 after = [ "systemd-modules-load.service" ]; 280 wantedBy = [ "${realDevice'}.swap" ]; 281 requiredBy = lib.optionals sw.randomEncryption.enable [ "${realDevice'}.swap" ]; 282 before = [ 283 "${realDevice'}.swap" 284 "shutdown.target" 285 ]; 286 conflicts = [ "shutdown.target" ]; 287 path = [ 288 pkgs.util-linux 289 pkgs.e2fsprogs 290 ] 291 ++ lib.optional btrfsInSystem pkgs.btrfs-progs 292 ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup; 293 294 environment.DEVICE = sw.device; 295 296 script = '' 297 ${lib.optionalString (sw.size != null) '' 298 currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 )) 299 if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then 300 if [[ $(stat -f -c %T $(dirname "$DEVICE")) == "btrfs" ]]; then 301 # Use btrfs mkswapfile to speed up the creation of swapfile. 302 rm -f "$DEVICE" 303 btrfs filesystem mkswapfile --size "${toString sw.size}M" --uuid clear "$DEVICE" 304 else 305 # Disable CoW for CoW based filesystems. 306 truncate --size 0 "$DEVICE" 307 chattr +C "$DEVICE" 2>/dev/null || true 308 309 echo "Creating swap file using dd and mkswap." 310 dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress 311 ${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"} 312 fi 313 fi 314 ''} 315 ${lib.optionalString sw.randomEncryption.enable '' 316 cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \ 317 ${ 318 lib.concatStringsSep " \\\n" ( 319 lib.flatten [ 320 (lib.optional ( 321 sw.randomEncryption.sectorSize != null 322 ) "--sector-size=${toString sw.randomEncryption.sectorSize}") 323 (lib.optional ( 324 sw.randomEncryption.keySize != null 325 ) "--key-size=${toString sw.randomEncryption.keySize}") 326 (lib.optional sw.randomEncryption.allowDiscards "--allow-discards") 327 ] 328 ) 329 } ${sw.device} ${sw.deviceName} 330 mkswap ${sw.realDevice} 331 ''} 332 ''; 333 334 unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ]; 335 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 336 serviceConfig = { 337 Type = "oneshot"; 338 RemainAfterExit = sw.randomEncryption.enable; 339 UMask = "0177"; 340 ExecStop = lib.optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}"; 341 }; 342 restartIfChanged = false; 343 }; 344 345 in 346 lib.listToAttrs ( 347 lib.map createSwapDevice ( 348 lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices 349 ) 350 ); 351 352 }; 353 354}