grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / backup / sanoid.nix
blob823a2ed565e781121f2a12359525456d1c4cff63
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.sanoid;
5   datasetSettingsType = with lib.types;
6     (attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // {
7       description = "dataset/template options";
8     };
10   commonOptions = {
11     hourly = lib.mkOption {
12       description = "Number of hourly snapshots.";
13       type = with lib.types; nullOr ints.unsigned;
14       default = null;
15     };
17     daily = lib.mkOption {
18       description = "Number of daily snapshots.";
19       type = with lib.types; nullOr ints.unsigned;
20       default = null;
21     };
23     monthly = lib.mkOption {
24       description = "Number of monthly snapshots.";
25       type = with lib.types; nullOr ints.unsigned;
26       default = null;
27     };
29     yearly = lib.mkOption {
30       description = "Number of yearly snapshots.";
31       type = with lib.types; nullOr ints.unsigned;
32       default = null;
33     };
35     autoprune = lib.mkOption {
36       description = "Whether to automatically prune old snapshots.";
37       type = with lib.types; nullOr bool;
38       default = null;
39     };
41     autosnap = lib.mkOption {
42       description = "Whether to automatically take snapshots.";
43       type = with lib.types; nullOr bool;
44       default = null;
45     };
46   };
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";
54       });
55       default = [ ];
56     };
57     useTemplate = use_template;
59     recursive = lib.mkOption {
60       description = ''
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.
65       '';
66       type = with lib.types; oneOf [ bool (enum [ "zfs" ]) ];
67       default = false;
68     };
70     process_children_only = lib.mkOption {
71       description = "Whether to only snapshot child datasets if recursing.";
72       type = lib.types.bool;
73       default = false;
74     };
75     processChildrenOnly = process_children_only;
76   };
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"
86     zfsAction
87     "sanoid"
88     (lib.concatStringsSep "," permissions)
89     dataset
90   ];
92   configFile =
93     let
94       mkValueString = v:
95         if lib.isList v then lib.concatStringsSep "," v
96         else lib.generators.mkValueStringDefault { } v;
98       mkKeyValue = k: v:
99         if v == null then ""
100         else if k == "processChildrenOnly" then ""
101         else if k == "useTemplate" then ""
102         else lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
103     in
104     lib.generators.toINI { inherit mkKeyValue; } cfg.settings;
109   # Interface
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;
118       default = "hourly";
119       example = "daily";
120       description = ''
121         Run sanoid at this interval. The default is to run hourly.
123         The format is described in
124         {manpage}`systemd.time(7)`.
125       '';
126     };
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 { });
134       }));
135       default = { };
136       description = "Datasets to snapshot.";
137     };
139     templates = lib.mkOption {
140       type = lib.types.attrsOf (lib.types.submodule {
141         freeformType = datasetSettingsType;
142         options = commonOptions;
143       });
144       default = { };
145       description = "Templates for datasets.";
146     };
148     settings = lib.mkOption {
149       type = lib.types.attrsOf datasetSettingsType;
150       description = ''
151         Free-form settings written directly to the config file. See
152         <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
153         for allowed values.
154       '';
155     };
157     extraArgs = lib.mkOption {
158       type = lib.types.listOf lib.types.str;
159       default = [ ];
160       example = [ "--verbose" "--readonly" "--debug" ];
161       description = ''
162         Extra arguments to pass to sanoid. See
163         <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
164         for allowed options.
165       '';
166     };
167   };
169   # Implementation
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)
175     ];
177     systemd.services.sanoid = {
178       description = "Sanoid snapshot service";
179       serviceConfig = {
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"
184           "--cron"
185           "--configdir"
186           (pkgs.writeTextDir "sanoid.conf" configFile)
187         ] ++ cfg.extraArgs);
188         User = "sanoid";
189         Group = "sanoid";
190         DynamicUser = true;
191         RuntimeDirectory = "sanoid";
192         CacheDirectory = "sanoid";
193       };
194       # Prevents missing snapshots during DST changes
195       environment.TZ = "UTC";
196       after = [ "zfs.target" ];
197       startAt = cfg.interval;
198     };
199   };
201   meta.maintainers = with lib.maintainers; [ lopsided98 ];