at 17.09-beta 13 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3with lib; 4 5let 6 gcfg = config.services.tarsnap; 7 8 configFile = name: cfg: '' 9 keyfile ${cfg.keyfile} 10 ${optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"} 11 ${optionalString cfg.nodump "nodump"} 12 ${optionalString cfg.printStats "print-stats"} 13 ${optionalString cfg.printStats "humanize-numbers"} 14 ${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)} 15 ${optionalString cfg.aggressiveNetworking "aggressive-networking"} 16 ${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)} 17 ${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)} 18 ${optionalString cfg.lowmem "lowmem"} 19 ${optionalString cfg.verylowmem "verylowmem"} 20 ${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"} 21 ${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"} 22 ${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"} 23 ''; 24in 25{ 26 options = { 27 services.tarsnap = { 28 enable = mkOption { 29 type = types.bool; 30 default = false; 31 description = '' 32 Enable periodic tarsnap backups. 33 ''; 34 }; 35 36 keyfile = mkOption { 37 type = types.str; 38 default = "/root/tarsnap.key"; 39 description = '' 40 The keyfile which associates this machine with your tarsnap 41 account. 42 Create the keyfile with <command>tarsnap-keygen</command>. 43 44 Note that each individual archive (specified below) may also have its 45 own individual keyfile specified. Tarsnap does not allow multiple 46 concurrent backups with the same cache directory and key (starting a 47 new backup will cause another one to fail). If you have multiple 48 archives specified, you should either spread out your backups to be 49 far apart, or specify a separate key for each archive. By default 50 every archive defaults to using 51 <literal>"/root/tarsnap.key"</literal>. 52 53 It's recommended for backups that you generate a key for every archive 54 using <literal>tarsnap-keygen(1)</literal>, and then generate a 55 write-only tarsnap key using <literal>tarsnap-keymgmt(1)</literal>, 56 and keep your master key(s) for a particular machine off-site. 57 58 The keyfile name should be given as a string and not a path, to 59 avoid the key being copied into the Nix store. 60 ''; 61 }; 62 63 archives = mkOption { 64 type = types.attrsOf (types.submodule ({ config, ... }: 65 { 66 options = { 67 keyfile = mkOption { 68 type = types.str; 69 default = gcfg.keyfile; 70 description = '' 71 Set a specific keyfile for this archive. This defaults to 72 <literal>"/root/tarsnap.key"</literal> if left unspecified. 73 74 Use this option if you want to run multiple backups 75 concurrently - each archive must have a unique key. You can 76 generate a write-only key derived from your master key (which 77 is recommended) using <literal>tarsnap-keymgmt(1)</literal>. 78 79 Note: every archive must have an individual master key. You 80 must generate multiple keys with 81 <literal>tarsnap-keygen(1)</literal>, and then generate write 82 only keys from those. 83 84 The keyfile name should be given as a string and not a path, to 85 avoid the key being copied into the Nix store. 86 ''; 87 }; 88 89 cachedir = mkOption { 90 type = types.nullOr types.path; 91 default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}"; 92 description = '' 93 The cache allows tarsnap to identify previously stored data 94 blocks, reducing archival time and bandwidth usage. 95 96 Should the cache become desynchronized or corrupted, tarsnap 97 will refuse to run until you manually rebuild the cache with 98 <command>tarsnap --fsck</command>. 99 100 Set to <literal>null</literal> to disable caching. 101 ''; 102 }; 103 104 nodump = mkOption { 105 type = types.bool; 106 default = true; 107 description = '' 108 Exclude files with the <literal>nodump</literal> flag. 109 ''; 110 }; 111 112 printStats = mkOption { 113 type = types.bool; 114 default = true; 115 description = '' 116 Print global archive statistics upon completion. 117 The output is available via 118 <command>systemctl status tarsnap@archive-name</command>. 119 ''; 120 }; 121 122 checkpointBytes = mkOption { 123 type = types.nullOr types.str; 124 default = "1GB"; 125 description = '' 126 Create a checkpoint every <literal>checkpointBytes</literal> 127 of uploaded data (optionally specified using an SI prefix). 128 129 1GB is the minimum value. A higher value is recommended, 130 as checkpointing is expensive. 131 132 Set to <literal>null</literal> to disable checkpointing. 133 ''; 134 }; 135 136 period = mkOption { 137 type = types.str; 138 default = "01:15"; 139 example = "hourly"; 140 description = '' 141 Create archive at this interval. 142 143 The format is described in 144 <citerefentry><refentrytitle>systemd.time</refentrytitle> 145 <manvolnum>7</manvolnum></citerefentry>. 146 ''; 147 }; 148 149 aggressiveNetworking = mkOption { 150 type = types.bool; 151 default = false; 152 description = '' 153 Upload data over multiple TCP connections, potentially 154 increasing tarsnap's bandwidth utilisation at the cost 155 of slowing down all other network traffic. Not 156 recommended unless TCP congestion is the dominant 157 limiting factor. 158 ''; 159 }; 160 161 directories = mkOption { 162 type = types.listOf types.path; 163 default = []; 164 description = "List of filesystem paths to archive."; 165 }; 166 167 excludes = mkOption { 168 type = types.listOf types.str; 169 default = []; 170 description = '' 171 Exclude files and directories matching these patterns. 172 ''; 173 }; 174 175 includes = mkOption { 176 type = types.listOf types.str; 177 default = []; 178 description = '' 179 Include only files and directories matching these 180 patterns (the empty list includes everything). 181 182 Exclusions have precedence over inclusions. 183 ''; 184 }; 185 186 lowmem = mkOption { 187 type = types.bool; 188 default = false; 189 description = '' 190 Reduce memory consumption by not caching small files. 191 Possibly beneficial if the average file size is smaller 192 than 1 MB and the number of files is lower than the 193 total amount of RAM in KB. 194 ''; 195 }; 196 197 verylowmem = mkOption { 198 type = types.bool; 199 default = false; 200 description = '' 201 Reduce memory consumption by a factor of 2 beyond what 202 <literal>lowmem</literal> does, at the cost of significantly 203 slowing down the archiving process. 204 ''; 205 }; 206 207 maxbw = mkOption { 208 type = types.nullOr types.int; 209 default = null; 210 description = '' 211 Abort archival if upstream bandwidth usage in bytes 212 exceeds this threshold. 213 ''; 214 }; 215 216 maxbwRateUp = mkOption { 217 type = types.nullOr types.int; 218 default = null; 219 example = literalExample "25 * 1000"; 220 description = '' 221 Upload bandwidth rate limit in bytes. 222 ''; 223 }; 224 225 maxbwRateDown = mkOption { 226 type = types.nullOr types.int; 227 default = null; 228 example = literalExample "50 * 1000"; 229 description = '' 230 Download bandwidth rate limit in bytes. 231 ''; 232 }; 233 234 verbose = mkOption { 235 type = types.bool; 236 default = false; 237 description = '' 238 Whether to produce verbose logging output. 239 ''; 240 }; 241 }; 242 } 243 )); 244 245 default = {}; 246 247 example = literalExample '' 248 { 249 nixos = 250 { directories = [ "/home" "/root/ssl" ]; 251 }; 252 253 gamedata = 254 { directories = [ "/var/lib/minecraft" ]; 255 period = "*:30"; 256 }; 257 } 258 ''; 259 260 description = '' 261 Tarsnap archive configurations. Each attribute names an archive 262 to be created at a given time interval, according to the options 263 associated with it. When uploading to the tarsnap server, 264 archive names are suffixed by a 1 second resolution timestamp. 265 266 For each member of the set is created a timer which triggers the 267 instanced <literal>tarsnap-archive-name</literal> service unit. You may use 268 <command>systemctl start tarsnap-archive-name</command> to 269 manually trigger creation of <literal>archive-name</literal> at 270 any time. 271 ''; 272 }; 273 }; 274 }; 275 276 config = mkIf gcfg.enable { 277 assertions = 278 (mapAttrsToList (name: cfg: 279 { assertion = cfg.directories != []; 280 message = "Must specify paths for tarsnap to back up"; 281 }) gcfg.archives) ++ 282 (mapAttrsToList (name: cfg: 283 { assertion = !(cfg.lowmem && cfg.verylowmem); 284 message = "You cannot set both lowmem and verylowmem"; 285 }) gcfg.archives); 286 287 systemd.services = 288 mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" { 289 description = "Tarsnap archive '${name}'"; 290 requires = [ "network-online.target" ]; 291 after = [ "network-online.target" ]; 292 293 path = [ pkgs.iputils pkgs.tarsnap pkgs.utillinux ]; 294 295 # In order for the persistent tarsnap timer to work reliably, we have to 296 # make sure that the tarsnap server is reachable after systemd starts up 297 # the service - therefore we sleep in a loop until we can ping the 298 # endpoint. 299 preStart = '' 300 while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done 301 ''; 302 303 script = 304 let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" \ 305 -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \ 306 ${optionalString cfg.verbose "-v"} \ 307 ${concatStringsSep " " cfg.directories}''; 308 in if (cfg.cachedir != null) then '' 309 mkdir -p ${cfg.cachedir} 310 chmod 0700 ${cfg.cachedir} 311 312 ( flock 9 313 if [ ! -e ${cfg.cachedir}/firstrun ]; then 314 ( flock 10 315 flock -u 9 316 tarsnap --configfile "/etc/tarsnap/${name}.conf" --fsck 317 flock 9 318 ) 10>${cfg.cachedir}/firstrun 319 fi 320 ) 9>${cfg.cachedir}/lockf 321 322 exec flock ${cfg.cachedir}/firstrun ${run} 323 '' else "exec ${run}"; 324 325 serviceConfig = { 326 Type = "oneshot"; 327 IOSchedulingClass = "idle"; 328 NoNewPrivileges = "true"; 329 CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ]; 330 PermissionsStartOnly = "true"; 331 }; 332 }) gcfg.archives; 333 334 # Note: the timer must be Persistent=true, so that systemd will start it even 335 # if e.g. your laptop was asleep while the latest interval occurred. 336 systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" 337 { timerConfig.OnCalendar = cfg.period; 338 timerConfig.Persistent = "true"; 339 wantedBy = [ "timers.target" ]; 340 }) gcfg.archives; 341 342 environment.etc = 343 mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf" 344 { text = configFile name cfg; 345 }) gcfg.archives; 346 347 environment.systemPackages = [ pkgs.tarsnap ]; 348 }; 349}