feat: refined options for datasets

This commit is contained in:
Leyla Becker 2025-11-08 13:21:01 -06:00
parent 0de97fa4a2
commit 9df29cc07f
6 changed files with 295 additions and 271 deletions

View file

@ -33,44 +33,6 @@
isPrincipleUser = true; isPrincipleUser = true;
}; };
}; };
impermanence.enable = true;
storage = {
enable = true;
encryption = true;
notifications = {
enable = true;
host = "smtp.protonmail.ch";
port = 587;
to = "leyla@jan-leila.com";
user = "noreply@jan-leila.com";
tokenFile = config.sops.secrets."services/zfs_smtp_token".path;
};
pool = {
# We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA
bootDrives = ["nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F"];
vdevs = [
[
"ata-ST18000NE000-3G6101_ZVTCXVEB"
"ata-ST18000NE000-3G6101_ZVTCXWSC"
"ata-ST18000NE000-3G6101_ZVTD10EH"
"ata-ST18000NT001-3NF101_ZVTE0S3Q"
"ata-ST18000NT001-3NF101_ZVTEF27J"
"ata-ST18000NE000-3G6101_ZVTJ7359"
]
[
"ata-ST4000NE001-2MA101_WS2275P3"
"ata-ST4000NE001-2MA101_WS227B9F"
"ata-ST4000NE001-2MA101_WS227CEW"
"ata-ST4000NE001-2MA101_WS227CYN"
"ata-ST4000NE001-2MA101_WS23TBWV"
"ata-ST4000NE001-2MA101_WS23TC5F"
]
];
cache = [
"nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F"
];
};
};
network_storage = { network_storage = {
enable = true; enable = true;
directories = [ directories = [
@ -104,6 +66,53 @@
}; };
}; };
storage = {
zfs = {
enable = true;
notifications = {
enable = true;
host = "smtp.protonmail.ch";
port = 587;
to = "leyla@jan-leila.com";
user = "noreply@jan-leila.com";
tokenFile = config.sops.secrets."services/zfs_smtp_token".path;
};
pool = {
encryption = {
enable = true;
};
vdevs = [
[
"ata-ST18000NE000-3G6101_ZVTCXVEB"
"ata-ST18000NE000-3G6101_ZVTCXWSC"
"ata-ST18000NE000-3G6101_ZVTD10EH"
"ata-ST18000NT001-3NF101_ZVTE0S3Q"
"ata-ST18000NT001-3NF101_ZVTEF27J"
"ata-ST18000NE000-3G6101_ZVTJ7359"
]
[
"ata-ST4000NE001-2MA101_WS2275P3"
"ata-ST4000NE001-2MA101_WS227B9F"
"ata-ST4000NE001-2MA101_WS227CEW"
"ata-ST4000NE001-2MA101_WS227CYN"
"ata-ST4000NE001-2MA101_WS23TBWV"
"ata-ST4000NE001-2MA101_WS23TC5F"
]
];
# We are having to boot off of the nvm cache drive because I cant figure out how to boot via the HBA
cache = {
cache0 = {
device = "nvme-Samsung_SSD_990_PRO_4TB_S7KGNU0X907881F";
boot = true;
};
};
};
};
impermanence = {
enable = true;
};
};
systemd.network = { systemd.network = {
enable = true; enable = true;

View file

@ -8,5 +8,6 @@
imports = [ imports = [
./impermanence.nix ./impermanence.nix
./zfs.nix ./zfs.nix
./storage.nix
]; ];
} }

View file

