feat: made impermanence create datasets for zfs and persistence

This commit is contained in:
Leyla Becker 2025-11-07 16:29:56 -06:00
parent 409fdb7276
commit adc6b90c93
4 changed files with 188 additions and 43 deletions

View file

@ -1,33 +1,90 @@
args @ {lib, ...}: let args @ {
impermanenceDatasetSubmodules = (import ./submodules/impermanenceDataset.nix) 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 { in {
options.storage = { options.storage = {
impermanence = { impermanence = {
enable = lib.mkEnableOption "should impermanence be enabled for this system"; enable = lib.mkEnableOption "should impermanence be enabled for this system";
# TODO: enable option implementation
# TODO: assertion that zfs needs to be enabled when impermanence is enabled
# TODO: datasets option that is a submodule that will be used to define what datasets to add to the storage system
# We should by default create the `local`, `local/system/nix`, `local/system/root`, `persist` `persist/system/root`, and `persist/system/var/log` datasets
# We should also create datasets for systemd modules that have have impermanence enabled for them
datasets = lib.mkOption { datasets = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodules); type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule);
default = {}; default = {};
}; };
};
};
# TODO: this should just live under home-manager.users.<user>.storage.impermanence config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [
home-manager = lib.mkOption { {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { environment.persistence =
enable = lib.mkEnableOption "should impermanence be enabled for this user"; lib.mapAttrs (datasetName: dataset: {
# We should by default create the `local/home/${name}`, and `persist/home/${name}` datasets enable = true;
datasets = lib.mkOption { hideMounts = true;
type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodules); directories = lib.mapAttrsToList (path: dirConfig: {
default = {}; 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;
user = fileConfig.owner.name;
group = fileConfig.group.name;
mode = permissionsToMode fileConfig;
}) (lib.filterAttrs (_: fileConfig: fileConfig.enable) dataset.files);
})
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;
})
]);
} }

View file

@ -0,0 +1,73 @@
{
lib,
config,
util,
...
}: {
# TODO: create all of the datasets from option and home-manager datasets
# TODO: set up datasets for systemd services that want a dataset created
config = lib.mkMerge [
(
lib.mkIf config.storage.zfs.enable (lib.mkMerge [
{
storage.zfs.datasets = {
"persist/system/nix" = {
type = "zfs_fs";
mountpoint = "/nix";
options = {
atime = "off";
relatime = "off";
canmount = "on";
"com.sun:auto-snapshot" = "false";
};
};
"persist/system/var/log" = {
type = "zfs_fs";
mountpoint = "/persist/system/var/log";
options = {
"com.sun:auto-snapshot" = "false";
};
};
};
}
(util.mkUnless config.storage.impermanence.enable {
# TODO: configure datasets for normal zfs
# TODO: create datasets for systemd.services.<name>.storage.impermanence.datasets
storage.zfs.datasets = {
"persist/system/root" = {
type = "zfs_fs";
mountpoint = "/";
canmount = "on";
};
};
})
(lib.mkIf config.storage.impermanence.enable {
storage.impermanence.datasets = {
"persist/system/root" = {
type = "zfs_fs";
};
};
storage.zfs.datasets = {
# TODO: is there a way that we can link these two folders in configs via storage.impermanence.datasets
"local/system/root" = {
type = "zfs_fs";
mountpoint = "/";
options = {
canmount = "on";
};
postCreateHook = ''
zfs snapshot rpool/local/system/root@blank
'';
};
};
# 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
];
}

View file

@ -1,4 +1,10 @@
{lib, ...}: {...}: let args @ {
lib,
name,
...
}: {...}: let
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";
@ -11,14 +17,14 @@
default = true; default = true;
}; };
owner = { owner = {
user = lib.mkOption { name = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "nouser"; default = "nouser";
}; };
permissions = pathPermissions; permissions = pathPermissions;
}; };
group = { group = {
group = lib.mkOption { name = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "nogroup"; default = "nogroup";
}; };
@ -31,16 +37,21 @@
}; };
in { in {
imports = [ imports = [
./dataset.nix datasetSubmodule
]; ];
options = { options = {
files = lib.types.mkOption { files = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule);
default = {}; default = {};
}; };
directories = { directories = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule);
default = {}; default = {};
}; };
}; };
config = {
mountpoint = "/${name}";
};
} }

View file

@ -9,8 +9,10 @@ args @ {
# max gpt length is 36 and disk adds formats it like disk-xxxx-zfs which means we need to be 9 characters under that # max gpt length is 36 and disk adds formats it like disk-xxxx-zfs which means we need to be 9 characters under that
hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive));
poolVdevs = [ poolVdevs =
(builtins.map ( builtins.map (
vdev:
builtins.map (
device: let device: let
deviceStr = deviceStr =
if builtins.isString device if builtins.isString device
@ -19,8 +21,9 @@ args @ {
in in
lib.attrsets.nameValuePair (hashDisk deviceStr) deviceStr lib.attrsets.nameValuePair (hashDisk deviceStr) deviceStr
) )
config.storage.zfs.pool.vdevs) vdev
]; )
config.storage.zfs.pool.vdevs;
poolCache = builtins.map ( poolCache = builtins.map (
name: let name: let
@ -45,7 +48,7 @@ args @ {
then false then false
else device.boot else device.boot
) )
config.storage.zfs.pool.vdevs); (lib.lists.flatten config.storage.zfs.pool.vdevs));
allDrives = (lib.lists.flatten poolVdevs) ++ poolCache; allDrives = (lib.lists.flatten poolVdevs) ++ poolCache;
in { in {
@ -113,8 +116,9 @@ in {
description = "Size of the boot partition on boot drives"; description = "Size of the boot partition on boot drives";
}; };
vdevs = lib.mkOption { vdevs = lib.mkOption {
type = lib.types.listOf deviceType; type = lib.types.listOf (lib.types.listOf deviceType);
default = []; default = [];
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.attrsOf deviceType;
@ -359,7 +363,7 @@ in {
fi fi
'' ''
) )
config.storage.zfs.pool.vdevs} (lib.lists.flatten config.storage.zfs.pool.vdevs)}
# Check pool mode matches configuration # Check pool mode matches configuration
if ! echo "$pool_status" | grep -q "$expected_mode"; then if ! echo "$pool_status" | grep -q "$expected_mode"; then