diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 81b197186c41..a89ce2c743d4 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -147,6 +147,9 @@ with lib; # parsoid (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] [ "services" "parsoid" "wikis" ]) + # tarsnap + (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives..cachedir") + # Options that are obsolete and have no replacement. (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") (mkRemovedOptionModule [ "programs" "bash" "enable" ] "") diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix index 24892a2a59a1..67112343c335 100644 --- a/nixos/modules/services/backup/tarsnap.nix +++ b/nixos/modules/services/backup/tarsnap.nix @@ -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 - tarsnap --fsck. - - Note that each individual archive (specified below) has its own cache - directory specified under cachedir; 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 null 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 "/root/tarsnap.key" 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 + tarsnap --fsck. + + Set to null 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 tarsnap@ service unit. You may use - systemctl start tarsnap@archive-name to + instanced tarsnap-archive-name service unit. You may use + systemctl start tarsnap-archive-name to manually trigger creation of archive-name 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 ]; };