at 25.11-pre 6.7 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.sanoid; 9 10 datasetSettingsType = 11 with lib.types; 12 (attrsOf ( 13 nullOr (oneOf [ 14 str 15 int 16 bool 17 (listOf str) 18 ]) 19 )) 20 // { 21 description = "dataset/template options"; 22 }; 23 24 commonOptions = { 25 hourly = lib.mkOption { 26 description = "Number of hourly snapshots."; 27 type = with lib.types; nullOr ints.unsigned; 28 default = null; 29 }; 30 31 daily = lib.mkOption { 32 description = "Number of daily snapshots."; 33 type = with lib.types; nullOr ints.unsigned; 34 default = null; 35 }; 36 37 monthly = lib.mkOption { 38 description = "Number of monthly snapshots."; 39 type = with lib.types; nullOr ints.unsigned; 40 default = null; 41 }; 42 43 yearly = lib.mkOption { 44 description = "Number of yearly snapshots."; 45 type = with lib.types; nullOr ints.unsigned; 46 default = null; 47 }; 48 49 autoprune = lib.mkOption { 50 description = "Whether to automatically prune old snapshots."; 51 type = with lib.types; nullOr bool; 52 default = null; 53 }; 54 55 autosnap = lib.mkOption { 56 description = "Whether to automatically take snapshots."; 57 type = with lib.types; nullOr bool; 58 default = null; 59 }; 60 }; 61 62 datasetOptions = rec { 63 use_template = lib.mkOption { 64 description = "Names of the templates to use for this dataset."; 65 type = lib.types.listOf ( 66 lib.types.str 67 // { 68 check = (lib.types.enum (lib.attrNames cfg.templates)).check; 69 description = "configured template name"; 70 } 71 ); 72 default = [ ]; 73 }; 74 useTemplate = use_template; 75 76 recursive = lib.mkOption { 77 description = '' 78 Whether to recursively snapshot dataset children. 79 You can also set this to `"zfs"` to handle datasets 80 recursively in an atomic way without the possibility to 81 override settings for child datasets. 82 ''; 83 type = 84 with lib.types; 85 oneOf [ 86 bool 87 (enum [ "zfs" ]) 88 ]; 89 default = false; 90 }; 91 92 process_children_only = lib.mkOption { 93 description = "Whether to only snapshot child datasets if recursing."; 94 type = lib.types.bool; 95 default = false; 96 }; 97 processChildrenOnly = process_children_only; 98 }; 99 100 # Extract unique dataset names 101 datasets = lib.unique (lib.attrNames cfg.datasets); 102 103 # Function to build "zfs allow" and "zfs unallow" commands for the 104 # filesystems we've delegated permissions to. 105 buildAllowCommand = 106 zfsAction: permissions: dataset: 107 lib.escapeShellArgs [ 108 # Here we explicitly use the booted system to guarantee the stable API needed by ZFS 109 "-+/run/booted-system/sw/bin/zfs" 110 zfsAction 111 "sanoid" 112 (lib.concatStringsSep "," permissions) 113 dataset 114 ]; 115 116 configFile = 117 let 118 mkValueString = 119 v: if lib.isList v then lib.concatStringsSep "," v else lib.generators.mkValueStringDefault { } v; 120 121 mkKeyValue = 122 k: v: 123 if v == null then 124 "" 125 else if k == "processChildrenOnly" then 126 "" 127 else if k == "useTemplate" then 128 "" 129 else 130 lib.generators.mkKeyValueDefault { inherit mkValueString; } "=" k v; 131 in 132 lib.generators.toINI { inherit mkKeyValue; } cfg.settings; 133 134in 135{ 136 137 # Interface 138 139 options.services.sanoid = { 140 enable = lib.mkEnableOption "Sanoid ZFS snapshotting service"; 141 142 package = lib.mkPackageOption pkgs "sanoid" { }; 143 144 interval = lib.mkOption { 145 type = lib.types.str; 146 default = "hourly"; 147 example = "daily"; 148 description = '' 149 Run sanoid at this interval. The default is to run hourly. 150 151 The format is described in 152 {manpage}`systemd.time(7)`. 153 ''; 154 }; 155 156 datasets = lib.mkOption { 157 type = lib.types.attrsOf ( 158 lib.types.submodule ( 159 { config, options, ... }: 160 { 161 freeformType = datasetSettingsType; 162 options = commonOptions // datasetOptions; 163 config.use_template = lib.modules.mkAliasAndWrapDefsWithPriority lib.id ( 164 options.useTemplate or { } 165 ); 166 config.process_children_only = lib.modules.mkAliasAndWrapDefsWithPriority lib.id ( 167 options.processChildrenOnly or { } 168 ); 169 } 170 ) 171 ); 172 default = { }; 173 description = "Datasets to snapshot."; 174 }; 175 176 templates = lib.mkOption { 177 type = lib.types.attrsOf ( 178 lib.types.submodule { 179 freeformType = datasetSettingsType; 180 options = commonOptions; 181 } 182 ); 183 default = { }; 184 description = "Templates for datasets."; 185 }; 186 187 settings = lib.mkOption { 188 type = lib.types.attrsOf datasetSettingsType; 189 description = '' 190 Free-form settings written directly to the config file. See 191 <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf> 192 for allowed values. 193 ''; 194 }; 195 196 extraArgs = lib.mkOption { 197 type = lib.types.listOf lib.types.str; 198 default = [ ]; 199 example = [ 200 "--verbose" 201 "--readonly" 202 "--debug" 203 ]; 204 description = '' 205 Extra arguments to pass to sanoid. See 206 <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options> 207 for allowed options. 208 ''; 209 }; 210 }; 211 212 # Implementation 213 214 config = lib.mkIf cfg.enable { 215 services.sanoid.settings = lib.mkMerge [ 216 (lib.mapAttrs' (d: v: lib.nameValuePair ("template_" + d) v) cfg.templates) 217 (lib.mapAttrs (d: v: v) cfg.datasets) 218 ]; 219 220 systemd.services.sanoid = { 221 description = "Sanoid snapshot service"; 222 serviceConfig = { 223 ExecStartPre = ( 224 map (buildAllowCommand "allow" [ 225 "snapshot" 226 "mount" 227 "destroy" 228 ]) datasets 229 ); 230 ExecStopPost = ( 231 map (buildAllowCommand "unallow" [ 232 "snapshot" 233 "mount" 234 "destroy" 235 ]) datasets 236 ); 237 ExecStart = lib.escapeShellArgs ( 238 [ 239 "${cfg.package}/bin/sanoid" 240 "--cron" 241 "--configdir" 242 (pkgs.writeTextDir "sanoid.conf" configFile) 243 ] 244 ++ cfg.extraArgs 245 ); 246 User = "sanoid"; 247 Group = "sanoid"; 248 DynamicUser = true; 249 RuntimeDirectory = "sanoid"; 250 CacheDirectory = "sanoid"; 251 }; 252 # Prevents missing snapshots during DST changes 253 environment.TZ = "UTC"; 254 after = [ "zfs.target" ]; 255 startAt = cfg.interval; 256 }; 257 }; 258 259 meta.maintainers = with lib.maintainers; [ lopsided98 ]; 260}