@ -1,7 +1,6 @@
{ {
lib, lib,
config, config,
util,
... ...
}: { }: {
# TODO: create all of the datasets from option and home-manager datasets # TODO: create all of the datasets from option and home-manager datasets
@ -13,50 +12,49 @@
storage.zfs.datasets = { storage.zfs.datasets = {
"persist/system/nix" = { "persist/system/nix" = {
type = "zfs_fs"; type = "zfs_fs";
mountpoint = "/nix"; mount = {
options = { enable = true;
mountPoint = "/nix";
};
snapshot = {
autoSnapshot = false;
};
atime = "off"; atime = "off";
relatime = "off"; relatime = "off";
canmount = "on";
"com.sun:auto-snapshot" = "false";
};
}; };
"persist/system/var/log" = { "persist/system/var/log" = {
type = "zfs_fs"; type = "zfs_fs";
mountpoint = "/var/log"; mount = {
options = { enable = true;
"com.sun:auto-snapshot" = "false"; mountPoint = "/var/log";
};
snapshot = {
autoSnapshot = false;
};
};
"persist/system/root" = {
type = "zfs_fs";
mount = {
enable = true;
mountPoint = "/";
}; };
}; };
}; };
} }
(util.mkUnless config.storage.impermanence.enable { (lib.mkIf (!config.storage.impermanence.enable) {
# TODO: create datasets for systemd.services.<name>.storage.impermanence.datasets # TODO: create datasets for systemd.services.<name>.storage.impermanence.datasets
storage.zfs.datasets = { storage.zfs.datasets = {
"persist/system/root" = { "persist/system/root" = {
type = "zfs_fs"; type = "zfs_fs";
mountpoint = "/"; snapshot = {
canmount = "on"; autoSnapshot = true;
};
}; };
}; };
}) })
(lib.mkIf config.storage.impermanence.enable { (lib.mkIf config.storage.impermanence.enable {
storage.impermanence.datasets = { storage.impermanence.datasets = {
"persist/system/root" = { "persist/system/root" = {
type = "zfs_fs";
};
};
storage.zfs.datasets = {
"local/system/root" = {
type = "zfs_fs";
mountpoint = "/";
options = {
canmount = "on";
};
postCreateHook = ''
zfs snapshot rpool/local/system/root@blank
'';
directories = { directories = {
"/var/lib/nixos".enable = true; "/var/lib/nixos".enable = true;
"/var/lib/systemd/coredump".enable = true; "/var/lib/systemd/coredump".enable = true;
@ -66,6 +64,18 @@
}; };
}; };
}; };
storage.zfs.datasets = {
"local/system/root" = {
type = "zfs_fs";
mount = {
enable = true;
mountPoint = "/";
};
snapshot = {
blankSnapshot = true;
};
};
};
# TODO: home-manager.users.<user>.storage.impermanence.enable # TODO: home-manager.users.<user>.storage.impermanence.enable
# is false then persist the entire directory of the user # is false then persist the entire directory of the user

View file

@ -6,25 +6,6 @@
description = "Type of ZFS dataset (filesystem or volume)"; description = "Type of ZFS dataset (filesystem or volume)";
}; };
# ZFS dataset options that match what's currently hardcoded in rootFsOptions
canmount = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["on" "off" "noauto"]);
default = null;
description = "Controls whether the file system can be mounted";
};
mountpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Controls the mount point used for this file system";
};
xattr = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["on" "off" "sa" "dir"]);
default = null;
description = "Extended attribute storage method";
};
acltype = lib.mkOption { acltype = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]); type = lib.types.nullOr (lib.types.enum ["off" "nfsv4" "posixacl"]);
default = null; default = null;
@ -37,56 +18,82 @@
description = "Controls when access time is updated"; 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 { compression = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]); type = lib.types.nullOr (lib.types.enum ["on" "off" "lz4" "gzip" "zstd" "lzjb" "zle"]);
default = null; default = null;
description = "Compression algorithm to use"; description = "Compression algorithm to use";
}; };
encryption = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["on" "off" "aes-128-ccm" "aes-192-ccm" "aes-256-ccm" "aes-128-gcm" "aes-192-gcm" "aes-256-gcm"]);
default = null;
description = "Encryption algorithm to use";
};
keyformat = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["raw" "hex" "passphrase"]);
default = null;
description = "Format of the encryption key";
};
keylocation = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Location of the encryption key";
};
autoSnapshot = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
description = "Enable automatic snapshots for this dataset";
};
# Additional common ZFS options
recordsize = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Suggested block size for files in the file system";
};
sync = lib.mkOption { sync = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]); type = lib.types.nullOr (lib.types.enum ["standard" "always" "disabled"]);
default = null; default = null;
description = "Synchronous write behavior"; description = "Synchronous write behavior";
}; };
atime = lib.mkOption { mount = {
type = lib.types.nullOr (lib.types.enum ["on" "off"]); enable = lib.mkOption {
type = lib.types.nullOr (lib.types.either lib.types.bool (lib.types.enum ["on" "off" "noauto"]));
default = null; default = null;
description = "Controls whether access time is updated"; };
mountPoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Controls the mount point used for this file system";
};
};
encryption = {
enable = lib.mkEnableOption "should encryption be enabled";
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"]);
default = null;
description = "What encryption type to use";
};
keyformat = lib.mkOption {
type = lib.types.nullOr (lib.types.enum ["raw" "hex" "passphrase"]);
default = null;
description = "Format of the encryption key";
};
keylocation = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Location of the encryption key";
};
};
snapshot = {
# This option should set this option flag
# "com.sun:auto-snapshot" = "false";
autoSnapshot = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
description = "Enable automatic snapshots for this dataset";
};
# TODO: this is what blank snapshot should set
# postCreateHook = ''
# zfs snapshot rpool/local/system/root@blank
# '';
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";
}; };
# Custom options for disko integration
postCreateHook = lib.mkOption { postCreateHook = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = ""; default = "";

View file

@ -1,10 +1,5 @@
args @ { args @ {lib, ...}: {name, ...}: let
lib,
name,
...
}: {...}: let
datasetSubmodule = (import ./dataset.nix) args; datasetSubmodule = (import ./dataset.nix) args;
pathPermissions = { pathPermissions = {
read = lib.mkEnableOption "should the path have read permissions"; read = lib.mkEnableOption "should the path have read permissions";
write = lib.mkEnableOption "should the path have read permissions"; write = lib.mkEnableOption "should the path have read permissions";
@ -52,6 +47,8 @@ in {
}; };
config = { config = {
mountpoint = "/${name}"; mount = {
mountPoint = lib.mkDefault "/${name}";
};
}; };
} }

