storage-refactor #9
5 changed files with 229 additions and 42 deletions
|
|
@ -70,6 +70,7 @@ in {
|
||||||
lib.mapAttrs (datasetName: dataset: {
|
lib.mapAttrs (datasetName: dataset: {
|
||||||
enable = true;
|
enable = true;
|
||||||
hideMounts = true;
|
hideMounts = true;
|
||||||
|
persistentStoragePath = "/${datasetName}";
|
||||||
directories = lib.mapAttrsToList (path: dirConfig: {
|
directories = lib.mapAttrsToList (path: dirConfig: {
|
||||||
directory = path;
|
directory = path;
|
||||||
user = dirConfig.owner.name;
|
user = dirConfig.owner.name;
|
||||||
|
|
@ -78,9 +79,11 @@ in {
|
||||||
}) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories);
|
}) (lib.filterAttrs (_: dirConfig: dirConfig.enable) dataset.directories);
|
||||||
files = lib.mapAttrsToList (path: fileConfig: {
|
files = lib.mapAttrsToList (path: fileConfig: {
|
||||||
file = path;
|
file = path;
|
||||||
user = fileConfig.owner.name;
|
parentDirectory = {
|
||||||
group = fileConfig.group.name;
|
user = fileConfig.owner.name;
|
||||||
mode = permissionsToMode fileConfig;
|
group = fileConfig.group.name;
|
||||||
|
mode = permissionsToMode fileConfig;
|
||||||
|
};
|
||||||
}) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files);
|
}) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files);
|
||||||
})
|
})
|
||||||
config.storage.impermanence.datasets;
|
config.storage.impermanence.datasets;
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,6 @@
|
||||||
autoSnapshot = false;
|
autoSnapshot = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"persist/system/root" = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mount = {
|
|
||||||
enable = true;
|
|
||||||
mountPoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(lib.mkIf (!config.storage.impermanence.enable) {
|
(lib.mkIf (!config.storage.impermanence.enable) {
|
||||||
|
|
@ -46,6 +39,10 @@
|
||||||
storage.zfs.datasets = {
|
storage.zfs.datasets = {
|
||||||
"persist/system/root" = {
|
"persist/system/root" = {
|
||||||
type = "zfs_fs";
|
type = "zfs_fs";
|
||||||
|
mount = {
|
||||||
|
enable = false;
|
||||||
|
mountPoint = "/";
|
||||||
|
};
|
||||||
snapshot = {
|
snapshot = {
|
||||||
autoSnapshot = true;
|
autoSnapshot = true;
|
||||||
};
|
};
|
||||||
|
|
@ -55,6 +52,10 @@
|
||||||
(lib.mkIf config.storage.impermanence.enable {
|
(lib.mkIf config.storage.impermanence.enable {
|
||||||
storage.impermanence.datasets = {
|
storage.impermanence.datasets = {
|
||||||
"persist/system/root" = {
|
"persist/system/root" = {
|
||||||
|
mount = {
|
||||||
|
enable = false;
|
||||||
|
mountPoint = "/";
|
||||||
|
};
|
||||||
directories = {
|
directories = {
|
||||||
"/var/lib/nixos".enable = true;
|
"/var/lib/nixos".enable = true;
|
||||||
"/var/lib/systemd/coredump".enable = true;
|
"/var/lib/systemd/coredump".enable = true;
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,12 @@
|
||||||
|
|
||||||
mount = {
|
mount = {
|
||||||
enable = lib.mkOption {
|
enable = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.either lib.types.bool (lib.types.enum ["on" "off" "noauto"]));
|
type = lib.types.either lib.types.bool (lib.types.enum ["on" "off" "noauto"]);
|
||||||
default = null;
|
default = true;
|
||||||
|
description = "Whether and how the dataset should be mounted";
|
||||||
};
|
};
|
||||||
mountPoint = lib.mkOption {
|
mountPoint = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.str;
|
||||||
default = null;
|
|
||||||
description = "Controls the mount point used for this file system";
|
description = "Controls the mount point used for this file system";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -57,18 +57,15 @@
|
||||||
encryption = {
|
encryption = {
|
||||||
enable = lib.mkEnableOption "should encryption be enabled";
|
enable = lib.mkEnableOption "should encryption be enabled";
|
||||||
type = lib.mkOption {
|
type = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"]);
|
type = lib.types.enum ["aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"];
|
||||||
default = null;
|
|
||||||
description = "What encryption type to use";
|
description = "What encryption type to use";
|
||||||
};
|
};
|
||||||
keyformat = lib.mkOption {
|
keyformat = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.enum ["raw" "hex" "passphrase"]);
|
type = lib.types.enum ["raw" "hex" "passphrase"];
|
||||||
default = null;
|
|
||||||
description = "Format of the encryption key";
|
description = "Format of the encryption key";
|
||||||
};
|
};
|
||||||
keylocation = lib.mkOption {
|
keylocation = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.str;
|
||||||
default = null;
|
|
||||||
description = "Location of the encryption key";
|
description = "Location of the encryption key";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -77,14 +74,11 @@
|
||||||
# This option should set this option flag
|
# This option should set this option flag
|
||||||
# "com.sun:auto-snapshot" = "false";
|
# "com.sun:auto-snapshot" = "false";
|
||||||
autoSnapshot = lib.mkOption {
|
autoSnapshot = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = null;
|
default = false;
|
||||||
description = "Enable automatic snapshots for this dataset";
|
description = "Enable automatic snapshots for this dataset";
|
||||||
};
|
};
|
||||||
# TODO: this is what blank snapshot should set
|
# Creates a blank snapshot in the post create hook for rollback purposes
|
||||||
# postCreateHook = ''
|
|
||||||
# zfs snapshot rpool/local/system/root@blank
|
|
||||||
# '';
|
|
||||||
blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook";
|
blankSnapshot = lib.mkEnableOption "Should a blank snapshot be auto created in the post create hook";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ in {
|
||||||
config = {
|
config = {
|
||||||
mount = {
|
mount = {
|
||||||
mountPoint = lib.mkDefault "/${name}";
|
mountPoint = lib.mkDefault "/${name}";
|
||||||
|
enable = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,98 @@ args @ {
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
datasetSubmodule = (import ./submodules/dataset.nix) args;
|
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));
|
||||||
|
|
||||||
|
# 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.mountPoint 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.mountPoint or null;
|
||||||
|
postCreateHook = generatePostCreateHook "" config.storage.zfs.rootDataset;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
in {
|
in {
|
||||||
options.storage = {
|
options.storage = {
|
||||||
zfs = {
|
zfs = {
|
||||||
|
|
@ -39,12 +131,14 @@ in {
|
||||||
lib.types.coercedTo lib.types.str (device: {
|
lib.types.coercedTo lib.types.str (device: {
|
||||||
device = device;
|
device = device;
|
||||||
boot = false;
|
boot = false;
|
||||||
}) {
|
}) (lib.types.submodule {
|
||||||
device = lib.mkOption {
|
options = {
|
||||||
type = lib.types.str;
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
boot = lib.mkEnableOption "should this device be a boot device";
|
||||||
};
|
};
|
||||||
boot = lib.mkEnableOption "should this device be a boot device";
|
});
|
||||||
};
|
|
||||||
in {
|
in {
|
||||||
encryption = {
|
encryption = {
|
||||||
enable = lib.mkEnableOption "Should encryption be enabled on this pool.";
|
enable = lib.mkEnableOption "Should encryption be enabled on this pool.";
|
||||||
|
|
@ -75,15 +169,15 @@ in {
|
||||||
description = "List of vdevs, where each vdev is a list of devices";
|
description = "List of vdevs, where each vdev is a list of devices";
|
||||||
};
|
};
|
||||||
cache = lib.mkOption {
|
cache = lib.mkOption {
|
||||||
type = lib.types.attrsOf deviceType;
|
type = lib.types.listOf deviceType;
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
rootDataset = lib.mkOption {
|
rootDataset = lib.mkOption {
|
||||||
type = lib.types.submodule datasetSubmodule;
|
type = lib.types.nullOr (lib.types.submodule datasetSubmodule);
|
||||||
description = "Root ZFS dataset to create";
|
description = "Root ZFS dataset to create";
|
||||||
default = {};
|
default = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
datasets = lib.mkOption {
|
datasets = lib.mkOption {
|
||||||
|
|
@ -96,15 +190,109 @@ in {
|
||||||
|
|
||||||
config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [
|
config = lib.mkIf config.storage.zfs.enable (lib.mkMerge [
|
||||||
{
|
{
|
||||||
services.zfs = {
|
# Assertion that we have at least one boot device
|
||||||
autoScrub.enable = true;
|
assertions = [
|
||||||
autoSnapshot.enable = true;
|
{
|
||||||
};
|
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.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
# TODO: configure disko
|
# # Warning about disk/dataset mismatches - these would be runtime checks
|
||||||
# TODO: assertion that we have a boot device
|
# warnings = let
|
||||||
# TODO: check that disks on system match configuration and warn user if they don't
|
# configuredDisks = builtins.map (device: device.device) (builtins.map (dev: dev.value) allDevices);
|
||||||
# TODO: check that datasets on system match configuration and warn user if they don't
|
# 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 = config.storage.zfs.pool.mode;
|
||||||
|
members = builtins.map (device: hashDisk device.device) vdev;
|
||||||
|
})
|
||||||
|
config.storage.zfs.pool.vdevs;
|
||||||
|
cache = builtins.map (device: hashDisk device.device) (builtins.attrValues 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 {
|
(lib.mkIf config.storage.zfs.notifications.enable {
|
||||||
programs.msmtp = {
|
programs.msmtp = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue