Merge pull request #19089 from abbradar/tarsnap

tarsnap service: fix multiple simultaneous archives with a single key
This commit is contained in:
Domen Kožar 2016-11-20 17:52:42 +01:00 committed by GitHub
commit 9c5a9f32c6
2 changed files with 79 additions and 72 deletions

View file

@ -147,6 +147,9 @@ with lib;
# parsoid
(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.
(mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
(mkRemovedOptionModule [ "programs" "bash" "enable" ] "")

View file

@ -1,25 +1,25 @@
{ config, lib, pkgs, ... }:
{ config, lib, pkgs, utils, ... }:
with lib;
let
cfg = config.services.tarsnap;
gcfg = config.services.tarsnap;
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.printStats "print-stats"}
${optionalString cfg.printStats "humanize-numbers"}
${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)}
${optionalString cfg.aggressiveNetworking "aggressive-networking"}
${concatStringsSep "\n" (map (v: "exclude "+v) cfg.excludes)}
${concatStringsSep "\n" (map (v: "include "+v) cfg.includes)}
${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
${optionalString cfg.lowmem "lowmem"}
${optionalString cfg.verylowmem "verylowmem"}
${optionalString (cfg.maxbw != null) ("maxbw "+toString cfg.maxbw)}
${optionalString (cfg.maxbwRateUp != null) ("maxbw-rate-up "+toString cfg.maxbwRateUp)}
${optionalString (cfg.maxbwRateDown != null) ("maxbw-rate-down "+toString cfg.maxbwRateDown)}
${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
'';
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 {
type = types.attrsOf (types.submodule (
type = types.attrsOf (types.submodule ({ config, ... }:
{
options = {
keyfile = mkOption {
type = types.str;
default = config.services.tarsnap.keyfile;
default = gcfg.keyfile;
description = ''
Set a specific keyfile for this archive. This defaults to
<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 {
type = types.bool;
default = true;
@ -249,7 +243,7 @@ in
};
gamedata =
{ directories = [ "/var/lib/minecraft "];
{ directories = [ "/var/lib/minecraft" ];
period = "*:30";
};
}
@ -262,8 +256,8 @@ in
archive names are suffixed by a 1 second resolution timestamp.
For each member of the set is created a timer which triggers the
instanced <literal>tarsnap@</literal> service unit. You may use
<command>systemctl start tarsnap@archive-name</command> to
instanced <literal>tarsnap-archive-name</literal> service unit. You may use
<command>systemctl start tarsnap-archive-name</command> to
manually trigger creation of <literal>archive-name</literal> at
any time.
'';
@ -271,63 +265,73 @@ in
};
};
config = mkIf cfg.enable {
config = mkIf gcfg.enable {
assertions =
(mapAttrsToList (name: cfg:
{ assertion = cfg.directories != [];
message = "Must specify paths for tarsnap to back up";
}) cfg.archives) ++
}) gcfg.archives) ++
(mapAttrsToList (name: cfg:
{ assertion = !(cfg.lowmem && cfg.verylowmem);
message = "You cannot set both lowmem and verylowmem";
}) cfg.archives);
}) gcfg.archives);
systemd.services."tarsnap@" = {
description = "Tarsnap archive '%i'";
requires = [ "network-online.target" ];
after = [ "network-online.target" ];
systemd.services =
mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" {
description = "Tarsnap archive '${name}'";
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
# 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
# endpoint.
preStart = "while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done";
scriptArgs = "%i";
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
'';
# 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
# the service - therefore we sleep in a loop until we can ping the
# endpoint.
preStart = ''
while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
'';
serviceConfig = {
IOSchedulingClass = "idle";
NoNewPrivileges = "true";
CapabilityBoundingSet = "CAP_DAC_READ_SEARCH";
PermissionsStartOnly = "true";
};
};
script =
let run = ''tarsnap --configfile "/etc/tarsnap/${name}.conf" -c -f "${name}-$(date +"%Y%m%d%H%M%S")" ${concatStringsSep " " cfg.directories}'';
in if (cfg.cachedir != null) then ''
mkdir -p ${cfg.cachedir}
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
# 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.Persistent = "true";
wantedBy = [ "timers.target" ];
}) cfg.archives;
}) gcfg.archives;
environment.etc =
(mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
{ text = configFile name cfg;
}) cfg.archives) //
(mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.dirs"
{ text = concatStringsSep " " cfg.directories;
}) cfg.archives);
}) gcfg.archives;
environment.systemPackages = [ pkgs.tarsnap ];
};