View file

@ -171,153 +171,153 @@ in {
}; };
# Disko configuration based on pool settings # Disko configuration based on pool settings
disko.devices = { # disko.devices = {
disk = ( # disk = (
builtins.listToAttrs ( # builtins.listToAttrs (
builtins.map # builtins.map
(drive: # (drive:
lib.attrsets.nameValuePair (drive.name) { # lib.attrsets.nameValuePair (drive.name) {
type = "disk"; # type = "disk";
device = "/dev/disk/by-id/${drive.value}"; # device = "/dev/disk/by-id/${drive.value}";
content = { # content = {
type = "gpt"; # type = "gpt";
partitions = { # partitions = {
ESP = lib.mkIf (builtins.elem drive.value bootDrives) { # ESP = lib.mkIf (builtins.elem drive.value bootDrives) {
size = config.storage.zfs.pool.bootPartitionSize; # size = config.storage.zfs.pool.bootPartitionSize;
type = "EF00"; # type = "EF00";
content = { # content = {
type = "filesystem"; # type = "filesystem";
format = "vfat"; # format = "vfat";
mountpoint = "/boot"; # mountpoint = "/boot";
mountOptions = ["umask=0077"]; # mountOptions = ["umask=0077"];
}; # };
}; # };
zfs = { # zfs = {
size = "100%"; # size = "100%";
content = { # content = {
type = "zfs"; # type = "zfs";
pool = "rpool"; # pool = "rpool";
}; # };
}; # };
}; # };
}; # };
}) # })
allDrives # allDrives
) # )
); # );
zpool = { # zpool = {
rpool = { # rpool = {
type = "zpool"; # type = "zpool";
mode = { # mode = {
topology = { # topology = {
type = "topology"; # type = "topology";
vdev = ( # vdev = (
builtins.map (disks: { # builtins.map (disks: {
mode = config.storage.zfs.pool.mode; # mode = config.storage.zfs.pool.mode;
members = # members =
builtins.map (disk: disk.name) disks; # builtins.map (disk: disk.name) disks;
}) # })
poolVdevs # poolVdevs
); # );
cache = builtins.map (disk: disk.name) poolCache; # cache = builtins.map (disk: disk.name) poolCache;
}; # };
}; # };
options = { # options = {
ashift = "12"; # ashift = "12";
autotrim = "on"; # autotrim = "on";
}; # };
rootFsOptions = let # rootFsOptions = let
rootDataset = config.storage.zfs.rootDataset; # rootDataset = config.storage.zfs.rootDataset;
# Start with defaults that match the original hardcoded values # # Start with defaults that match the original hardcoded values
defaults = { # defaults = {
canmount = "off"; # canmount = "off";
mountpoint = "none"; # mountpoint = "none";
xattr = "sa"; # xattr = "sa";
acltype = "posixacl"; # acltype = "posixacl";
relatime = "on"; # relatime = "on";
compression = "lz4"; # compression = "lz4";
"com.sun:auto-snapshot" = "false"; # "com.sun:auto-snapshot" = "false";
}; # };
# Override defaults with non-null values from rootDataset # # Override defaults with non-null values from rootDataset
userOptions = lib.attrsets.filterAttrs (_: v: v != null) { # userOptions = lib.attrsets.filterAttrs (_: v: v != null) {
canmount = rootDataset.canmount; # canmount = rootDataset.canmount;
mountpoint = rootDataset.mountpoint; # mountpoint = rootDataset.mountpoint;
xattr = rootDataset.xattr; # xattr = rootDataset.xattr;
acltype = rootDataset.acltype; # acltype = rootDataset.acltype;
relatime = rootDataset.relatime; # relatime = rootDataset.relatime;
compression = rootDataset.compression; # compression = rootDataset.compression;
encryption = rootDataset.encryption; # encryption = rootDataset.encryption;
keyformat = rootDataset.keyformat; # keyformat = rootDataset.keyformat;
keylocation = rootDataset.keylocation; # keylocation = rootDataset.keylocation;
recordsize = rootDataset.recordsize; # recordsize = rootDataset.recordsize;
sync = rootDataset.sync; # sync = rootDataset.sync;
atime = rootDataset.atime; # atime = rootDataset.atime;
"com.sun:auto-snapshot" = # "com.sun:auto-snapshot" =
if rootDataset.autoSnapshot == null # if rootDataset.autoSnapshot == null
then null # then null
else # else
( # (
if rootDataset.autoSnapshot # if rootDataset.autoSnapshot
then "true" # then "true"
else "false" # else "false"
); # );
}; # };
# Only apply pool encryption if user hasn't set encryption options in rootDataset # # Only apply pool encryption if user hasn't set encryption options in rootDataset
poolEncryptionOptions = # poolEncryptionOptions =
lib.attrsets.optionalAttrs ( # lib.attrsets.optionalAttrs (
config.storage.zfs.pool.encryption.enable # config.storage.zfs.pool.encryption.enable
&& rootDataset.encryption == null # && rootDataset.encryption == null
&& rootDataset.keyformat == null # && rootDataset.keyformat == null
&& rootDataset.keylocation == null # && rootDataset.keylocation == null
) { # ) {
encryption = "on"; # encryption = "on";
keyformat = config.storage.zfs.pool.encryption.keyformat; # keyformat = config.storage.zfs.pool.encryption.keyformat;
keylocation = config.storage.zfs.pool.encryption.keylocation; # keylocation = config.storage.zfs.pool.encryption.keylocation;
}; # };
in # in
defaults // userOptions // rootDataset.options // poolEncryptionOptions; # defaults // userOptions // rootDataset.options // poolEncryptionOptions;
datasets = lib.mkMerge [ # datasets = lib.mkMerge [
( # (
lib.attrsets.mapAttrs (name: value: { # lib.attrsets.mapAttrs (name: value: {
type = value.type; # type = value.type;
options = let # options = let
# For datasets, only include non-null user-specified values # # For datasets, only include non-null user-specified values
userOptions = lib.attrsets.filterAttrs (_: v: v != null) { # userOptions = lib.attrsets.filterAttrs (_: v: v != null) {
canmount = value.canmount; # canmount = value.canmount;
xattr = value.xattr; # xattr = value.xattr;
acltype = value.acltype; # acltype = value.acltype;
relatime = value.relatime; # relatime = value.relatime;
compression = value.compression; # compression = value.compression;
encryption = value.encryption; # encryption = value.encryption;
keyformat = value.keyformat; # keyformat = value.keyformat;
keylocation = value.keylocation; # keylocation = value.keylocation;
recordsize = value.recordsize; # recordsize = value.recordsize;
sync = value.sync; # sync = value.sync;
atime = value.atime; # atime = value.atime;
"com.sun:auto-snapshot" = # "com.sun:auto-snapshot" =
if value.autoSnapshot == null # if value.autoSnapshot == null
then null # then null
else # else
( # (
if value.autoSnapshot # if value.autoSnapshot
then "true" # then "true"
else "false" # else "false"
); # );
}; # };
in # in
userOptions // (value.options or {}); # userOptions // (value.options or {});
mountpoint = value.mountpoint; # mountpoint = value.mountpoint;
postCreateHook = value.postCreateHook or ""; # postCreateHook = value.postCreateHook or "";
}) # })
config.storage.zfs.datasets # config.storage.zfs.datasets
) # )
]; # ];
}; # };
}; # };
}; # };
# Post-activation scripts for validation # Post-activation scripts for validation
system.activationScripts = { system.activationScripts = {