python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / backup / sanoid.nix
bloba51708170fbfd9eb7f3e1da583a02cf86dbe3c8b
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.sanoid;
8   datasetSettingsType = with types;
9     (attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // {
10       description = "dataset/template options";
11     };
13   commonOptions = {
14     hourly = mkOption {
15       description = lib.mdDoc "Number of hourly snapshots.";
16       type = with types; nullOr ints.unsigned;
17       default = null;
18     };
20     daily = mkOption {
21       description = lib.mdDoc "Number of daily snapshots.";
22       type = with types; nullOr ints.unsigned;
23       default = null;
24     };
26     monthly = mkOption {
27       description = lib.mdDoc "Number of monthly snapshots.";
28       type = with types; nullOr ints.unsigned;
29       default = null;
30     };
32     yearly = mkOption {
33       description = lib.mdDoc "Number of yearly snapshots.";
34       type = with types; nullOr ints.unsigned;
35       default = null;
36     };
38     autoprune = mkOption {
39       description = lib.mdDoc "Whether to automatically prune old snapshots.";
40       type = with types; nullOr bool;
41       default = null;
42     };
44     autosnap = mkOption {
45       description = lib.mdDoc "Whether to automatically take snapshots.";
46       type = with types; nullOr bool;
47       default = null;
48     };
49   };
51   datasetOptions = rec {
52     use_template = mkOption {
53       description = lib.mdDoc "Names of the templates to use for this dataset.";
54       type = types.listOf (types.str // {
55         check = (types.enum (attrNames cfg.templates)).check;
56         description = "configured template name";
57       });
58       default = [ ];
59     };
60     useTemplate = use_template;
62     recursive = mkOption {
63       description = lib.mdDoc ''
64         Whether to recursively snapshot dataset children.
65         You can also set this to `"zfs"` to handle datasets
66         recursively in an atomic way without the possibility to
67         override settings for child datasets.
68       '';
69       type = with types; oneOf [ bool (enum [ "zfs" ]) ];
70       default = false;
71     };
73     process_children_only = mkOption {
74       description = lib.mdDoc "Whether to only snapshot child datasets if recursing.";
75       type = types.bool;
76       default = false;
77     };
78     processChildrenOnly = process_children_only;
79   };
81   # Extract unique dataset names
82   datasets = unique (attrNames cfg.datasets);
84   # Function to build "zfs allow" and "zfs unallow" commands for the
85   # filesystems we've delegated permissions to.
86   buildAllowCommand = zfsAction: permissions: dataset: lib.escapeShellArgs [
87     # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
88     "-+/run/booted-system/sw/bin/zfs"
89     zfsAction
90     "sanoid"
91     (concatStringsSep "," permissions)
92     dataset
93   ];
95   configFile =
96     let
97       mkValueString = v:
98         if builtins.isList v then concatStringsSep "," v
99         else generators.mkValueStringDefault { } v;
101       mkKeyValue = k: v:
102         if v == null then ""
103         else if k == "processChildrenOnly" then ""
104         else if k == "useTemplate" then ""
105         else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
106     in
107     generators.toINI { inherit mkKeyValue; } cfg.settings;
112   # Interface
114   options.services.sanoid = {
115     enable = mkEnableOption (lib.mdDoc "Sanoid ZFS snapshotting service");
117     interval = mkOption {
118       type = types.str;
119       default = "hourly";
120       example = "daily";
121       description = lib.mdDoc ''
122         Run sanoid at this interval. The default is to run hourly.
124         The format is described in
125         {manpage}`systemd.time(7)`.
126       '';
127     };
129     datasets = mkOption {
130       type = types.attrsOf (types.submodule ({ config, options, ... }: {
131         freeformType = datasetSettingsType;
132         options = commonOptions // datasetOptions;
133         config.use_template = modules.mkAliasAndWrapDefsWithPriority id (options.useTemplate or { });
134         config.process_children_only = modules.mkAliasAndWrapDefsWithPriority id (options.processChildrenOnly or { });
135       }));
136       default = { };
137       description = lib.mdDoc "Datasets to snapshot.";
138     };
140     templates = mkOption {
141       type = types.attrsOf (types.submodule {
142         freeformType = datasetSettingsType;
143         options = commonOptions;
144       });
145       default = { };
146       description = lib.mdDoc "Templates for datasets.";
147     };
149     settings = mkOption {
150       type = types.attrsOf datasetSettingsType;
151       description = lib.mdDoc ''
152         Free-form settings written directly to the config file. See
153         <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
154         for allowed values.
155       '';
156     };
158     extraArgs = mkOption {
159       type = types.listOf types.str;
160       default = [ ];
161       example = [ "--verbose" "--readonly" "--debug" ];
162       description = lib.mdDoc ''
163         Extra arguments to pass to sanoid. See
164         <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
165         for allowed options.
166       '';
167     };
168   };
170   # Implementation
172   config = mkIf cfg.enable {
173     services.sanoid.settings = mkMerge [
174       (mapAttrs' (d: v: nameValuePair ("template_" + d) v) cfg.templates)
175       (mapAttrs (d: v: v) cfg.datasets)
176     ];
178     systemd.services.sanoid = {
179       description = "Sanoid snapshot service";
180       serviceConfig = {
181         ExecStartPre = (map (buildAllowCommand "allow" [ "snapshot" "mount" "destroy" ]) datasets);
182         ExecStopPost = (map (buildAllowCommand "unallow" [ "snapshot" "mount" "destroy" ]) datasets);
183         ExecStart = lib.escapeShellArgs ([
184           "${pkgs.sanoid}/bin/sanoid"
185           "--cron"
186           "--configdir"
187           (pkgs.writeTextDir "sanoid.conf" configFile)
188         ] ++ cfg.extraArgs);
189         User = "sanoid";
190         Group = "sanoid";
191         DynamicUser = true;
192         RuntimeDirectory = "sanoid";
193         CacheDirectory = "sanoid";
194       };
195       # Prevents missing snapshots during DST changes
196       environment.TZ = "UTC";
197       after = [ "zfs.target" ];
198       startAt = cfg.interval;
199     };
200   };
202   meta.maintainers = with maintainers; [ lopsided98 ];