tarsnap service: fix multiple simultaneous archives with a single key

This commit is contained in:
Nikolay Amiantov 2016-09-30 02:27:34 +03:00
parent 6bb292d42b
commit 15567e6d8e
2 changed files with 79 additions and 72 deletions

View File

@ -147,6 +147,9 @@ with lib;
# parsoid # parsoid
(mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] [ "services" "parsoid" "wikis" ]) (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] [ "services" "parsoid" "wikis" ])
# tarsnap
(mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir")
# Options that are obsolete and have no replacement. # Options that are obsolete and have no replacement.
(mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
(mkRemovedOptionModule [ "programs" "bash" "enable" ] "") (mkRemovedOptionModule [ "programs" "bash" "enable" ] "")

View File

@ -1,25 +1,25 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, utils, ... }:
with lib; with lib;
let let
cfg = config.services.tarsnap; gcfg = config.services.tarsnap;
configFile = name: cfg: '' configFile = name: cfg: ''
cachedir ${config.services.tarsnap.cachedir}/${name} keyfile ${cfg.keyfile}
keyfile ${cfg.keyfile} ${optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"}
${optionalString cfg.nodump "nodump"} ${optionalString cfg.nodump "nodump"}
${optionalString cfg.printStats "print-stats"} ${optionalString cfg.printStats "print-stats"}
${optionalString cfg.printStats "humanize-numbers"} ${optionalString cfg.printStats "humanize-numbers"}
${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)} ${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)}
${optionalString cfg.aggressiveNetworking "aggressive-networking"} ${optionalString cfg.aggressiveNetworking "aggressive-networking"}
${concatStringsSep "\n" (map (v: "exclude "+v) cfg.excludes)} ${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
${concatStringsSep "\n" (map (v: "include "+v) cfg.includes)} ${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
${optionalString cfg.lowmem "lowmem"} ${optionalString cfg.lowmem "lowmem"}
${optionalString cfg.verylowmem "verylowmem"} ${optionalString cfg.verylowmem "verylowmem"}
${optionalString (cfg.maxbw != null) ("maxbw "+toString cfg.maxbw)} ${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
${optionalString (cfg.maxbwRateUp != null) ("maxbw-rate-up "+toString cfg.maxbwRateUp)} ${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
${optionalString (cfg.maxbwRateDown != null) ("maxbw-rate-down "+toString cfg.maxbwRateDown)} ${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
''; '';
in in
{ {
@ -60,34 +60,13 @@ in
''; '';
}; };
cachedir = mkOption {
type = types.nullOr types.path;
default = "/var/cache/tarsnap";
description = ''
The cache allows tarsnap to identify previously stored data
blocks, reducing archival time and bandwidth usage.
Should the cache become desynchronized or corrupted, tarsnap
will refuse to run until you manually rebuild the cache with
<command>tarsnap --fsck</command>.
Note that each individual archive (specified below) has its own cache
directory specified under <literal>cachedir</literal>; this is because
tarsnap locks the cache during backups, meaning multiple services
archives cannot be backed up concurrently or overlap with a shared
cache.
Set to <literal>null</literal> to disable caching.
'';
};
archives = mkOption { archives = mkOption {
type = types.attrsOf (types.submodule ( type = types.attrsOf (types.submodule ({ config, ... }:
{ {
options = { options = {
keyfile = mkOption { keyfile = mkOption {
type = types.str; type = types.str;
default = config.services.tarsnap.keyfile; default = gcfg.keyfile;
description = '' description = ''
Set a specific keyfile for this archive. This defaults to Set a specific keyfile for this archive. This defaults to
<literal>"/root/tarsnap.key"</literal> if left unspecified. <literal>"/root/tarsnap.key"</literal> if left unspecified.
@ -107,6 +86,21 @@ in
''; '';
}; };
cachedir = mkOption {
type = types.nullOr types.path;
default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
description = ''
The cache allows tarsnap to identify previously stored data
blocks, reducing archival time and bandwidth usage.
Should the cache become desynchronized or corrupted, tarsnap
will refuse to run until you manually rebuild the cache with
<command>tarsnap --fsck</command>.
Set to <literal>null</literal> to disable caching.
'';
};
nodump = mkOption { nodump = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -249,7 +243,7 @@ in
}; };
gamedata = gamedata =
{ directories = [ "/var/lib/minecraft "]; { directories = [ "/var/lib/minecraft" ];
period = "*:30"; period = "*:30";
}; };
} }
@ -262,8 +256,8 @@ in
archive names are suffixed by a 1 second resolution timestamp. archive names are suffixed by a 1 second resolution timestamp.
For each member of the set is created a timer which triggers the For each member of the set is created a timer which triggers the
instanced <literal>tarsnap@</literal> service unit. You may use instanced <literal>tarsnap-archive-name</literal> service unit. You may use
<command>systemctl start tarsnap@archive-name</command> to <command>systemctl start tarsnap-archive-name</command> to
manually trigger creation of <literal>archive-name</literal> at manually trigger creation of <literal>archive-name</literal> at
any time. any time.
''; '';
@ -271,63 +265,73 @@ in
}; };
}; };
config = mkIf cfg.enable { config = mkIf gcfg.enable {
assertions = assertions =
(mapAttrsToList (name: cfg: (mapAttrsToList (name: cfg:
{ assertion = cfg.directories != []; { assertion = cfg.directories != [];
message = "Must specify paths for tarsnap to back up"; message = "Must specify paths for tarsnap to back up";
}) cfg.archives) ++ }) gcfg.archives) ++
(mapAttrsToList (name: cfg: (mapAttrsToList (name: cfg:
{ assertion = !(cfg.lowmem && cfg.verylowmem); { assertion = !(cfg.lowmem && cfg.verylowmem);
message = "You cannot set both lowmem and verylowmem"; message = "You cannot set both lowmem and verylowmem";
}) cfg.archives); }) gcfg.archives);
systemd.services."tarsnap@" = { systemd.services =
description = "Tarsnap archive '%i'"; mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" {
requires = [ "network-online.target" ]; description = "Tarsnap archive '${name}'";
after = [ "network-online.target" ]; requires = [ "network-online.target" ];
after = [ "network-online.target" ];
path = [ pkgs.iputils pkgs.tarsnap pkgs.coreutils ]; path = [ pkgs.iputils pkgs.tarsnap pkgs.utillinux ];
# In order for the persistent tarsnap timer to work reliably, we have to # In order for the persistent tarsnap timer to work reliably, we have to
# make sure that the tarsnap server is reachable after systemd starts up # make sure that the tarsnap server is reachable after systemd starts up
# the service - therefore we sleep in a loop until we can ping the # the service - therefore we sleep in a loop until we can ping the
# endpoint. # endpoint.
preStart = "while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done"; preStart = ''
scriptArgs = "%i"; while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
script = '' '';
mkdir -p -m 0755 ${dirOf cfg.cachedir}
mkdir -p -m 0700 ${cfg.cachedir}
chown root:root ${cfg.cachedir}
chmod 0700 ${cfg.cachedir}
mkdir -p -m 0700 ${cfg.cachedir}/$1
DIRS=`cat /etc/tarsnap/$1.dirs`
exec tarsnap --configfile /etc/tarsnap/$1.conf -c -f $1-$(date +"%Y%m%d%H%M%S") $DIRS
'';
serviceConfig = { script =
IOSchedulingClass = "idle"; let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" -c -f "${name}-$(date +"%Y%m%d%H%M%S")" ${concatStringsSep " " cfg.directories}'';
NoNewPrivileges = "true"; in if (cfg.cachedir != null) then ''
CapabilityBoundingSet = "CAP_DAC_READ_SEARCH"; mkdir -p ${cfg.cachedir}
PermissionsStartOnly = "true"; chmod 0700 ${cfg.cachedir}
};
}; ( flock 9
if [ ! -e ${cfg.cachedir}/firstrun ]; then
( flock 10
flock -u 9
tarsnap --configfile "/etc/tarsnap/${name}.conf" --fsck
flock 9
) 10>${cfg.cachedir}/firstrun
fi
) 9>${cfg.cachedir}/lockf
exec flock ${cfg.cachedir}/firstrun ${run}
'' else "exec ${run}";
serviceConfig = {
Type = "oneshot";
IOSchedulingClass = "idle";
NoNewPrivileges = "true";
CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
PermissionsStartOnly = "true";
};
}) gcfg.archives;
# Note: the timer must be Persistent=true, so that systemd will start it even # Note: the timer must be Persistent=true, so that systemd will start it even
# if e.g. your laptop was asleep while the latest interval occurred. # if e.g. your laptop was asleep while the latest interval occurred.
systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap@${name}" systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}"
{ timerConfig.OnCalendar = cfg.period; { timerConfig.OnCalendar = cfg.period;
timerConfig.Persistent = "true"; timerConfig.Persistent = "true";
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
}) cfg.archives; }) gcfg.archives;
environment.etc = environment.etc =
(mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf" mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
{ text = configFile name cfg; { text = configFile name cfg;
}) cfg.archives) // }) gcfg.archives;
(mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.dirs"
{ text = concatStringsSep " " cfg.directories;
}) cfg.archives);
environment.systemPackages = [ pkgs.tarsnap ]; environment.systemPackages = [ pkgs.tarsnap ];
}; };