summary refs log tree commit diff
path: root/nixos/modules/config/swap.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/config/swap.nix')
-rw-r--r--nixos/modules/config/swap.nix249
1 files changed, 249 insertions, 0 deletions
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
new file mode 100644
index 00000000000..2b94b954cb8
--- /dev/null
+++ b/nixos/modules/config/swap.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, utils, ... }:
+
+with utils;
+with lib;
+
+let
+
+  randomEncryptionCoerce = enable: { inherit enable; };
+
+  randomEncryptionOpts = { ... }: {
+
+    options = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Encrypt swap device with a random key. This way you won't have a persistent swap device.
+
+          WARNING: Don't try to hibernate when you have at least one swap partition with
+          this option enabled! We have no way to set the partition into which hibernation image
+          is saved, so if your image ends up on an encrypted one you would lose it!
+
+          WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
+          when using randomEncryption as the UUIDs and labels will get erased on every boot when
+          the partition is encrypted. Best to use /dev/disk/by-partuuid/…
+        '';
+      };
+
+      cipher = mkOption {
+        default = "aes-xts-plain64";
+        example = "serpent-xts-plain64";
+        type = types.str;
+        description = ''
+          Use specified cipher for randomEncryption.
+
+          Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
+        '';
+      };
+
+      source = mkOption {
+        default = "/dev/urandom";
+        example = "/dev/random";
+        type = types.str;
+        description = ''
+          Define the source of randomness to obtain a random key for encryption.
+        '';
+      };
+
+      allowDiscards = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to allow TRIM requests to the underlying device. This option
+          has security implications; please read the LUKS documentation before
+          activating it.
+        '';
+      };
+    };
+
+  };
+
+  swapCfg = {config, options, ...}: {
+
+    options = {
+
+      device = mkOption {
+        example = "/dev/sda3";
+        type = types.str;
+        description = "Path of the device or swap file.";
+      };
+
+      label = mkOption {
+        example = "swap";
+        type = types.str;
+        description = ''
+          Label of the device.  Can be used instead of <varname>device</varname>.
+        '';
+      };
+
+      size = mkOption {
+        default = null;
+        example = 2048;
+        type = types.nullOr types.int;
+        description = ''
+          If this option is set, ‘device’ is interpreted as the
+          path of a swapfile that will be created automatically
+          with the indicated size (in megabytes).
+        '';
+      };
+
+      priority = mkOption {
+        default = null;
+        example = 2048;
+        type = types.nullOr types.int;
+        description = ''
+          Specify the priority of the swap device. Priority is a value between 0 and 32767.
+          Higher numbers indicate higher priority.
+          null lets the kernel choose a priority, which will show up as a negative value.
+        '';
+      };
+
+      randomEncryption = mkOption {
+        default = false;
+        example = {
+          enable = true;
+          cipher = "serpent-xts-plain64";
+          source = "/dev/random";
+        };
+        type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
+        description = ''
+          Encrypt swap device with a random key. This way you won't have a persistent swap device.
+
+          HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
+
+          WARNING: Don't try to hibernate when you have at least one swap partition with
+          this option enabled! We have no way to set the partition into which hibernation image
+          is saved, so if your image ends up on an encrypted one you would lose it!
+
+          WARNING #2: Do not use /dev/disk/by-uuid/… or /dev/disk/by-label/… as your swap device
+          when using randomEncryption as the UUIDs and labels will get erased on every boot when
+          the partition is encrypted. Best to use /dev/disk/by-partuuid/…
+        '';
+      };
+
+      discardPolicy = mkOption {
+        default = null;
+        example = "once";
+        type = types.nullOr (types.enum ["once" "pages" "both" ]);
+        description = ''
+          Specify the discard policy for the swap device. If "once", then the
+          whole swap space is discarded at swapon invocation. If "pages",
+          asynchronous discard on freed pages is performed, before returning to
+          the available pages pool. With "both", both policies are activated.
+          See swapon(8) for more information.
+        '';
+      };
+
+      options = mkOption {
+        default = [ "defaults" ];
+        example = [ "nofail" ];
+        type = types.listOf types.nonEmptyStr;
+        description = ''
+          Options used to mount the swap.
+        '';
+      };
+
+      deviceName = mkOption {
+        type = types.str;
+        internal = true;
+      };
+
+      realDevice = mkOption {
+        type = types.path;
+        internal = true;
+      };
+
+    };
+
+    config = rec {
+      device = mkIf options.label.isDefined
+        "/dev/disk/by-label/${config.label}";
+      deviceName = lib.replaceChars ["\\"] [""] (escapeSystemdPath config.device);
+      realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
+    };
+
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    swapDevices = mkOption {
+      default = [];
+      example = [
+        { device = "/dev/hda7"; }
+        { device = "/var/swapfile"; }
+        { label = "bigswap"; }
+      ];
+      description = ''
+        The swap devices and swap files.  These must have been
+        initialised using <command>mkswap</command>.  Each element
+        should be an attribute set specifying either the path of the
+        swap device or file (<literal>device</literal>) or the label
+        of the swap device (<literal>label</literal>, see
+        <command>mkswap -L</command>).  Using a label is
+        recommended.
+      '';
+
+      type = types.listOf (types.submodule swapCfg);
+    };
+
+  };
+
+  config = mkIf ((length config.swapDevices) != 0) {
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "SWAP")
+    ];
+
+    # Create missing swapfiles.
+    systemd.services =
+      let
+
+        createSwapDevice = sw:
+          assert sw.device != "";
+          assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-uuid"  sw.device);
+          assert !(sw.randomEncryption.enable && lib.hasPrefix "/dev/disk/by-label" sw.device);
+          let realDevice' = escapeSystemdPath sw.realDevice;
+          in nameValuePair "mkswap-${sw.deviceName}"
+          { description = "Initialisation of swap device ${sw.device}";
+            wantedBy = [ "${realDevice'}.swap" ];
+            before = [ "${realDevice'}.swap" ];
+            path = [ pkgs.util-linux ] ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+
+            script =
+              ''
+                ${optionalString (sw.size != null) ''
+                  currentSize=$(( $(stat -c "%s" "${sw.device}" 2>/dev/null || echo 0) / 1024 / 1024 ))
+                  if [ "${toString sw.size}" != "$currentSize" ]; then
+                    dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
+                    chmod 0600 ${sw.device}
+                    ${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
+                  fi
+                ''}
+                ${optionalString sw.randomEncryption.enable ''
+                  cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
+                    ${optionalString sw.randomEncryption.allowDiscards "--allow-discards"} ${sw.device} ${sw.deviceName}
+                  mkswap ${sw.realDevice}
+                ''}
+              '';
+
+            unitConfig.RequiresMountsFor = [ "${dirOf sw.device}" ];
+            unitConfig.DefaultDependencies = false; # needed to prevent a cycle
+            serviceConfig.Type = "oneshot";
+            serviceConfig.RemainAfterExit = sw.randomEncryption.enable;
+            serviceConfig.ExecStop = optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
+            restartIfChanged = false;
+          };
+
+      in listToAttrs (map createSwapDevice (filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));
+
+  };
+
+}