2019-10-26 23:37:30 -04:00
|
|
|
{
|
|
|
|
config,
|
|
|
|
lib,
|
|
|
|
pkgs,
|
|
|
|
...
|
|
|
|
}:
|
|
|
|
let
|
|
|
|
cfg = config.services.sanoid;
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
datasetSettingsType =
|
|
|
|
with lib.types;
|
2019-10-26 23:37:30 -04:00
|
|
|
(attrsOf (
|
|
|
|
nullOr (oneOf [
|
|
|
|
str
|
|
|
|
int
|
|
|
|
bool
|
|
|
|
(listOf str)
|
|
|
|
])
|
|
|
|
))
|
|
|
|
// {
|
|
|
|
description = "dataset/template options";
|
|
|
|
};
|
|
|
|
|
|
|
|
commonOptions = {
|
2024-08-28 21:18:54 +02:00
|
|
|
hourly = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Number of hourly snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr ints.unsigned;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
daily = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Number of daily snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr ints.unsigned;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
monthly = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Number of monthly snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr ints.unsigned;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
yearly = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Number of yearly snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr ints.unsigned;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
autoprune = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Whether to automatically prune old snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr bool;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
autosnap = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Whether to automatically take snapshots.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = with lib.types; nullOr bool;
|
2020-03-31 18:09:59 +02:00
|
|
|
default = null;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-03-31 18:09:59 +02:00
|
|
|
datasetOptions = rec {
|
2024-08-28 21:18:54 +02:00
|
|
|
use_template = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Names of the templates to use for this dataset.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = lib.types.listOf (
|
|
|
|
lib.types.str
|
|
|
|
// {
|
|
|
|
check = (lib.types.enum (lib.attrNames cfg.templates)).check;
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "configured template name";
|
2020-03-31 18:09:59 +02:00
|
|
|
}
|
|
|
|
);
|
2021-07-25 18:31:42 +02:00
|
|
|
default = [ ];
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
2020-03-31 18:09:59 +02:00
|
|
|
useTemplate = use_template;
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
recursive = lib.mkOption {
|
2021-09-02 14:35:30 +03:00
|
|
|
description = ''
|
|
|
|
Whether to recursively snapshot dataset children.
|
|
|
|
You can also set this to `"zfs"` to handle datasets
|
|
|
|
recursively in an atomic way without the possibility to
|
|
|
|
override settings for child datasets.
|
|
|
|
'';
|
2024-08-28 21:18:54 +02:00
|
|
|
type =
|
|
|
|
with lib.types;
|
|
|
|
oneOf [
|
|
|
|
bool
|
|
|
|
(enum [ "zfs" ])
|
|
|
|
];
|
2019-10-26 23:37:30 -04:00
|
|
|
default = false;
|
|
|
|
};
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
process_children_only = lib.mkOption {
|
2019-10-26 23:37:30 -04:00
|
|
|
description = "Whether to only snapshot child datasets if recursing.";
|
2024-08-28 21:18:54 +02:00
|
|
|
type = lib.types.bool;
|
2019-10-26 23:37:30 -04:00
|
|
|
default = false;
|
|
|
|
};
|
2020-03-31 18:09:59 +02:00
|
|
|
processChildrenOnly = process_children_only;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
|
|
|
|
2021-07-25 10:00:37 +02:00
|
|
|
# Extract unique dataset names
|
2024-08-28 21:18:54 +02:00
|
|
|
datasets = lib.unique (lib.attrNames cfg.datasets);
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2021-07-25 18:27:36 +02:00
|
|
|
# Function to build "zfs allow" and "zfs unallow" commands for the
|
|
|
|
# filesystems we've delegated permissions to.
|
|
|
|
buildAllowCommand =
|
|
|
|
zfsAction: permissions: dataset:
|
|
|
|
lib.escapeShellArgs [
|
|
|
|
# Here we explicitly use the booted system to guarantee the stable API needed by ZFS
|
|
|
|
"-+/run/booted-system/sw/bin/zfs"
|
|
|
|
zfsAction
|
|
|
|
"sanoid"
|
2024-08-28 21:18:54 +02:00
|
|
|
(lib.concatStringsSep "," permissions)
|
2021-07-25 18:27:36 +02:00
|
|
|
dataset
|
|
|
|
];
|
|
|
|
|
2021-07-25 18:31:42 +02:00
|
|
|
configFile =
|
|
|
|
let
|
|
|
|
mkValueString =
|
2024-08-28 21:18:54 +02:00
|
|
|
v: if lib.isList v then lib.concatStringsSep "," v else lib.generators.mkValueStringDefault { } v;
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2021-07-25 18:31:42 +02:00
|
|
|
mkKeyValue =
|
|
|
|
k: v:
|
|
|
|
if v == null then
|
|
|
|
""
|
|
|
|
else if k == "processChildrenOnly" then
|
|
|
|
""
|
|
|
|
else if k == "useTemplate" then
|
|
|
|
""
|
2024-08-28 21:18:54 +02:00
|
|
|
else
|
|
|
|
lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
|
2021-07-25 18:31:42 +02:00
|
|
|
in
|
2024-08-28 21:18:54 +02:00
|
|
|
lib.generators.toINI { inherit mkKeyValue; } cfg.settings;
|
2021-07-25 18:31:42 +02:00
|
|
|
|
|
|
|
in
|
|
|
|
{
|
|
|
|
|
|
|
|
# Interface
|
|
|
|
|
|
|
|
options.services.sanoid = {
|
2024-08-28 21:18:54 +02:00
|
|
|
enable = lib.mkEnableOption "Sanoid ZFS snapshotting service";
|
2021-07-25 18:31:42 +02:00
|
|
|
|
2023-11-30 19:03:14 +01:00
|
|
|
package = lib.mkPackageOption pkgs "sanoid" { };
|
2023-03-19 01:34:01 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
interval = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
2021-07-25 18:31:42 +02:00
|
|
|
default = "hourly";
|
|
|
|
example = "daily";
|
|
|
|
description = ''
|
|
|
|
Run sanoid at this interval. The default is to run hourly.
|
|
|
|
|
|
|
|
The format is described in
|
|
|
|
{manpage}`systemd.time(7)`.
|
|
|
|
'';
|
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
datasets = lib.mkOption {
|
|
|
|
type = lib.types.attrsOf (
|
|
|
|
lib.types.submodule (
|
|
|
|
{ config, options, ... }:
|
|
|
|
{
|
2021-07-25 18:31:42 +02:00
|
|
|
freeformType = datasetSettingsType;
|
|
|
|
options = commonOptions // datasetOptions;
|
2024-08-28 21:18:54 +02:00
|
|
|
config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
|
|
|
|
options.useTemplate or { }
|
|
|
|
);
|
|
|
|
config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
|
|
|
|
options.processChildrenOnly or { }
|
|
|
|
);
|
2021-07-25 18:31:42 +02:00
|
|
|
}
|
2025-04-01 20:10:43 +02:00
|
|
|
)
|
2021-07-25 18:31:42 +02:00
|
|
|
);
|
|
|
|
default = { };
|
|
|
|
description = "Datasets to snapshot.";
|
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
templates = lib.mkOption {
|
|
|
|
type = lib.types.attrsOf (
|
|
|
|
lib.types.submodule {
|
2021-07-25 18:31:42 +02:00
|
|
|
freeformType = datasetSettingsType;
|
|
|
|
options = commonOptions;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
default = { };
|
|
|
|
description = "Templates for datasets.";
|
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
settings = lib.mkOption {
|
|
|
|
type = lib.types.attrsOf datasetSettingsType;
|
2021-07-25 18:31:42 +02:00
|
|
|
description = ''
|
|
|
|
Free-form settings written directly to the config file. See
|
|
|
|
<https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
|
|
|
|
for allowed values.
|
|
|
|
'';
|
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
extraArgs = lib.mkOption {
|
|
|
|
type = lib.types.listOf lib.types.str;
|
2021-07-25 18:31:42 +02:00
|
|
|
default = [ ];
|
|
|
|
example = [
|
|
|
|
"--verbose"
|
|
|
|
"--readonly"
|
|
|
|
"--debug"
|
|
|
|
];
|
|
|
|
description = ''
|
|
|
|
Extra arguments to pass to sanoid. See
|
|
|
|
<https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
|
|
|
|
for allowed options.
|
|
|
|
'';
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
2021-07-25 18:31:42 +02:00
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2021-07-25 18:31:42 +02:00
|
|
|
# Implementation
|
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
config = lib.mkIf cfg.enable {
|
|
|
|
services.sanoid.settings = lib.mkMerge [
|
|
|
|
(lib.mapAttrs' (d: v: lib.nameValuePair ("template_" + d) v) cfg.templates)
|
|
|
|
(lib.mapAttrs (d: v: v) cfg.datasets)
|
2021-07-25 18:31:42 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
systemd.services.sanoid = {
|
|
|
|
description = "Sanoid snapshot service";
|
|
|
|
serviceConfig = {
|
|
|
|
ExecStartPre = (
|
|
|
|
map (buildAllowCommand "allow" [
|
|
|
|
"snapshot"
|
|
|
|
"mount"
|
|
|
|
"destroy"
|
|
|
|
]) datasets
|
|
|
|
);
|
|
|
|
ExecStopPost = (
|
|
|
|
map (buildAllowCommand "unallow" [
|
|
|
|
"snapshot"
|
2025-04-01 20:10:43 +02:00
|
|
|
"mount"
|
2021-07-25 18:31:42 +02:00
|
|
|
"destroy"
|
|
|
|
]) datasets
|
2025-04-01 20:10:43 +02:00
|
|
|
);
|
2021-07-25 18:31:42 +02:00
|
|
|
ExecStart = lib.escapeShellArgs (
|
|
|
|
[
|
2023-03-19 01:34:01 -04:00
|
|
|
"${cfg.package}/bin/sanoid"
|
2021-07-25 18:31:42 +02:00
|
|
|
"--cron"
|
|
|
|
"--configdir"
|
|
|
|
(pkgs.writeTextDir "sanoid.conf" configFile)
|
|
|
|
]
|
|
|
|
++ cfg.extraArgs
|
|
|
|
);
|
|
|
|
User = "sanoid";
|
|
|
|
Group = "sanoid";
|
|
|
|
DynamicUser = true;
|
|
|
|
RuntimeDirectory = "sanoid";
|
|
|
|
CacheDirectory = "sanoid";
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
2021-07-25 18:31:42 +02:00
|
|
|
# Prevents missing snapshots during DST changes
|
|
|
|
environment.TZ = "UTC";
|
|
|
|
after = [ "zfs.target" ];
|
|
|
|
startAt = cfg.interval;
|
2019-10-26 23:37:30 -04:00
|
|
|
};
|
2021-07-25 18:31:42 +02:00
|
|
|
};
|
2019-10-26 23:37:30 -04:00
|
|
|
|
2024-08-28 21:18:54 +02:00
|
|
|
meta.maintainers = with lib.maintainers; [ lopsided98 ];
|
2021-07-25 18:31:42 +02:00
|
|
|
}
|