2020-04-24 23:36:52 +00:00
|
|
|
|
{ config, lib, pkgs, utils, ... }:
|
|
|
|
|
|
|
|
|
|
with utils;
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
|
|
randomEncryptionCoerce = enable: { inherit enable; };
|
|
|
|
|
|
|
|
|
|
randomEncryptionOpts = { ... }: {
|
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
enable = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
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;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
Use specified cipher for randomEncryption.
|
|
|
|
|
|
|
|
|
|
Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-24 13:37:59 +00:00
|
|
|
|
keySize = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = "512";
|
|
|
|
|
type = types.nullOr types.int;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2023-05-24 13:37:59 +00:00
|
|
|
|
Set the encryption key size for the plain device.
|
|
|
|
|
|
|
|
|
|
If not specified, the amount of data to read from `source` will be
|
|
|
|
|
determined by cryptsetup.
|
|
|
|
|
|
|
|
|
|
See `cryptsetup-open(8)` for details.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sectorSize = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = "4096";
|
|
|
|
|
type = types.nullOr types.int;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2023-05-24 13:37:59 +00:00
|
|
|
|
Set the sector size for the plain encrypted device type.
|
|
|
|
|
|
|
|
|
|
If not specified, the default sector size is determined from the
|
|
|
|
|
underlying block device.
|
|
|
|
|
|
|
|
|
|
See `cryptsetup-open(8)` for details.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
|
source = mkOption {
|
|
|
|
|
default = "/dev/urandom";
|
|
|
|
|
example = "/dev/random";
|
|
|
|
|
type = types.str;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
Define the source of randomness to obtain a random key for encryption.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2021-12-06 16:07:01 +00:00
|
|
|
|
allowDiscards = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2021-12-06 16:07:01 +00:00
|
|
|
|
Whether to allow TRIM requests to the underlying device. This option
|
|
|
|
|
has security implications; please read the LUKS documentation before
|
|
|
|
|
activating it.
|
|
|
|
|
'';
|
|
|
|
|
};
|
2020-04-24 23:36:52 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
swapCfg = {config, options, ...}: {
|
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
device = mkOption {
|
|
|
|
|
example = "/dev/sda3";
|
2023-01-20 10:41:00 +00:00
|
|
|
|
type = types.nonEmptyStr;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = "Path of the device or swap file.";
|
2020-04-24 23:36:52 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
label = mkOption {
|
|
|
|
|
example = "swap";
|
|
|
|
|
type = types.str;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2022-09-09 14:08:57 +00:00
|
|
|
|
Label of the device. Can be used instead of {var}`device`.
|
2020-04-24 23:36:52 +00:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
size = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = 2048;
|
|
|
|
|
type = types.nullOr types.int;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
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;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
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);
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
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/…
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-28 23:13:55 +00:00
|
|
|
|
discardPolicy = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = "once";
|
|
|
|
|
type = types.nullOr (types.enum ["once" "pages" "both" ]);
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2021-06-28 23:13:55 +00:00
|
|
|
|
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.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-24 12:14:16 +00:00
|
|
|
|
options = mkOption {
|
|
|
|
|
default = [ "defaults" ];
|
|
|
|
|
example = [ "nofail" ];
|
|
|
|
|
type = types.listOf types.nonEmptyStr;
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2021-07-24 12:14:16 +00:00
|
|
|
|
Options used to mount the swap.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2020-04-24 23:36:52 +00:00
|
|
|
|
deviceName = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
realDevice = mkOption {
|
|
|
|
|
type = types.path;
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-24 13:37:59 +00:00
|
|
|
|
config = {
|
2020-04-24 23:36:52 +00:00
|
|
|
|
device = mkIf options.label.isDefined
|
|
|
|
|
"/dev/disk/by-label/${config.label}";
|
2022-12-17 10:02:37 +00:00
|
|
|
|
deviceName = lib.replaceStrings ["\\"] [""] (escapeSystemdPath config.device);
|
2023-05-24 13:37:59 +00:00
|
|
|
|
realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
|
2020-04-24 23:36:52 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
###### interface
|
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
swapDevices = mkOption {
|
|
|
|
|
default = [];
|
|
|
|
|
example = [
|
|
|
|
|
{ device = "/dev/hda7"; }
|
|
|
|
|
{ device = "/var/swapfile"; }
|
|
|
|
|
{ label = "bigswap"; }
|
|
|
|
|
];
|
2024-04-21 15:54:59 +00:00
|
|
|
|
description = ''
|
2020-04-24 23:36:52 +00:00
|
|
|
|
The swap devices and swap files. These must have been
|
2022-08-12 12:06:08 +00:00
|
|
|
|
initialised using {command}`mkswap`. Each element
|
2020-04-24 23:36:52 +00:00
|
|
|
|
should be an attribute set specifying either the path of the
|
2022-08-12 12:06:08 +00:00
|
|
|
|
swap device or file (`device`) or the label
|
|
|
|
|
of the swap device (`label`, see
|
|
|
|
|
{command}`mkswap -L`). Using a label is
|
2020-04-24 23:36:52 +00:00
|
|
|
|
recommended.
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
type = types.listOf (types.submodule swapCfg);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = mkIf ((length config.swapDevices) != 0) {
|
2023-01-20 10:41:00 +00:00
|
|
|
|
assertions = map (sw: {
|
|
|
|
|
assertion = sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
|
|
|
|
|
message = ''
|
|
|
|
|
You cannot use swap device "${sw.device}" with randomEncryption enabled.
|
|
|
|
|
The UUIDs and labels will get erased on every boot when the partition is encrypted.
|
|
|
|
|
Use /dev/disk/by-partuuid/… instead.
|
|
|
|
|
'';
|
|
|
|
|
}) config.swapDevices;
|
|
|
|
|
|
|
|
|
|
warnings =
|
|
|
|
|
concatMap (sw:
|
|
|
|
|
if sw.size != null && hasPrefix "/dev/" sw.device
|
|
|
|
|
then [ "Setting the swap size of block device ${sw.device} has no effect" ]
|
|
|
|
|
else [ ])
|
|
|
|
|
config.swapDevices;
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
|
|
system.requiredKernelConfig = with config.lib.kernelConfig; [
|
|
|
|
|
(isYes "SWAP")
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
# Create missing swapfiles.
|
|
|
|
|
systemd.services =
|
|
|
|
|
let
|
|
|
|
|
createSwapDevice = sw:
|
|
|
|
|
let realDevice' = escapeSystemdPath sw.realDevice;
|
|
|
|
|
in nameValuePair "mkswap-${sw.deviceName}"
|
|
|
|
|
{ description = "Initialisation of swap device ${sw.device}";
|
2023-07-15 17:15:38 +00:00
|
|
|
|
# The mkswap service fails for file-backed swap devices if the
|
|
|
|
|
# loop module has not been loaded before the service runs.
|
|
|
|
|
# We add an ordering constraint to run after systemd-modules-load to
|
|
|
|
|
# avoid this race condition.
|
|
|
|
|
after = [ "systemd-modules-load.service" ];
|
2020-04-24 23:36:52 +00:00
|
|
|
|
wantedBy = [ "${realDevice'}.swap" ];
|
2024-01-02 11:29:13 +00:00
|
|
|
|
before = [ "${realDevice'}.swap" "shutdown.target"];
|
|
|
|
|
conflicts = [ "shutdown.target" ];
|
2023-01-20 10:41:00 +00:00
|
|
|
|
path = [ pkgs.util-linux pkgs.e2fsprogs ]
|
|
|
|
|
++ optional sw.randomEncryption.enable pkgs.cryptsetup;
|
|
|
|
|
|
|
|
|
|
environment.DEVICE = sw.device;
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
|
|
script =
|
|
|
|
|
''
|
|
|
|
|
${optionalString (sw.size != null) ''
|
2023-01-20 10:41:00 +00:00
|
|
|
|
currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
|
|
|
|
|
if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
|
|
|
|
|
# Disable CoW for CoW based filesystems like BTRFS.
|
|
|
|
|
truncate --size 0 "$DEVICE"
|
|
|
|
|
chattr +C "$DEVICE" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size}
|
2020-04-24 23:36:52 +00:00
|
|
|
|
chmod 0600 ${sw.device}
|
|
|
|
|
${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
|
|
|
|
|
fi
|
|
|
|
|
''}
|
|
|
|
|
${optionalString sw.randomEncryption.enable ''
|
2021-12-06 16:07:01 +00:00
|
|
|
|
cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
|
2023-05-24 13:37:59 +00:00
|
|
|
|
${concatStringsSep " \\\n" (flatten [
|
|
|
|
|
(optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
|
|
|
|
|
(optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
|
|
|
|
|
(optional sw.randomEncryption.allowDiscards "--allow-discards")
|
|
|
|
|
])} ${sw.device} ${sw.deviceName}
|
2020-04-24 23:36:52 +00:00
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|