zfs: add doc and tests and convert to systemd services
This commit is contained in:
parent
ff20e56ae1
commit
bdd8a0cc2a
6 changed files with 414 additions and 93 deletions
|
|
@ -43,6 +43,12 @@ modules/blocks/postgresql/docs/default.md
|
||||||
modules/blocks/sops/docs/default.md
|
modules/blocks/sops/docs/default.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filesystem {#blocks-category-filesystem}
|
||||||
|
|
||||||
|
```{=include=} chapters html:into-file=//blocks-zfs.html
|
||||||
|
modules/blocks/zfs/docs/default.md
|
||||||
|
```
|
||||||
|
|
||||||
## Network {#blocks-category-network}
|
## Network {#blocks-category-network}
|
||||||
|
|
||||||
```{=include=} chapters html:into-file=//blocks-ssl.html
|
```{=include=} chapters html:into-file=//blocks-ssl.html
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,9 @@
|
||||||
"blocks-category-database": [
|
"blocks-category-database": [
|
||||||
"blocks.html#blocks-category-database"
|
"blocks.html#blocks-category-database"
|
||||||
],
|
],
|
||||||
|
"blocks-category-filesystem": [
|
||||||
|
"blocks.html#blocks-category-filesystem"
|
||||||
|
],
|
||||||
"blocks-category-introspection": [
|
"blocks-category-introspection": [
|
||||||
"blocks.html#blocks-category-introspection"
|
"blocks.html#blocks-category-introspection"
|
||||||
],
|
],
|
||||||
|
|
@ -1772,6 +1775,78 @@
|
||||||
"blocks-ssl-options-shb.certs.systemdService": [
|
"blocks-ssl-options-shb.certs.systemdService": [
|
||||||
"blocks-ssl.html#blocks-ssl-options-shb.certs.systemdService"
|
"blocks-ssl.html#blocks-ssl-options-shb.certs.systemdService"
|
||||||
],
|
],
|
||||||
|
"blocks-zfs": [
|
||||||
|
"blocks-zfs.html#blocks-zfs"
|
||||||
|
],
|
||||||
|
"blocks-zfs-features": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-features"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.after": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.after"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.excludePatterns": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.excludePatterns"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks.afterBackup": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks.afterBackup"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks.beforeBackup": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.hooks.beforeBackup"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.sourceDirectories": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.sourceDirectories"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.user": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.request.user"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result.backupService": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result.backupService"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result.restoreScript": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup.result.restoreScript"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.defaultACLs": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.defaultACLs"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.enable": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.enable"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.group": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.group"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.mode": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.mode"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.owner": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.owner"
|
||||||
|
],
|
||||||
|
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.path": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.path"
|
||||||
|
],
|
||||||
|
"blocks-zfs-usage": [
|
||||||
|
"blocks-zfs.html#blocks-zfs-usage"
|
||||||
|
],
|
||||||
"build-time-validation": [
|
"build-time-validation": [
|
||||||
"service-implementation-guide.html#build-time-validation"
|
"service-implementation-guide.html#build-time-validation"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@
|
||||||
"blocks/postgresql" = ./modules/blocks/postgresql.nix;
|
"blocks/postgresql" = ./modules/blocks/postgresql.nix;
|
||||||
"blocks/restic" = ./modules/blocks/restic.nix;
|
"blocks/restic" = ./modules/blocks/restic.nix;
|
||||||
"blocks/sops" = ./modules/blocks/sops.nix;
|
"blocks/sops" = ./modules/blocks/sops.nix;
|
||||||
|
"blocks/zfs" = ./modules/blocks/zfs.nix;
|
||||||
"services/arr" = ./modules/services/arr.nix;
|
"services/arr" = ./modules/services/arr.nix;
|
||||||
"services/firefly-iii" = ./modules/services/firefly-iii.nix;
|
"services/firefly-iii" = ./modules/services/firefly-iii.nix;
|
||||||
"services/forgejo" = [
|
"services/forgejo" = [
|
||||||
|
|
@ -403,6 +404,7 @@
|
||||||
// (vm_test "postgresql" ./test/blocks/postgresql.nix)
|
// (vm_test "postgresql" ./test/blocks/postgresql.nix)
|
||||||
// (vm_test "restic" ./test/blocks/restic.nix)
|
// (vm_test "restic" ./test/blocks/restic.nix)
|
||||||
// (vm_test "ssl" ./test/blocks/ssl.nix)
|
// (vm_test "ssl" ./test/blocks/ssl.nix)
|
||||||
|
// (vm_test "zfs" ./test/blocks/zfs.nix)
|
||||||
|
|
||||||
// (vm_test "contracts-backup" ./test/contracts/backup.nix)
|
// (vm_test "contracts-backup" ./test/contracts/backup.nix)
|
||||||
// (vm_test "contracts-databasebackup" ./test/contracts/databasebackup.nix)
|
// (vm_test "contracts-databasebackup" ./test/contracts/databasebackup.nix)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
|
shb,
|
||||||
|
utils,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
|
@ -9,121 +11,184 @@ let
|
||||||
cfg = config.shb.zfs;
|
cfg = config.shb.zfs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
imports = [
|
||||||
|
../../lib/module.nix
|
||||||
|
];
|
||||||
|
|
||||||
options.shb.zfs = {
|
options.shb.zfs = {
|
||||||
defaultPoolName = lib.mkOption {
|
pools = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "ZFS pool name datasets should be created on if no pool name is given in the dataset.";
|
|
||||||
};
|
|
||||||
|
|
||||||
datasets = lib.mkOption {
|
|
||||||
description = ''
|
description = ''
|
||||||
ZFS Datasets.
|
Attrset of ZFS pools under which datasets will be created.
|
||||||
|
|
||||||
Each entry in the attrset will be created and mounted in the given path.
|
The ZFS pools are not managed by this module, they should already exist.
|
||||||
The attrset name is the dataset name.
|
|
||||||
|
|
||||||
This block implements the following contracts:
|
Each pool named here will be added to the [`boot.zfs.extraPools`](https://search.nixos.org/options?channel=unstable&include_modular_service_options=0&include_nixos_options=1&query=boot.zfs.extrapools&show=option:boot.zfs.extraPools) option.
|
||||||
- mount
|
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = { };
|
||||||
example = lib.literalExpression ''
|
|
||||||
shb.zfs."safe/postgresql".path = "/var/lib/postgresql";
|
|
||||||
'';
|
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
lib.types.submodule {
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
enable = lib.mkEnableOption "shb.zfs.datasets";
|
datasets = lib.mkOption {
|
||||||
|
|
||||||
poolName = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "ZFS pool name this dataset should be created on. Overrides the defaultPoolName.";
|
|
||||||
};
|
|
||||||
|
|
||||||
path = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Path this dataset should be mounted on. If the string 'none' is given, the dataset will not be mounted.";
|
|
||||||
};
|
|
||||||
|
|
||||||
mode = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
description = "If non null, unix mode to apply to the dataset root folder.";
|
|
||||||
default = null;
|
|
||||||
example = "ug=rwx,g+s";
|
|
||||||
};
|
|
||||||
|
|
||||||
owner = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
description = "If non null, unix user to apply to the dataset root folder.";
|
|
||||||
default = null;
|
|
||||||
example = "syncthing";
|
|
||||||
};
|
|
||||||
|
|
||||||
group = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
description = "If non null, unix group to apply to the dataset root folder.";
|
|
||||||
default = null;
|
|
||||||
example = "syncthing";
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultACLs = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
description = ''
|
description = ''
|
||||||
If non null, default ACL to set on the dataset root folder.
|
ZFS Datasets.
|
||||||
|
|
||||||
Executes "setfacl -d -m $acl $path"
|
Each entry in the attrset will be created and mounted in the given path.
|
||||||
|
The attrset name is the dataset name.
|
||||||
|
|
||||||
|
This block implements the following contracts:
|
||||||
|
- mount
|
||||||
'';
|
'';
|
||||||
default = null;
|
default = { };
|
||||||
example = "g:syncthing:rwX";
|
example = lib.literalExpression ''
|
||||||
|
shb.zfs."safe/postgresql".path = "/var/lib/postgresql";
|
||||||
|
'';
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ config, name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "shb.zfs.datasets";
|
||||||
|
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Path this dataset should be mounted on. If the string 'none' is given, the dataset will not be mounted.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mode = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = "If non null, unix mode to apply to the dataset root folder.";
|
||||||
|
default = null;
|
||||||
|
example = "ug=rwx,g+s";
|
||||||
|
};
|
||||||
|
|
||||||
|
owner = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = "If non null, unix user to apply to the dataset root folder.";
|
||||||
|
default = null;
|
||||||
|
example = "syncthing";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = "If non null, unix group to apply to the dataset root folder.";
|
||||||
|
default = null;
|
||||||
|
example = "syncthing";
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultACLs = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = ''
|
||||||
|
If non null, default ACL to set on the dataset root folder.
|
||||||
|
|
||||||
|
Executes "setfacl -d -m $acl $path"
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
example = "g:syncthing:rwX";
|
||||||
|
};
|
||||||
|
|
||||||
|
after = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Order creating this dataset after the mentioned ones.
|
||||||
|
This only works with datasets managed by this module.
|
||||||
|
|
||||||
|
Use the name of the dataset without the pool name.
|
||||||
|
'';
|
||||||
|
default = [ ];
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
"backup"
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
backup = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Backup contract consumer configuration.
|
||||||
|
|
||||||
|
This contract will backup the files inside the dataset.
|
||||||
|
'';
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.backup.mkRequester {
|
||||||
|
user = if config.owner == null then "root" else config.owner;
|
||||||
|
sourceDirectories = [
|
||||||
|
config.path
|
||||||
|
];
|
||||||
|
sourceDirectoriesText = ''
|
||||||
|
[
|
||||||
|
shb.zfs.pools.<name>.datasets.<name>.path
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The implementation is greatly inspired by https://discourse.nixos.org/t/configure-zfs-filesystems-after-install/48633/3
|
||||||
config = {
|
config = {
|
||||||
assertions = [
|
boot.zfs.extraPools = lib.uniqueStrings (builtins.attrNames cfg.pools);
|
||||||
{
|
|
||||||
assertion =
|
|
||||||
lib.any (x: x.poolName == null) (lib.mapAttrsToList (n: v: v) cfg.datasets)
|
|
||||||
-> cfg.defaultPoolName != null;
|
|
||||||
message = "Cannot have both datasets.poolName and defaultPoolName set to null";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
system.activationScripts = lib.mapAttrs' (
|
systemd.services =
|
||||||
name: cfg':
|
|
||||||
let
|
let
|
||||||
dataset = (if cfg'.poolName != null then cfg'.poolName else cfg.defaultPoolName) + "/" + name;
|
mkPool =
|
||||||
|
poolName: poolCfg: lib.listToAttrs (lib.mapAttrsToList (mkDataset poolName) poolCfg.datasets);
|
||||||
|
|
||||||
|
mkDataset =
|
||||||
|
poolName: name: cfg':
|
||||||
|
let
|
||||||
|
dataset = poolName + "/" + name;
|
||||||
|
in
|
||||||
|
lib.attrsets.nameValuePair "zfs-create-${utils.escapeSystemdPath dataset}" {
|
||||||
|
# oneshot is used to make the systemd service wait on completion of the script.
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
unitConfig.DefaultDependencies = false;
|
||||||
|
requiredBy = [ "local-fs.target" ];
|
||||||
|
before = [ "local-fs.target" ];
|
||||||
|
after = [
|
||||||
|
"zfs-import-${poolName}.service"
|
||||||
|
"zfs-mount.service"
|
||||||
|
]
|
||||||
|
++ map (n: "zfs-create-${poolName}-${n}.service") cfg'.after;
|
||||||
|
|
||||||
|
unitConfig.ConditionPathIsMountPoint = lib.mkIf (cfg'.path != "none") "!${cfg'.path}";
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${pkgs.zfs}/bin/zfs list ${dataset} > /dev/null 2>&1 \
|
||||||
|
|| ${pkgs.zfs}/bin/zfs create \
|
||||||
|
-o mountpoint=none \
|
||||||
|
${dataset} || :
|
||||||
|
|
||||||
|
[ "$(${pkgs.zfs}/bin/zfs get -H mountpoint -o value ${dataset})" = ${cfg'.path} ] \
|
||||||
|
|| ${pkgs.zfs}/bin/zfs set \
|
||||||
|
mountpoint="${cfg'.path}" \
|
||||||
|
${dataset}
|
||||||
|
|
||||||
|
''
|
||||||
|
+ lib.optionalString (cfg'.path != "none" && cfg'.mode != null) ''
|
||||||
|
chmod "${cfg'.mode}" "${cfg'.path}"
|
||||||
|
''
|
||||||
|
+ lib.optionalString (cfg'.path != "none" && cfg'.owner != null) ''
|
||||||
|
chown "${cfg'.owner}" "${cfg'.path}"
|
||||||
|
''
|
||||||
|
+ lib.optionalString (cfg'.path != "none" && cfg'.group != null) ''
|
||||||
|
chown :"${cfg'.group}" "${cfg'.path}"
|
||||||
|
''
|
||||||
|
+ lib.optionalString (cfg'.path != "none" && cfg'.defaultACLs != null) ''
|
||||||
|
${pkgs.acl}/bin/setfacl -d -m "${cfg'.defaultACLs}" "${cfg'.path}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
mergeAttrs = lib.foldl lib.mergeAttrs { };
|
||||||
in
|
in
|
||||||
lib.attrsets.nameValuePair "zfsCreate-${name}" {
|
mergeAttrs (lib.mapAttrsToList mkPool cfg.pools);
|
||||||
text = ''
|
|
||||||
${pkgs.zfs}/bin/zfs list ${dataset} > /dev/null 2>&1 \
|
|
||||||
|| ${pkgs.zfs}/bin/zfs create \
|
|
||||||
-o mountpoint=none \
|
|
||||||
${dataset} || :
|
|
||||||
|
|
||||||
[ "$(${pkgs.zfs}/bin/zfs get -H mountpoint -o value ${dataset})" = ${cfg'.path} ] \
|
|
||||||
|| ${pkgs.zfs}/bin/zfs set \
|
|
||||||
mountpoint="${cfg'.path}" \
|
|
||||||
${dataset}
|
|
||||||
|
|
||||||
''
|
|
||||||
+ lib.optionalString (cfg'.path != "none" && cfg'.mode != null) ''
|
|
||||||
chmod "${cfg'.mode}" "${cfg'.path}"
|
|
||||||
''
|
|
||||||
+ lib.optionalString (cfg'.path != "none" && cfg'.owner != null) ''
|
|
||||||
chown "${cfg'.owner}" "${cfg'.path}"
|
|
||||||
''
|
|
||||||
+ lib.optionalString (cfg'.path != "none" && cfg'.group != null) ''
|
|
||||||
chown :"${cfg'.group}" "${cfg'.path}"
|
|
||||||
''
|
|
||||||
+ lib.optionalString (cfg'.path != "none" && cfg'.defaultACLs != null) ''
|
|
||||||
${pkgs.acl}/bin/setfacl -d -m "${cfg'.defaultACLs}" "${cfg'.path}"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
) cfg.datasets;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
modules/blocks/zfs/docs/default.md
Normal file
58
modules/blocks/zfs/docs/default.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# ZFS Block {#blocks-zfs}
|
||||||
|
|
||||||
|
Defined in [`/modules/blocks/zfs.nix`](@REPO@/modules/blocks/zfs.nix):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.selfhostblocks.nixosModules.zfs
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This block creates ZFS datasets, optionally mounts them and sets permissions on the mount point.
|
||||||
|
|
||||||
|
## Features {#blocks-zfs-features}
|
||||||
|
|
||||||
|
- Creates ZFS dataset which is [optionally mounted](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.path).
|
||||||
|
- Sets permissions, [owner](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.owner), [group](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.group) and [ACL](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.defaultACLs) on the mount point.
|
||||||
|
|
||||||
|
## Usage {#blocks-zfs-usage}
|
||||||
|
|
||||||
|
Create a dataset at `root/safe/users` mounted on `/var/lib/nixos`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
shb.zfs.pools.root.datasets."safe/users".path = "/var/lib/nixos";
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a dataset at `backup/syncoid` but do not mount it:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
shb.zfs.pools.backup.datasets."syncoid".path = "none";
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a dataset at `root/syncthing` and set custom permissions and ACL.
|
||||||
|
Permission and ACL are only enforced for the mount point.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
shb.zfs.pools.root.datasets."syncthing" = {
|
||||||
|
path = "/srv/syncthing";
|
||||||
|
|
||||||
|
mode = "ug=rwx,g+s,o=";
|
||||||
|
owner = "syncthing";
|
||||||
|
group = "syncthing";
|
||||||
|
defaultACLs = "g:syncthing:rwX";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options Reference {#blocks-zfs-options}
|
||||||
|
|
||||||
|
```{=include=} options
|
||||||
|
id-prefix: blocks-zfs-options-
|
||||||
|
list-id: selfhostblocks-block-zfs-options
|
||||||
|
source: @OPTIONS_JSON@
|
||||||
|
```
|
||||||
115
test/blocks/zfs.nix
Normal file
115
test/blocks/zfs.nix
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
shb,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
default = shb.test.runNixOSTest {
|
||||||
|
name = "zfs-default";
|
||||||
|
|
||||||
|
nodes.machine =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../../modules/blocks/zfs.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
# Inspiration from https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/zfs.nix
|
||||||
|
networking.hostId = "deadbeef";
|
||||||
|
boot.supportedFilesystems = [ "zfs" ];
|
||||||
|
|
||||||
|
users.users.syncthing = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "syncthing";
|
||||||
|
};
|
||||||
|
users.groups.syncthing = { };
|
||||||
|
virtualisation = {
|
||||||
|
emptyDiskImages = [
|
||||||
|
512
|
||||||
|
512
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."zfs-zpool-create" = {
|
||||||
|
unitConfig.DefaultDependencies = false;
|
||||||
|
after = [ "systemd-modules-load.service" ];
|
||||||
|
requiredBy = [
|
||||||
|
"zfs-import-root.service"
|
||||||
|
"zfs-import-data.service"
|
||||||
|
"zfs-mount.service"
|
||||||
|
];
|
||||||
|
before = [
|
||||||
|
"zfs-import-root.service"
|
||||||
|
"zfs-import-data.service"
|
||||||
|
"zfs-mount.service"
|
||||||
|
];
|
||||||
|
script = ''
|
||||||
|
if [ ! -f /var/done ]; then
|
||||||
|
${pkgs.zfs}/bin/zpool create -m none -O acltype=posixacl root /dev/vdb
|
||||||
|
${pkgs.zfs}/bin/zpool create -m none -O acltype=posixacl data /dev/vdc
|
||||||
|
sync
|
||||||
|
fi
|
||||||
|
touch /var/done
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.zfs.pools.root.datasets.one.path = "/var/root/one";
|
||||||
|
shb.zfs.pools.root.datasets.two.path = "/var/root/two";
|
||||||
|
shb.zfs.pools.root.datasets.none.path = "none";
|
||||||
|
shb.zfs.pools.data.datasets.two = {
|
||||||
|
path = "/var/data/two";
|
||||||
|
|
||||||
|
mode = "ug=rwx,g+s,o=";
|
||||||
|
owner = "syncthing";
|
||||||
|
group = "syncthing";
|
||||||
|
defaultACLs = "g:syncthing:rwX";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
|
import difflib
|
||||||
|
|
||||||
|
def assert_facl():
|
||||||
|
out = machine.succeed("getfacl /var/data/two").splitlines()
|
||||||
|
expect = """\
|
||||||
|
# file: var/data/two
|
||||||
|
# owner: syncthing
|
||||||
|
# group: syncthing
|
||||||
|
# flags: -s-
|
||||||
|
user::rwx
|
||||||
|
group::rwx
|
||||||
|
other::---
|
||||||
|
default:user::rwx
|
||||||
|
default:group::rwx
|
||||||
|
default:group:syncthing:rwx
|
||||||
|
default:mask::rwx
|
||||||
|
default:other::---
|
||||||
|
|
||||||
|
""".splitlines()
|
||||||
|
if out != expect:
|
||||||
|
diff = difflib.context_diff(expect, out)
|
||||||
|
raise Exception(f"Unexpected getfacl:\n{"\n".join(diff)}")
|
||||||
|
|
||||||
|
def assert_mounts():
|
||||||
|
out = sorted([l.split()[0] for l in machine.succeed("mount | grep /var/").splitlines()])
|
||||||
|
expect = ["data/two", "root/one", "root/two"]
|
||||||
|
if out != expect:
|
||||||
|
diff = difflib.context_diff(expect, out)
|
||||||
|
raise Exception(f"Unexpected mounts:\n{"\n".join(diff)}")
|
||||||
|
|
||||||
|
machine.start(allow_reboot=True)
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
assert_facl()
|
||||||
|
assert_mounts()
|
||||||
|
|
||||||
|
# TODO: make this work. zpool import does not work after reboot.
|
||||||
|
# machine.crash()
|
||||||
|
# machine.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
# assert_facl()
|
||||||
|
# assert_mounts()
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue