10 gcfg = config.services.tarsnap;
11 opt = options.services.tarsnap;
13 configFile = name: cfg: ''
14 keyfile ${cfg.keyfile}
15 ${lib.optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"}
16 ${lib.optionalString cfg.nodump "nodump"}
17 ${lib.optionalString cfg.printStats "print-stats"}
18 ${lib.optionalString cfg.printStats "humanize-numbers"}
19 ${lib.optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes " + cfg.checkpointBytes)}
20 ${lib.optionalString cfg.aggressiveNetworking "aggressive-networking"}
21 ${lib.concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
22 ${lib.concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
23 ${lib.optionalString cfg.lowmem "lowmem"}
24 ${lib.optionalString cfg.verylowmem "verylowmem"}
25 ${lib.optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
26 ${lib.optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
27 ${lib.optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
32 (lib.mkRemovedOptionModule [
36 ] "Use services.tarsnap.archives.<name>.cachedir")
41 enable = lib.mkEnableOption "periodic tarsnap backups";
43 package = lib.mkPackageOption pkgs "tarsnap" { };
45 keyfile = lib.mkOption {
47 default = "/root/tarsnap.key";
49 The keyfile which associates this machine with your tarsnap
51 Create the keyfile with {command}`tarsnap-keygen`.
53 Note that each individual archive (specified below) may also have its
54 own individual keyfile specified. Tarsnap does not allow multiple
55 concurrent backups with the same cache directory and key (starting a
56 new backup will cause another one to fail). If you have multiple
57 archives specified, you should either spread out your backups to be
58 far apart, or specify a separate key for each archive. By default
59 every archive defaults to using
60 `"/root/tarsnap.key"`.
62 It's recommended for backups that you generate a key for every archive
63 using `tarsnap-keygen(1)`, and then generate a
64 write-only tarsnap key using `tarsnap-keymgmt(1)`,
65 and keep your master key(s) for a particular machine off-site.
67 The keyfile name should be given as a string and not a path, to
68 avoid the key being copied into the Nix store.
72 archives = lib.mkOption {
73 type = lib.types.attrsOf (
75 { config, options, ... }:
78 keyfile = lib.mkOption {
80 default = gcfg.keyfile;
81 defaultText = lib.literalExpression "config.${opt.keyfile}";
83 Set a specific keyfile for this archive. This defaults to
84 `"/root/tarsnap.key"` if left unspecified.
86 Use this option if you want to run multiple backups
87 concurrently - each archive must have a unique key. You can
88 generate a write-only key derived from your master key (which
89 is recommended) using `tarsnap-keymgmt(1)`.
91 Note: every archive must have an individual master key. You
92 must generate multiple keys with
93 `tarsnap-keygen(1)`, and then generate write
96 The keyfile name should be given as a string and not a path, to
97 avoid the key being copied into the Nix store.
101 cachedir = lib.mkOption {
102 type = lib.types.nullOr lib.types.path;
103 default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
104 defaultText = lib.literalExpression ''
105 "/var/cache/tarsnap/''${utils.escapeSystemdPath config.${options.keyfile}}"
108 The cache allows tarsnap to identify previously stored data
109 blocks, reducing archival time and bandwidth usage.
111 Should the cache become desynchronized or corrupted, tarsnap
112 will refuse to run until you manually rebuild the cache with
113 {command}`tarsnap --fsck`.
115 Set to `null` to disable caching.
119 nodump = lib.mkOption {
120 type = lib.types.bool;
123 Exclude files with the `nodump` flag.
127 printStats = lib.mkOption {
128 type = lib.types.bool;
131 Print global archive statistics upon completion.
132 The output is available via
133 {command}`systemctl status tarsnap-archive-name`.
137 checkpointBytes = lib.mkOption {
138 type = lib.types.nullOr lib.types.str;
141 Create a checkpoint every `checkpointBytes`
142 of uploaded data (optionally specified using an SI prefix).
144 1GB is the minimum value. A higher value is recommended,
145 as checkpointing is expensive.
147 Set to `null` to disable checkpointing.
151 period = lib.mkOption {
152 type = lib.types.str;
156 Create archive at this interval.
158 The format is described in
159 {manpage}`systemd.time(7)`.
163 aggressiveNetworking = lib.mkOption {
164 type = lib.types.bool;
167 Upload data over multiple TCP connections, potentially
168 increasing tarsnap's bandwidth utilisation at the cost
169 of slowing down all other network traffic. Not
170 recommended unless TCP congestion is the dominant
175 directories = lib.mkOption {
176 type = lib.types.listOf lib.types.path;
178 description = "List of filesystem paths to archive.";
181 excludes = lib.mkOption {
182 type = lib.types.listOf lib.types.str;
185 Exclude files and directories matching these patterns.
189 includes = lib.mkOption {
190 type = lib.types.listOf lib.types.str;
193 Include only files and directories matching these
194 patterns (the empty list includes everything).
196 Exclusions have precedence over inclusions.
200 lowmem = lib.mkOption {
201 type = lib.types.bool;
204 Reduce memory consumption by not caching small files.
205 Possibly beneficial if the average file size is smaller
206 than 1 MB and the number of files is lower than the
207 total amount of RAM in KB.
211 verylowmem = lib.mkOption {
212 type = lib.types.bool;
215 Reduce memory consumption by a factor of 2 beyond what
216 `lowmem` does, at the cost of significantly
217 slowing down the archiving process.
221 maxbw = lib.mkOption {
222 type = lib.types.nullOr lib.types.int;
225 Abort archival if upstream bandwidth usage in bytes
226 exceeds this threshold.
230 maxbwRateUp = lib.mkOption {
231 type = lib.types.nullOr lib.types.int;
233 example = lib.literalExpression "25 * 1000";
235 Upload bandwidth rate limit in bytes.
239 maxbwRateDown = lib.mkOption {
240 type = lib.types.nullOr lib.types.int;
242 example = lib.literalExpression "50 * 1000";
244 Download bandwidth rate limit in bytes.
248 verbose = lib.mkOption {
249 type = lib.types.bool;
252 Whether to produce verbose logging output.
255 explicitSymlinks = lib.mkOption {
256 type = lib.types.bool;
259 Whether to follow symlinks specified as archives.
262 followSymlinks = lib.mkOption {
263 type = lib.types.bool;
266 Whether to follow all symlinks in archive trees.
276 example = lib.literalExpression ''
279 { directories = [ "/home" "/root/ssl" ];
283 { directories = [ "/var/lib/minecraft" ];
290 Tarsnap archive configurations. Each attribute names an archive
291 to be created at a given time interval, according to the options
292 associated with it. When uploading to the tarsnap server,
293 archive names are suffixed by a 1 second resolution timestamp,
294 with the format `%Y%m%d%H%M%S`.
296 For each member of the set is created a timer which triggers the
297 instanced `tarsnap-archive-name` service unit. You may use
298 {command}`systemctl start tarsnap-archive-name` to
299 manually trigger creation of `archive-name` at
306 config = lib.mkIf gcfg.enable {
308 (lib.mapAttrsToList (name: cfg: {
309 assertion = cfg.directories != [ ];
310 message = "Must specify paths for tarsnap to back up";
312 ++ (lib.mapAttrsToList (name: cfg: {
313 assertion = !(cfg.lowmem && cfg.verylowmem);
314 message = "You cannot set both lowmem and verylowmem";
320 lib.nameValuePair "tarsnap-${name}" {
321 description = "Tarsnap archive '${name}'";
322 requires = [ "network-online.target" ];
323 after = [ "network-online.target" ];
331 # In order for the persistent tarsnap timer to work reliably, we have to
332 # make sure that the tarsnap server is reachable after systemd starts up
333 # the service - therefore we sleep in a loop until we can ping the
336 while ! ping -4 -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
341 tarsnap = ''${lib.getExe gcfg.package} --configfile "/etc/tarsnap/${name}.conf"'';
343 ${tarsnap} -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \
344 ${lib.optionalString cfg.verbose "-v"} \
345 ${lib.optionalString cfg.explicitSymlinks "-H"} \
346 ${lib.optionalString cfg.followSymlinks "-L"} \
347 ${lib.concatStringsSep " " cfg.directories}'';
348 cachedir = lib.escapeShellArg cfg.cachedir;
350 if (cfg.cachedir != null) then
353 chmod 0700 ${cachedir}
356 if [ ! -e ${cachedir}/firstrun ]; then
361 ) 10>${cachedir}/firstrun
363 ) 9>${cachedir}/lockf
365 exec flock ${cachedir}/firstrun ${run}
372 IOSchedulingClass = "idle";
373 NoNewPrivileges = "true";
374 CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
375 PermissionsStartOnly = "true";
383 lib.nameValuePair "tarsnap-restore-${name}" {
384 description = "Tarsnap restore '${name}'";
385 requires = [ "network-online.target" ];
395 tarsnap = ''${lib.getExe gcfg.package} --configfile "/etc/tarsnap/${name}.conf"'';
396 lastArchive = "$(${tarsnap} --list-archives | sort | tail -1)";
397 run = ''${tarsnap} -x -f "${lastArchive}" ${lib.optionalString cfg.verbose "-v"}'';
398 cachedir = lib.escapeShellArg cfg.cachedir;
401 if (cfg.cachedir != null) then
404 chmod 0700 ${cachedir}
407 if [ ! -e ${cachedir}/firstrun ]; then
412 ) 10>${cachedir}/firstrun
414 ) 9>${cachedir}/lockf
416 exec flock ${cachedir}/firstrun ${run}
423 IOSchedulingClass = "idle";
424 NoNewPrivileges = "true";
425 CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
426 PermissionsStartOnly = "true";
431 # Note: the timer must be Persistent=true, so that systemd will start it even
432 # if e.g. your laptop was asleep while the latest interval occurred.
433 systemd.timers = lib.mapAttrs' (
435 lib.nameValuePair "tarsnap-${name}" {
436 timerConfig.OnCalendar = cfg.period;
437 timerConfig.Persistent = "true";
438 wantedBy = [ "timers.target" ];
442 environment.etc = lib.mapAttrs' (
444 lib.nameValuePair "tarsnap/${name}.conf" {
445 text = configFile name cfg;
449 environment.systemPackages = [ gcfg.package ];