8 cfg = config.services.sanoid;
21 description = "dataset/template options";
25 hourly = lib.mkOption {
26 description = "Number of hourly snapshots.";
27 type = with lib.types; nullOr ints.unsigned;
31 daily = lib.mkOption {
32 description = "Number of daily snapshots.";
33 type = with lib.types; nullOr ints.unsigned;
37 monthly = lib.mkOption {
38 description = "Number of monthly snapshots.";
39 type = with lib.types; nullOr ints.unsigned;
43 yearly = lib.mkOption {
44 description = "Number of yearly snapshots.";
45 type = with lib.types; nullOr ints.unsigned;
49 autoprune = lib.mkOption {
50 description = "Whether to automatically prune old snapshots.";
51 type = with lib.types; nullOr bool;
55 autosnap = lib.mkOption {
56 description = "Whether to automatically take snapshots.";
57 type = with lib.types; nullOr bool;
61 pre_snapshot_script = lib.mkOption {
62 description = "Script to run before taking snapshot.";
63 type = with lib.types; nullOr str;
67 post_snapshot_script = lib.mkOption {
68 description = "Script to run after taking snapshot.";
69 type = with lib.types; nullOr str;
73 pruning_script = lib.mkOption {
74 description = "Script to run after pruning snapshot.";
75 type = with lib.types; nullOr str;
79 no_inconsistent_snapshot = lib.mkOption {
80 description = "Whether to take a snapshot if the pre script fails";
81 type = with lib.types; nullOr bool;
85 force_post_snapshot_script = lib.mkOption {
86 description = "Whether to run the post script if the pre script fails";
87 type = with lib.types; nullOr bool;
91 script_timeout = lib.mkOption {
92 description = "Time limit for pre/post/pruning script execution time (<=0 for infinite).";
93 type = with lib.types; nullOr int;
98 datasetOptions = rec {
99 use_template = lib.mkOption {
100 description = "Names of the templates to use for this dataset.";
101 type = lib.types.listOf (
104 check = (lib.types.enum (lib.attrNames cfg.templates)).check;
105 description = "configured template name";
110 useTemplate = use_template;
112 recursive = lib.mkOption {
114 Whether to recursively snapshot dataset children.
115 You can also set this to `"zfs"` to handle datasets
116 recursively in an atomic way without the possibility to
117 override settings for child datasets.
128 process_children_only = lib.mkOption {
129 description = "Whether to only snapshot child datasets if recursing.";
130 type = lib.types.bool;
133 processChildrenOnly = process_children_only;
136 # Extract unique dataset names
137 datasets = lib.unique (lib.attrNames cfg.datasets);
139 # Function to build "zfs allow" and "zfs unallow" commands for the
140 # filesystems we've delegated permissions to.
142 zfsAction: permissions: dataset:
143 lib.escapeShellArgs [
144 # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
145 "-+/run/booted-system/sw/bin/zfs"
148 (lib.concatStringsSep "," permissions)
155 v: if lib.isList v then lib.concatStringsSep "," v else lib.generators.mkValueStringDefault { } v;
161 else if k == "processChildrenOnly" then
163 else if k == "useTemplate" then
166 lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
168 lib.generators.toINI { inherit mkKeyValue; } cfg.settings;
175 options.services.sanoid = {
176 enable = lib.mkEnableOption "Sanoid ZFS snapshotting service";
178 package = lib.mkPackageOption pkgs "sanoid" { };
180 interval = lib.mkOption {
181 type = lib.types.str;
185 Run sanoid at this interval. The default is to run hourly.
187 The format is described in
188 {manpage}`systemd.time(7)`.
192 datasets = lib.mkOption {
193 type = lib.types.attrsOf (
194 lib.types.submodule (
195 { config, options, ... }:
197 freeformType = datasetSettingsType;
198 options = commonOptions // datasetOptions;
199 config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
200 options.useTemplate or { }
202 config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id (
203 options.processChildrenOnly or { }
209 description = "Datasets to snapshot.";
212 templates = lib.mkOption {
213 type = lib.types.attrsOf (
214 lib.types.submodule {
215 freeformType = datasetSettingsType;
216 options = commonOptions;
220 description = "Templates for datasets.";
223 settings = lib.mkOption {
224 type = lib.types.attrsOf datasetSettingsType;
226 Free-form settings written directly to the config file. See
227 <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
232 extraArgs = lib.mkOption {
233 type = lib.types.listOf lib.types.str;
241 Extra arguments to pass to sanoid. See
242 <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
250 config = lib.mkIf cfg.enable {
251 services.sanoid.settings = lib.mkMerge [
252 (lib.mapAttrs' (d: v: lib.nameValuePair ("template_" + d) v) cfg.templates)
253 (lib.mapAttrs (d: v: v) cfg.datasets)
256 systemd.services.sanoid = {
257 description = "Sanoid snapshot service";
260 map (buildAllowCommand "allow" [
267 map (buildAllowCommand "unallow" [
273 ExecStart = lib.escapeShellArgs (
275 "${cfg.package}/bin/sanoid"
278 (pkgs.writeTextDir "sanoid.conf" configFile)
285 RuntimeDirectory = "sanoid";
286 CacheDirectory = "sanoid";
288 # Prevents missing snapshots during DST changes
289 environment.TZ = "UTC";
290 after = [ "zfs.target" ];
291 startAt = cfg.interval;
294 # Put those packages in the global environment
295 # so that syncoid can find them when targeting this host through ssh.
296 environment.systemPackages = with pkgs; [
302 meta.maintainers = with lib.maintainers; [ lopsided98 ];