at 25.11-pre 11 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 megabytes). 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 in 272 lib.nameValuePair "mkswap-${sw.deviceName}" { 273 description = "Initialisation of swap device ${sw.device}"; 274 # The mkswap service fails for file-backed swap devices if the 275 # loop module has not been loaded before the service runs. 276 # We add an ordering constraint to run after systemd-modules-load to 277 # avoid this race condition. 278 after = [ "systemd-modules-load.service" ]; 279 wantedBy = [ "${realDevice'}.swap" ]; 280 before = [ 281 "${realDevice'}.swap" 282 "shutdown.target" 283 ]; 284 conflicts = [ "shutdown.target" ]; 285 path = [ 286 pkgs.util-linux 287 pkgs.e2fsprogs 288 ] ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup; 289 290 environment.DEVICE = sw.device; 291 292 script = '' 293 ${lib.optionalString (sw.size != null) '' 294 currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 )) 295 if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then 296 # Disable CoW for CoW based filesystems like BTRFS. 297 truncate --size 0 "$DEVICE" 298 chattr +C "$DEVICE" 2>/dev/null || true 299 300 echo "Creating swap file using dd and mkswap." 301 dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress 302 ${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"} 303 fi 304 ''} 305 ${lib.optionalString sw.randomEncryption.enable '' 306 cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \ 307 ${ 308 lib.concatStringsSep " \\\n" ( 309 lib.flatten [ 310 (lib.optional ( 311 sw.randomEncryption.sectorSize != null 312 ) "--sector-size=${toString sw.randomEncryption.sectorSize}") 313 (lib.optional ( 314 sw.randomEncryption.keySize != null 315 ) "--key-size=${toString sw.randomEncryption.keySize}") 316 (lib.optional sw.randomEncryption.allowDiscards "--allow-discards") 317 ] 318 ) 319 } ${sw.device} ${sw.deviceName} 320 mkswap ${sw.realDevice} 321 ''} 322 ''; 323 324 unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ]; 325 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 326 serviceConfig = { 327 Type = "oneshot"; 328 RemainAfterExit = sw.randomEncryption.enable; 329 UMask = "0177"; 330 ExecStop = lib.optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}"; 331 }; 332 restartIfChanged = false; 333 }; 334 335 in 336 lib.listToAttrs ( 337 lib.map createSwapDevice ( 338 lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices 339 ) 340 ); 341 342 }; 343 344}