1{ config, lib, pkgs, utils, ... }: 2 3with utils; 4with lib; 5 6let 7 8 swapCfg = {config, options, ...}: { 9 10 options = { 11 12 device = mkOption { 13 example = "/dev/sda3"; 14 type = types.str; 15 description = "Path of the device."; 16 }; 17 18 label = mkOption { 19 example = "swap"; 20 type = types.str; 21 description = '' 22 Label of the device. Can be used instead of <varname>device</varname>. 23 ''; 24 }; 25 26 size = mkOption { 27 default = null; 28 example = 2048; 29 type = types.nullOr types.int; 30 description = '' 31 If this option is set, device is interpreted as the 32 path of a swapfile that will be created automatically 33 with the indicated size (in megabytes) if it doesn't 34 exist. 35 ''; 36 }; 37 38 priority = mkOption { 39 default = null; 40 example = 2048; 41 type = types.nullOr types.int; 42 description = '' 43 Specify the priority of the swap device. Priority is a value between 0 and 32767. 44 Higher numbers indicate higher priority. 45 null lets the kernel choose a priority, which will show up as a negative value. 46 ''; 47 }; 48 49 randomEncryption = mkOption { 50 default = false; 51 type = types.bool; 52 description = '' 53 Encrypt swap device with a random key. This way you won't have a persistent swap device. 54 55 WARNING: Don't try to hibernate when you have at least one swap partition with 56 this option enabled! We have no way to set the partition into which hibernation image 57 is saved, so if your image ends up on an encrypted one you would lose it! 58 ''; 59 }; 60 61 deviceName = mkOption { 62 type = types.str; 63 internal = true; 64 }; 65 66 realDevice = mkOption { 67 type = types.path; 68 internal = true; 69 }; 70 71 }; 72 73 config = rec { 74 device = mkIf options.label.isDefined 75 "/dev/disk/by-label/${config.label}"; 76 deviceName = escapeSystemdPath config.device; 77 realDevice = if config.randomEncryption then "/dev/mapper/${deviceName}" else config.device; 78 }; 79 80 }; 81 82in 83 84{ 85 86 ###### interface 87 88 options = { 89 90 swapDevices = mkOption { 91 default = []; 92 example = [ 93 { device = "/dev/hda7"; } 94 { device = "/var/swapfile"; } 95 { label = "bigswap"; } 96 ]; 97 description = '' 98 The swap devices and swap files. These must have been 99 initialised using <command>mkswap</command>. Each element 100 should be an attribute set specifying either the path of the 101 swap device or file (<literal>device</literal>) or the label 102 of the swap device (<literal>label</literal>, see 103 <command>mkswap -L</command>). Using a label is 104 recommended. 105 ''; 106 107 type = types.listOf (types.submodule swapCfg); 108 }; 109 110 }; 111 112 config = mkIf ((length config.swapDevices) != 0) { 113 114 system.requiredKernelConfig = with config.lib.kernelConfig; [ 115 (isYes "SWAP") 116 ]; 117 118 # Create missing swapfiles. 119 # FIXME: support changing the size of existing swapfiles. 120 systemd.services = 121 let 122 123 createSwapDevice = sw: 124 assert sw.device != ""; 125 let realDevice' = escapeSystemdPath sw.realDevice; 126 in nameValuePair "mkswap-${sw.deviceName}" 127 { description = "Initialisation of swap device ${sw.device}"; 128 wantedBy = [ "${realDevice'}.swap" ]; 129 before = [ "${realDevice'}.swap" ]; 130 path = [ pkgs.utillinux ] ++ optional sw.randomEncryption pkgs.cryptsetup; 131 132 script = 133 '' 134 ${optionalString (sw.size != null) '' 135 if [ ! -e "${sw.device}" ]; then 136 fallocate -l ${toString sw.size}M "${sw.device}" || 137 dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size} 138 chmod 0600 ${sw.device} 139 ${optionalString (!sw.randomEncryption) "mkswap ${sw.realDevice}"} 140 fi 141 ''} 142 ${optionalString sw.randomEncryption '' 143 echo "secretkey" | cryptsetup luksFormat --batch-mode ${sw.device} 144 echo "secretkey" | cryptsetup luksOpen ${sw.device} ${sw.deviceName} 145 cryptsetup luksErase --batch-mode ${sw.device} 146 mkswap ${sw.realDevice} 147 ''} 148 ''; 149 150 unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ]; 151 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 152 serviceConfig.Type = "oneshot"; 153 serviceConfig.RemainAfterExit = sw.randomEncryption; 154 serviceConfig.ExecStop = optionalString sw.randomEncryption "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}"; 155 restartIfChanged = false; 156 }; 157 158 in listToAttrs (map createSwapDevice (filter (sw: sw.size != null || sw.randomEncryption) config.swapDevices)); 159 160 }; 161 162}