refactor: moved modules to legacy-modules
This commit is contained in:
parent
d646b954ac
commit
db7ac35613
233 changed files with 5 additions and 5 deletions
13
legacy-modules/nixos-modules/storage/default.nix
Normal file
13
legacy-modules/nixos-modules/storage/default.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{...}: {
|
||||
# TODO: we should have an impermanence module for home manager that proxies its values namespaced to the user down here that matches the same interface
|
||||
|
||||
# TODO: we should have a way of enabling impermanence for a systemd config
|
||||
# these should have an option to put their folder into their own dataset (this needs to support private vs non private)
|
||||
# options for features that can be added to the dataset
|
||||
|
||||
imports = [
|
||||
./impermanence.nix
|
||||
./zfs.nix
|
||||
./storage.nix
|
||||
];
|
||||
}
|
||||
142
legacy-modules/nixos-modules/storage/impermanence.nix
Normal file
142
legacy-modules/nixos-modules/storage/impermanence.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
args @ {
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
datasetSubmodules = (import ./submodules/dataset.nix) args;
|
||||
impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args;
|
||||
|
||||
permissionsToMode = permissions: let
|
||||
permSetToDigit = permSet:
|
||||
(
|
||||
if permSet.read
|
||||
then 4
|
||||
else 0
|
||||
)
|
||||
+ (
|
||||
if permSet.write
|
||||
then 2
|
||||
else 0
|
||||
)
|
||||
+ (
|
||||
if permSet.execute
|
||||
then 1
|
||||
else 0
|
||||
);
|
||||
|
||||
ownerDigit = permSetToDigit permissions.owner.permissions;
|
||||
groupDigit = permSetToDigit permissions.group.permissions;
|
||||
otherDigit = permSetToDigit permissions.other.permissions;
|
||||
in
|
||||
toString ownerDigit + toString groupDigit + toString otherDigit;
|
||||
|
||||
# Get the option names from both submodules to automatically determine which are impermanence-specific
|
||||
regularDatasetEval = lib.evalModules {
|
||||
modules = [datasetSubmodules];
|
||||
specialArgs = args;
|
||||
};
|
||||
impermanenceDatasetEval = lib.evalModules {
|
||||
modules = [impermanenceDatasetSubmodule];
|
||||
specialArgs = args;
|
||||
};
|
||||
|
||||
regularDatasetOptions = builtins.attrNames regularDatasetEval.options;
|
||||
impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options;
|
||||
|
||||
# Find options that are only in impermanence datasets (not in regular ZFS datasets)
|
||||
impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions;
|
||||
in {
|
||||
options.storage = {
|
||||
impermanence = {
|
||||
enable = lib.mkEnableOption "should impermanence be enabled for this system";
|
||||
|
||||
datasets = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule);
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.storage.zfs.enable;
|
||||
message = "storage.impermanence can not be used without storage.zfs.";
|
||||
}
|
||||
];
|
||||
|
||||
system.activationScripts = {
|
||||
# fixes issues with /var/lib/private not having the correct permissions https://github.com/nix-community/impermanence/issues/254
|
||||
"createPersistentStorageDirs".deps = ["var-lib-private-permissions" "users" "groups"];
|
||||
|
||||
"var-lib-private-permissions" = lib.mkIf config.storage.generateBase {
|
||||
deps = ["specialfs"];
|
||||
text = ''
|
||||
mkdir -p /persist/replicate/system/root/var/lib/private
|
||||
chmod 0700 /persist/replicate/system/root/var/lib/private
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
# Suppress sudo lecture on every boot since impermanence wipes the lecture status file
|
||||
security.sudo.extraConfig = "Defaults lecture=never";
|
||||
|
||||
fileSystems =
|
||||
lib.mapAttrs' (
|
||||
datasetName: dataset:
|
||||
lib.nameValuePair "/${datasetName}" {
|
||||
device = "rpool/${datasetName}";
|
||||
fsType = "zfs";
|
||||
neededForBoot = true;
|
||||
}
|
||||
)
|
||||
(lib.filterAttrs (
|
||||
datasetName: dataset: dataset.impermanence.enable
|
||||
)
|
||||
config.storage.impermanence.datasets);
|
||||
|
||||
environment.persistence =
|
||||
lib.mapAttrs (datasetName: dataset: {
|
||||
enable = true;
|
||||
hideMounts = true;
|
||||
persistentStoragePath = "/${datasetName}";
|
||||
directories = lib.mapAttrsToList (path: dirConfig: {
|
||||
directory = path;
|
||||
user = dirConfig.owner.name;
|
||||
group = dirConfig.group.name;
|
||||
mode = permissionsToMode dirConfig;
|
||||
}) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories);
|
||||
files = lib.mapAttrsToList (path: fileConfig: {
|
||||
file = path;
|
||||
parentDirectory = {
|
||||
user = fileConfig.owner.name;
|
||||
group = fileConfig.group.name;
|
||||
mode = permissionsToMode fileConfig;
|
||||
};
|
||||
}) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files);
|
||||
})
|
||||
(lib.filterAttrs (
|
||||
datasetName: dataset: let
|
||||
enabledDirectories = lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories;
|
||||
enabledFiles = lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files;
|
||||
in
|
||||
(enabledDirectories != {}) || (enabledFiles != {})
|
||||
)
|
||||
(lib.filterAttrs (
|
||||
datasetName: dataset: dataset.impermanence.enable
|
||||
)
|
||||
config.storage.impermanence.datasets));
|
||||
}
|
||||
(lib.mkIf config.storage.zfs.enable {
|
||||
storage.zfs.datasets =
|
||||
lib.mapAttrs (
|
||||
datasetName: dataset:
|
||||
builtins.removeAttrs dataset impermanenceOnlyOptions
|
||||
)
|
||||
config.storage.impermanence.datasets;
|
||||
})
|
||||
]);
|
||||
}
|
||||
216
legacy-modules/nixos-modules/storage/storage.nix
Normal file
216
legacy-modules/nixos-modules/storage/storage.nix
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
args @ {
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
datasetSubmodule = (import ./submodules/dataset.nix) args;
|
||||
impermanenceDatasetSubmodule = (import ./submodules/impermanenceDataset.nix) args;
|
||||
|
||||
# Get the option names from both submodules to automatically determine which are impermanence-specific
|
||||
regularDatasetEval = lib.evalModules {
|
||||
modules = [datasetSubmodule];
|
||||
specialArgs = args;
|
||||
};
|
||||
impermanenceDatasetEval = lib.evalModules {
|
||||
modules = [impermanenceDatasetSubmodule];
|
||||
specialArgs = args;
|
||||
};
|
||||
|
||||
regularDatasetOptions = builtins.attrNames regularDatasetEval.options;
|
||||
impermanenceDatasetOptions = builtins.attrNames impermanenceDatasetEval.options;
|
||||
|
||||
# Find options that are only in impermanence datasets (not in regular ZFS datasets)
|
||||
impermanenceOnlyOptions = lib.lists.subtractLists regularDatasetOptions impermanenceDatasetOptions;
|
||||
in {
|
||||
options.storage = {
|
||||
generateBase = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
When enabled, enables automatic generation of base datasets (ephemeral, local, replicate roots).
|
||||
This allows manual definition of datasets matching an existing system layout for migration purposes.
|
||||
'';
|
||||
};
|
||||
datasets = {
|
||||
ephemeral = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule datasetSubmodule);
|
||||
default = {};
|
||||
};
|
||||
local = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule);
|
||||
default = {};
|
||||
};
|
||||
replicate = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule);
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf (config.storage.zfs.enable && config.storage.generateBase) {
|
||||
# Create ZFS datasets based on storage.datasets configuration
|
||||
storage.datasets = {
|
||||
local = {
|
||||
"nix" = {
|
||||
impermanence.enable = false;
|
||||
type = "zfs_fs";
|
||||
mount = "/nix";
|
||||
snapshot = {
|
||||
autoSnapshot = false;
|
||||
};
|
||||
atime = "off";
|
||||
relatime = "off";
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(lib.mkIf (config.storage.zfs.enable && config.storage.impermanence.enable && config.storage.generateBase) {
|
||||
storage.datasets = {
|
||||
ephemeral = {
|
||||
"" = {
|
||||
type = "zfs_fs";
|
||||
mount = null;
|
||||
};
|
||||
"system/root" = {
|
||||
type = "zfs_fs";
|
||||
mount = "/";
|
||||
snapshot = {
|
||||
blankSnapshot = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
# TODO: can we auto set the mount points on these to just be `"/persist/local/${name}"`
|
||||
local = {
|
||||
"" = {
|
||||
mount = "/persist/local";
|
||||
};
|
||||
};
|
||||
# TODO: can we auto set the mount points on these to just be `"/persist/replicate/${name}"`
|
||||
replicate = {
|
||||
"" = {
|
||||
mount = "/persist/replicate";
|
||||
};
|
||||
"system/root" = {
|
||||
mount = "/persist/replicate/system/root";
|
||||
snapshot = {
|
||||
autoSnapshot = true;
|
||||
};
|
||||
directories = {
|
||||
"/var/lib/nixos".enable = true;
|
||||
"/var/lib/systemd/coredump".enable = true;
|
||||
};
|
||||
files = {
|
||||
"/etc/machine-id".enable = true;
|
||||
};
|
||||
};
|
||||
"home" = {
|
||||
mount = "/persist/replicate/home";
|
||||
snapshot = {
|
||||
autoSnapshot = true;
|
||||
};
|
||||
};
|
||||
"system/var/log" = {
|
||||
type = "zfs_fs";
|
||||
directories = {
|
||||
"/var/log".enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
storage.zfs.datasets = lib.mkMerge [
|
||||
(lib.mapAttrs' (name: dataset: {
|
||||
name =
|
||||
if name == ""
|
||||
then "ephemeral"
|
||||
else "ephemeral/${name}";
|
||||
value = dataset;
|
||||
})
|
||||
config.storage.datasets.ephemeral)
|
||||
];
|
||||
|
||||
boot.initrd.postResumeCommands = lib.mkAfter ''
|
||||
zfs rollback -r rpool/ephemeral/system/root@blank
|
||||
'';
|
||||
|
||||
storage.impermanence.datasets = lib.mkMerge [
|
||||
(lib.mapAttrs' (name: dataset: {
|
||||
name =
|
||||
if name == ""
|
||||
then "persist/local"
|
||||
else "persist/local/${name}";
|
||||
value = dataset;
|
||||
})
|
||||
config.storage.datasets.local)
|
||||
(lib.mapAttrs' (name: dataset: {
|
||||
name =
|
||||
if name == ""
|
||||
then "persist/replicate"
|
||||
else "persist/replicate/${name}";
|
||||
value = dataset;
|
||||
})
|
||||
config.storage.datasets.replicate)
|
||||
];
|
||||
})
|
||||
(lib.mkIf (config.storage.zfs.enable && !config.storage.impermanence.enable && config.storage.generateBase) {
|
||||
storage.datasets = {
|
||||
# Base organizational datasets (only needed when impermanence is disabled)
|
||||
local = {
|
||||
"" = {
|
||||
type = "zfs_fs";
|
||||
mount = null;
|
||||
};
|
||||
"root" = {
|
||||
type = "zfs_fs";
|
||||
mount = "/";
|
||||
compression = "lz4";
|
||||
acltype = "posixacl";
|
||||
relatime = "on";
|
||||
xattr = "sa";
|
||||
snapshot = {
|
||||
autoSnapshot = true;
|
||||
blankSnapshot = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
replicate = {
|
||||
"" = {
|
||||
type = "zfs_fs";
|
||||
mount = null;
|
||||
};
|
||||
"system/var/log" = {
|
||||
type = "zfs_fs";
|
||||
mount = "/var/log";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
storage.zfs.datasets = lib.mkMerge [
|
||||
(lib.mapAttrs' (name: dataset: {
|
||||
name =
|
||||
if name == ""
|
||||
then "persist/local"
|
||||
else "persist/local/${name}";
|
||||
value = builtins.removeAttrs dataset impermanenceOnlyOptions;
|
||||
})
|
||||
config.storage.datasets.local)
|
||||
(lib.mapAttrs' (name: dataset: {
|
||||
name =
|
||||
if name == ""
|
||||
then "persist/replicate"
|
||||
else "persist/replicate/${name}";
|
||||
value = builtins.removeAttrs dataset impermanenceOnlyOptions;
|
||||
})
|
||||
config.storage.datasets.replicate)
|
||||
];
|
||||
})
|
||||
];
|
||||
|
||||
# TODO: set up datasets for systemd services that want a dataset created
|
||||
# TODO: home-manager.users.<user>.storage.impermanence.enable
|
||||
# is false then persist the entire directory of the user
|
||||
# if true persist home-manager.users.<user>.storage.impermanence.datasets
|
||||
# TODO: systemd.services.<name>.storage.datasets persists
|
||||
# TODO: configure other needed storage modes here
|
||||
}
|
||||
86
legacy-modules/nixos-modules/storage/submodules/dataset.nix
Normal file
86
legacy-modules/nixos-modules/storage/submodules/dataset.nix
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{lib, ...}: {name, ...}: {
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum ["zfs_fs" "zfs_volume"];
|
||||
default = "zfs_fs";
|
||||
description = "Type of ZFS dataset (filesystem or volume)";
|
||||
};
|
||||
|
||||
acltype = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]);
|
||||
default = null;
|
||||
description = "Access control list type";
|
||||
};
|
||||
|
||||
relatime = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["on" "off"]);
|
||||
default = null;
|
||||
description = "Controls when access time is updated";
|
||||
};
|
||||
|
||||
atime = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["on" "off"]);
|
||||
default = null;
|
||||
description = "Controls whether access time is updated";
|
||||
};
|
||||
|
||||
xattr = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["on" "off" "sa" "dir"]);
|
||||
default = null;
|
||||
description = "Extended attribute storage method";
|
||||
};
|
||||
|
||||
compression = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]);
|
||||
default = null;
|
||||
description = "Compression algorithm to use";
|
||||
};
|
||||
|
||||
sync = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]);
|
||||
default = null;
|
||||
description = "Synchronous write behavior";
|
||||
};
|
||||
|
||||
mount = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "Controls the mount point used for this file system";
|
||||
default = null;
|
||||
};
|
||||
|
||||
encryption = {
|
||||
enable = lib.mkEnableOption "should encryption be enabled";
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"];
|
||||
description = "What encryption type to use";
|
||||
};
|
||||
keyformat = lib.mkOption {
|
||||
type = lib.types.enum ["raw" "hex" "passphrase"];
|
||||
description = "Format of the encryption key";
|
||||
};
|
||||
keylocation = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location of the encryption key";
|
||||
};
|
||||
};
|
||||
|
||||
snapshot = {
|
||||
# This option should set this option flag
|
||||
autoSnapshot = lib.mkEnableOption "Enable automatic snapshots for this dataset";
|
||||
# Creates a blank snapshot in the post create hook for rollback purposes
|
||||
blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook";
|
||||
};
|
||||
|
||||
recordSize = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Suggested block size for files in the file system";
|
||||
};
|
||||
|
||||
postCreateHook = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Script to run after dataset creation";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
args @ {lib, ...}: {name, ...}: let
|
||||
datasetSubmodule = (import ./dataset.nix) args;
|
||||
pathPermissions = {
|
||||
read = lib.mkEnableOption "should the path have read permissions";
|
||||
write = lib.mkEnableOption "should the path have read permissions";
|
||||
execute = lib.mkEnableOption "should the path have read permissions";
|
||||
};
|
||||
pathTypeSubmodule = {name, ...}: {
|
||||
options = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
owner = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
permissions = pathPermissions;
|
||||
};
|
||||
group = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
permissions = pathPermissions;
|
||||
};
|
||||
other = {
|
||||
permissions = pathPermissions;
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
imports = [
|
||||
datasetSubmodule
|
||||
];
|
||||
|
||||
options = {
|
||||
files = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule);
|
||||
default = {};
|
||||
};
|
||||
directories = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule);
|
||||
default = {};
|
||||
};
|
||||
impermanence.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
mount = lib.mkDefault "/${name}";
|
||||
};
|
||||
}
|
||||
347
legacy-modules/nixos-modules/storage/zfs.nix
Normal file
347
legacy-modules/nixos-modules/storage/zfs.nix
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
args @ {
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
datasetSubmodule = (import ./submodules/dataset.nix) args;
|
||||
|
||||
# Hash function for disk names (max 27 chars to fit GPT limitations)
|
||||
hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive));
|
||||
|
||||
# Map "stripe" to "" for disko compatibility (disko uses "" for stripe mode)
|
||||
diskoPoolMode =
|
||||
if config.storage.zfs.pool.mode == "stripe"
|
||||
then ""
|
||||
else config.storage.zfs.pool.mode;
|
||||
|
||||
# Helper to flatten vdevs into list of devices with names
|
||||
allVdevDevices = lib.lists.flatten (builtins.map (
|
||||
vdev:
|
||||
builtins.map (
|
||||
device:
|
||||
lib.attrsets.nameValuePair (hashDisk device.device) device
|
||||
)
|
||||
vdev
|
||||
)
|
||||
config.storage.zfs.pool.vdevs);
|
||||
|
||||
# Cache devices with names
|
||||
allCacheDevices = builtins.map (
|
||||
device:
|
||||
lib.attrsets.nameValuePair (hashDisk device.device) device
|
||||
) (config.storage.zfs.pool.cache);
|
||||
|
||||
# All devices (vdevs + cache)
|
||||
allDevices = allVdevDevices ++ allCacheDevices;
|
||||
|
||||
# Boot devices - filter devices that have boot = true
|
||||
bootDevices = builtins.filter (device: device.value.boot) allDevices;
|
||||
|
||||
# Helper function to convert dataset options to ZFS properties
|
||||
datasetToZfsOptions = dataset: let
|
||||
baseOptions =
|
||||
(lib.attrsets.optionalAttrs (dataset.acltype != null) {acltype = dataset.acltype;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.relatime != null) {relatime = dataset.relatime;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.atime != null) {atime = dataset.atime;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.xattr != null) {xattr = dataset.xattr;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.compression != null) {compression = dataset.compression;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.sync != null) {sync = dataset.sync;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.recordSize != null) {recordSize = dataset.recordSize;});
|
||||
|
||||
encryptionOptions = lib.attrsets.optionalAttrs (dataset.encryption.enable) (
|
||||
(lib.attrsets.optionalAttrs (dataset.encryption ? type) {encryption = dataset.encryption.type;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.encryption ? keyformat) {keyformat = dataset.encryption.keyformat;})
|
||||
// (lib.attrsets.optionalAttrs (dataset.encryption ? keylocation) {keylocation = dataset.encryption.keylocation;})
|
||||
);
|
||||
|
||||
mountOptions = lib.attrsets.optionalAttrs (dataset ? mount && dataset.mount ? enable) (
|
||||
if builtins.isBool dataset.mount.enable
|
||||
then {
|
||||
canmount =
|
||||
if dataset.mount.enable
|
||||
then "on"
|
||||
else "off";
|
||||
}
|
||||
else {canmount = dataset.mount.enable;}
|
||||
);
|
||||
|
||||
snapshotOptions = lib.attrsets.optionalAttrs (dataset ? snapshot && dataset.snapshot ? autoSnapshot) {
|
||||
"com.sun:auto-snapshot" =
|
||||
if dataset.snapshot.autoSnapshot
|
||||
then "true"
|
||||
else "false";
|
||||
};
|
||||
in
|
||||
baseOptions // encryptionOptions // mountOptions // snapshotOptions;
|
||||
|
||||
# Helper to generate post create hooks
|
||||
generatePostCreateHook = name: dataset:
|
||||
dataset.postCreateHook
|
||||
+ (lib.optionalString dataset.snapshot.blankSnapshot ''
|
||||
zfs snapshot rpool/${name}@blank
|
||||
'');
|
||||
|
||||
# Convert datasets to disko format
|
||||
convertedDatasets = builtins.listToAttrs (
|
||||
(lib.attrsets.mapAttrsToList (
|
||||
name: dataset:
|
||||
lib.attrsets.nameValuePair name {
|
||||
type = dataset.type;
|
||||
options = datasetToZfsOptions dataset;
|
||||
mountpoint = dataset.mount or null;
|
||||
postCreateHook = generatePostCreateHook name dataset;
|
||||
}
|
||||
)
|
||||
config.storage.zfs.datasets)
|
||||
++ (lib.optional (config.storage.zfs.rootDataset != null) (
|
||||
lib.attrsets.nameValuePair "" {
|
||||
type = config.storage.zfs.rootDataset.type;
|
||||
options = datasetToZfsOptions config.storage.zfs.rootDataset;
|
||||
mountpoint = config.storage.zfs.rootDataset.mount or null;
|
||||
postCreateHook = generatePostCreateHook "" config.storage.zfs.rootDataset;
|
||||
}
|
||||
))
|
||||
);
|
||||
in {
|
||||
options.storage = {
|
||||
zfs = {
|
||||
enable = lib.mkEnableOption "Should zfs be enabled on this system.";
|
||||
|
||||
notifications = {
|
||||
enable = lib.mkEnableOption "are notifications enabled";
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what is the host that we are going to send the email to";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "what port is the host using to receive mail on";
|
||||
};
|
||||
to = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what account is the email going to be sent to";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "what user is the email going to be set from";
|
||||
};
|
||||
tokenFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "file containing the password to be used by msmtp for notifications";
|
||||
};
|
||||
};
|
||||
|
||||
pool = let
|
||||
deviceType =
|
||||
lib.types.coercedTo lib.types.str (device: {
|
||||
device = device;
|
||||
boot = false;
|
||||
}) (lib.types.submodule {
|
||||
options = {
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
boot = lib.mkEnableOption "should this device be a boot device";
|
||||
};
|
||||
});
|
||||
in {
|
||||
encryption = {
|
||||
enable = lib.mkEnableOption "Should encryption be enabled on this pool.";
|
||||
keyformat = lib.mkOption {
|
||||
type = lib.types.enum ["raw" "hex" "passphrase"];
|
||||
default = "hex";
|
||||
description = "Format of the encryption key";
|
||||
};
|
||||
keylocation = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "prompt";
|
||||
description = "Location of the encryption key";
|
||||
};
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum ["stripe" "mirror" "raidz1" "raidz2" "raidz3"];
|
||||
default = "raidz2";
|
||||
description = "ZFS redundancy mode for the pool";
|
||||
};
|
||||
bootPartitionSize = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "2G";
|
||||
description = "Size of the boot partition on boot drives";
|
||||
};
|
||||
vdevs = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.listOf deviceType);
|
||||
default = [];
|
||||
description = "List of vdevs, where each vdev is a list of devices";
|
||||
};
|
||||
cache = lib.mkOption {
|
||||
type = lib.types.listOf deviceType;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
|
||||
rootDataset = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.submodule datasetSubmodule);
|
||||
description = "Root ZFS dataset to create";
|
||||
default = null;
|
||||
};
|
||||
|
||||
datasets = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule datasetSubmodule);
|
||||
description = "Additional ZFS datasets to create";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [
|
||||
{
|
||||
# Assertion that we have at least one boot device
|
||||
assertions = [
|
||||
{
|
||||
assertion = (builtins.length bootDevices) > 0;
|
||||
message = "ZFS configuration requires at least one boot device. Set boot = true for at least one device in your vdevs or cache.";
|
||||
}
|
||||
];
|
||||
|
||||
# # Warning about disk/dataset mismatches - these would be runtime checks
|
||||
# warnings = let
|
||||
# configuredDisks = builtins.map (device: device.device) (builtins.map (dev: dev.value) allDevices);
|
||||
# diskWarnings =
|
||||
# lib.optional (config.storage.zfs.enable)
|
||||
# "ZFS: Please ensure the following disks are available on your system: ${builtins.concatStringsSep ", " configuredDisks}";
|
||||
|
||||
# configuredDatasets = builtins.attrNames config.storage.zfs.datasets;
|
||||
# datasetWarnings =
|
||||
# lib.optional (config.storage.zfs.enable && (builtins.length configuredDatasets) > 0)
|
||||
# "ZFS: Configured datasets: ${builtins.concatStringsSep ", " configuredDatasets}. Ensure these match your intended ZFS layout.";
|
||||
# in
|
||||
# diskWarnings ++ datasetWarnings;
|
||||
|
||||
services.zfs = {
|
||||
autoScrub.enable = true;
|
||||
autoSnapshot.enable = true;
|
||||
};
|
||||
|
||||
# # Configure disko for ZFS setup
|
||||
disko.devices = {
|
||||
disk = builtins.listToAttrs (
|
||||
builtins.map (
|
||||
drive:
|
||||
lib.attrsets.nameValuePair (drive.name) {
|
||||
type = "disk";
|
||||
device = "/dev/disk/by-id/${drive.value.device}";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
ESP = lib.mkIf drive.value.boot {
|
||||
size = config.storage.zfs.pool.bootPartitionSize;
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
mountOptions = ["umask=0077"];
|
||||
};
|
||||
};
|
||||
zfs = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "zfs";
|
||||
pool = "rpool";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
allDevices
|
||||
);
|
||||
|
||||
zpool = {
|
||||
rpool = {
|
||||
type = "zpool";
|
||||
mode = {
|
||||
topology = {
|
||||
type = "topology";
|
||||
vdev =
|
||||
builtins.map (vdev: {
|
||||
mode = diskoPoolMode;
|
||||
members = builtins.map (device: hashDisk device.device) vdev;
|
||||
})
|
||||
config.storage.zfs.pool.vdevs;
|
||||
cache = builtins.map (device: hashDisk device.device) config.storage.zfs.pool.cache;
|
||||
};
|
||||
};
|
||||
|
||||
options = {
|
||||
ashift = "12";
|
||||
autotrim = "on";
|
||||
};
|
||||
|
||||
rootFsOptions =
|
||||
{
|
||||
canmount = "off";
|
||||
mountpoint = "none";
|
||||
xattr = "sa";
|
||||
acltype = "posixacl";
|
||||
relatime = "on";
|
||||
compression = "lz4";
|
||||
"com.sun:auto-snapshot" = "false";
|
||||
}
|
||||
// (lib.attrsets.optionalAttrs config.storage.zfs.pool.encryption.enable {
|
||||
encryption = "on";
|
||||
keyformat = config.storage.zfs.pool.encryption.keyformat;
|
||||
keylocation = config.storage.zfs.pool.encryption.keylocation;
|
||||
});
|
||||
|
||||
datasets = convertedDatasets;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
(lib.mkIf config.storage.zfs.notifications.enable {
|
||||
programs.msmtp = {
|
||||
enable = true;
|
||||
setSendmail = true;
|
||||
defaults = {
|
||||
aliases = "/etc/aliases";
|
||||
port = config.storage.zfs.notifications.port;
|
||||
tls_trust_file = "/etc/ssl/certs/ca-certificates.crt";
|
||||
tls = "on";
|
||||
auth = "login";
|
||||
tls_starttls = "off";
|
||||
};
|
||||
accounts = {
|
||||
zfs_notifications = {
|
||||
auth = true;
|
||||
tls = true;
|
||||
host = config.storage.zfs.notifications.host;
|
||||
passwordeval = "cat ${config.storage.zfs.notifications.tokenFile}";
|
||||
user = config.storage.zfs.notifications.user;
|
||||
from = config.storage.zfs.notifications.user;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.zfs = {
|
||||
zed = {
|
||||
enableMail = true;
|
||||
|
||||
settings = {
|
||||
ZED_DEBUG_LOG = "/tmp/zed.debug.log";
|
||||
ZED_EMAIL_ADDR = [config.storage.zfs.notifications.to];
|
||||
ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp";
|
||||
ZED_EMAIL_OPTS = "-a zfs_notifications @ADDRESS@";
|
||||
|
||||
ZED_NOTIFY_INTERVAL_SECS = 3600;
|
||||
ZED_NOTIFY_VERBOSE = true;
|
||||
|
||||
ZED_USE_ENCLOSURE_LEDS = true;
|
||||
ZED_SCRUB_AFTER_RESILVER = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue