tarsnap service: fix multiple simultaneous archives with a single key
This commit is contained in:
parent
6bb292d42b
commit
15567e6d8e
@ -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" ] "")
|
||||||
|
@ -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 ];
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user