diff --git a/modules/nixos-modules/storage/impermanence.nix b/modules/nixos-modules/storage/impermanence.nix index b1fd6b5..c5f53a3 100644 --- a/modules/nixos-modules/storage/impermanence.nix +++ b/modules/nixos-modules/storage/impermanence.nix @@ -1,33 +1,90 @@ -args @ {lib, ...}: let - impermanenceDatasetSubmodules = (import ./submodules/impermanenceDataset.nix) args; +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"; - # 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 { - type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodules); + type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodule); default = {}; }; - - # TODO: this should just live under home-manager.users..storage.impermanence - home-manager = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { - enable = lib.mkEnableOption "should impermanence be enabled for this user"; - # We should by default create the `local/home/${name}`, and `persist/home/${name}` datasets - datasets = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule impermanenceDatasetSubmodules); - default = {}; - }; - })); - }; }; }; + + config = lib.mkIf config.storage.impermanence.enable (lib.mkMerge [ + { + environment.persistence = + lib.mapAttrs (datasetName: dataset: { + enable = true; + hideMounts = true; + 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; + 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; + }) + ]); } diff --git a/modules/nixos-modules/storage/storage.nix b/modules/nixos-modules/storage/storage.nix new file mode 100644 index 0000000..1c1986a --- /dev/null +++ b/modules/nixos-modules/storage/storage.nix @@ -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..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..storage.impermanence.enable + # is false then persist the entire directory of the user + # if true persist home-manager.users..storage.impermanence.datasets + # TODO: systemd.services..storage.datasets persists + }) + ]) + ) + # TODO: configure other needed storage modes here + ]; +} diff --git a/modules/nixos-modules/storage/submodules/impermanenceDataset.nix b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix index 193ab80..2169ec1 100644 --- a/modules/nixos-modules/storage/submodules/impermanenceDataset.nix +++ b/modules/nixos-modules/storage/submodules/impermanenceDataset.nix @@ -1,4 +1,10 @@ -{lib, ...}: {...}: let +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"; @@ -11,14 +17,14 @@ default = true; }; owner = { - user = lib.mkOption { + name = lib.mkOption { type = lib.types.str; default = "nouser"; }; permissions = pathPermissions; }; group = { - group = lib.mkOption { + name = lib.mkOption { type = lib.types.str; default = "nogroup"; }; @@ -31,16 +37,21 @@ }; in { imports = [ - ./dataset.nix + datasetSubmodule ]; + options = { - files = lib.types.mkOption { + files = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); default = {}; }; - directories = { + directories = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule pathTypeSubmodule); default = {}; }; }; + + config = { + mountpoint = "/${name}"; + }; } diff --git a/modules/nixos-modules/storage/zfs.nix b/modules/nixos-modules/storage/zfs.nix index 20e41ae..bf0c609 100644 --- a/modules/nixos-modules/storage/zfs.nix +++ b/modules/nixos-modules/storage/zfs.nix @@ -9,18 +9,21 @@ 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 hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive)); - poolVdevs = [ - (builtins.map ( - device: let - deviceStr = - if builtins.isString device - then device - else device.device; - in - lib.attrsets.nameValuePair (hashDisk deviceStr) deviceStr - ) - config.storage.zfs.pool.vdevs) - ]; + poolVdevs = + builtins.map ( + vdev: + builtins.map ( + device: let + deviceStr = + if builtins.isString device + then device + else device.device; + in + lib.attrsets.nameValuePair (hashDisk deviceStr) deviceStr + ) + vdev + ) + config.storage.zfs.pool.vdevs; poolCache = builtins.map ( name: let @@ -45,7 +48,7 @@ args @ { then false else device.boot ) - config.storage.zfs.pool.vdevs); + (lib.lists.flatten config.storage.zfs.pool.vdevs)); allDrives = (lib.lists.flatten poolVdevs) ++ poolCache; in { @@ -113,8 +116,9 @@ in { description = "Size of the boot partition on boot drives"; }; vdevs = lib.mkOption { - type = lib.types.listOf deviceType; + 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.attrsOf deviceType; @@ -359,7 +363,7 @@ in { fi '' ) - config.storage.zfs.pool.vdevs} + (lib.lists.flatten config.storage.zfs.pool.vdevs)} # Check pool mode matches configuration if ! echo "$pool_status" | grep -q "$expected_mode"; then