1 { config, lib, pkgs, ... }:
3 cfg = config.services.sanoid;
5 datasetSettingsType = with lib.types;
6 (attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // {
7 description = "dataset/template options";
11 hourly = lib.mkOption {
12 description = "Number of hourly snapshots.";
13 type = with lib.types; nullOr ints.unsigned;
17 daily = lib.mkOption {
18 description = "Number of daily snapshots.";
19 type = with lib.types; nullOr ints.unsigned;
23 monthly = lib.mkOption {
24 description = "Number of monthly snapshots.";
25 type = with lib.types; nullOr ints.unsigned;
29 yearly = lib.mkOption {
30 description = "Number of yearly snapshots.";
31 type = with lib.types; nullOr ints.unsigned;
35 autoprune = lib.mkOption {
36 description = "Whether to automatically prune old snapshots.";
37 type = with lib.types; nullOr bool;
41 autosnap = lib.mkOption {
42 description = "Whether to automatically take snapshots.";
43 type = with lib.types; nullOr bool;
48 datasetOptions = rec {
49 use_template = lib.mkOption {
50 description = "Names of the templates to use for this dataset.";
51 type = lib.types.listOf (lib.types.str // {
52 check = (lib.types.enum (lib.attrNames cfg.templates)).check;
53 description = "configured template name";
57 useTemplate = use_template;
59 recursive = lib.mkOption {
61 Whether to recursively snapshot dataset children.
62 You can also set this to `"zfs"` to handle datasets
63 recursively in an atomic way without the possibility to
64 override settings for child datasets.
66 type = with lib.types; oneOf [ bool (enum [ "zfs" ]) ];
70 process_children_only = lib.mkOption {
71 description = "Whether to only snapshot child datasets if recursing.";
72 type = lib.types.bool;
75 processChildrenOnly = process_children_only;
78 # Extract unique dataset names
79 datasets = lib.unique (lib.attrNames cfg.datasets);
81 # Function to build "zfs allow" and "zfs unallow" commands for the
82 # filesystems we've delegated permissions to.
83 buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
84 # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
85 "-+/run/booted-system/sw/bin/zfs"
88 (lib.concatStringsSep "," permissions)
95 if lib.isList v then lib.concatStringsSep "," v
96 else lib.generators.mkValueStringDefault { } v;
100 else if k == "processChildrenOnly" then ""
101 else if k == "useTemplate" then ""
102 else lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
104 lib.generators.toINI { inherit mkKeyValue; } cfg.settings;
111 options.services.sanoid = {
112 enable = lib.mkEnableOption "Sanoid ZFS snapshotting service";
114 package = lib.mkPackageOption pkgs "sanoid" {};
116 interval = lib.mkOption {
117 type = lib.types.str;
121 Run sanoid at this interval. The default is to run hourly.
123 The format is described in
124 {manpage}`systemd.time(7)`.
128 datasets = lib.mkOption {
129 type = lib.types.attrsOf (lib.types.submodule ({ config, options, ... }: {
130 freeformType = datasetSettingsType;
131 options = commonOptions // datasetOptions;
132 config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (options.useTemplate or { });
133 config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (options.processChildrenOnly or { });
136 description = "Datasets to snapshot.";
139 templates = lib.mkOption {
140 type = lib.types.attrsOf (lib.types.submodule {
141 freeformType = datasetSettingsType;
142 options = commonOptions;
145 description = "Templates for datasets.";
148 settings = lib.mkOption {
149 type = lib.types.attrsOf datasetSettingsType;
151 Free-form settings written directly to the config file. See
152 <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
157 extraArgs = lib.mkOption {
158 type = lib.types.listOf lib.types.str;
160 example = [ "--verbose" "--readonly" "--debug" ];
162 Extra arguments to pass to sanoid. See
163 <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
171 config = lib.mkIf cfg.enable {
172 services.sanoid.settings = lib.mkMerge [
173 (lib.mapAttrs' (d: v: lib.nameValuePair ("template_" + d) v) cfg.templates)
174 (lib.mapAttrs (d: v: v) cfg.datasets)
177 systemd.services.sanoid = {
178 description = "Sanoid snapshot service";
180 ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets);
181 ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets);
182 ExecStart = lib.escapeShellArgs ([
183 "${cfg.package}/bin/sanoid"
186 (pkgs.writeTextDir "sanoid.conf" configFile)
191 RuntimeDirectory = "sanoid";
192 CacheDirectory = "sanoid";
194 # Prevents missing snapshots during DST changes
195 environment.TZ = "UTC";
196 after = [ "zfs.target" ];
197 startAt = cfg.interval;
201 meta.maintainers = with lib.maintainers; [ lopsided98 ];