nixos/services.borgbackup: remove with lib;

This commit is contained in:
Felix Buehler 2024-08-30 00:46:39 +02:00
parent 1f34eeb672
commit f51cd10816

View File

@ -1,7 +1,4 @@
{ config, lib, pkgs, ... }:
with lib;
let
isLocalPath = x:
@ -11,17 +8,17 @@ let
mkExcludeFile = cfg:
# Write each exclude pattern to a new line
pkgs.writeText "excludefile" (concatMapStrings (s: s + "\n") cfg.exclude);
pkgs.writeText "excludefile" (lib.concatMapStrings (s: s + "\n") cfg.exclude);
mkPatternsFile = cfg:
# Write each pattern to a new line
pkgs.writeText "patternsfile" (concatMapStrings (s: s + "\n") cfg.patterns);
pkgs.writeText "patternsfile" (lib.concatMapStrings (s: s + "\n") cfg.patterns);
mkKeepArgs = cfg:
# If cfg.prune.keep e.g. has a yearly attribute,
# its content is passed on as --keep-yearly
concatStringsSep " "
(mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
lib.concatStringsSep " "
(lib.mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
mkBackupScript = name: cfg: pkgs.writeShellScript "${name}-script" (''
set -e
@ -44,10 +41,10 @@ let
fi
}
archiveName="${optionalString (cfg.archiveBaseName != null) (cfg.archiveBaseName + "-")}$(date ${cfg.dateFormat})"
archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
archiveName="${lib.optionalString (cfg.archiveBaseName != null) (cfg.archiveBaseName + "-")}$(date ${cfg.dateFormat})"
archiveSuffix="${lib.optionalString cfg.appendFailedSuffix ".failed"}"
${cfg.preHook}
'' + optionalString cfg.doInit ''
'' + lib.optionalString cfg.doInit ''
# Run borg init if the repo doesn't exist yet
if ! borgWrapper list $extraArgs > /dev/null; then
borgWrapper init $extraArgs \
@ -58,24 +55,24 @@ let
'' + ''
(
set -o pipefail
${optionalString (cfg.dumpCommand != null) ''${escapeShellArg cfg.dumpCommand} | \''}
${lib.optionalString (cfg.dumpCommand != null) ''${lib.escapeShellArg cfg.dumpCommand} | \''}
borgWrapper create $extraArgs \
--compression ${cfg.compression} \
--exclude-from ${mkExcludeFile cfg} \
--patterns-from ${mkPatternsFile cfg} \
$extraCreateArgs \
"::$archiveName$archiveSuffix" \
${if cfg.paths == null then "-" else escapeShellArgs cfg.paths}
${if cfg.paths == null then "-" else lib.escapeShellArgs cfg.paths}
)
'' + optionalString cfg.appendFailedSuffix ''
'' + lib.optionalString cfg.appendFailedSuffix ''
borgWrapper rename $extraArgs \
"::$archiveName$archiveSuffix" "$archiveName"
'' + ''
${cfg.postCreate}
'' + optionalString (cfg.prune.keep != { }) ''
'' + lib.optionalString (cfg.prune.keep != { }) ''
borgWrapper prune $extraArgs \
${mkKeepArgs cfg} \
${optionalString (cfg.prune.prefix != null) "--glob-archives ${escapeShellArg "${cfg.prune.prefix}*"}"} \
${lib.optionalString (cfg.prune.prefix != null) "--glob-archives ${lib.escapeShellArg "${cfg.prune.prefix}*"}"} \
$extraPruneArgs
borgWrapper compact $extraArgs $extraCompactArgs
${cfg.postPrune}
@ -93,18 +90,18 @@ let
userHome = config.users.users.${cfg.user}.home;
backupJobName = "borgbackup-job-${name}";
backupScript = mkBackupScript backupJobName cfg;
in nameValuePair backupJobName {
in lib.nameValuePair backupJobName {
description = "BorgBackup job ${name}";
path = [
config.services.borgbackup.package pkgs.openssh
];
script = "exec " + optionalString cfg.inhibitsSleep ''\
script = "exec " + lib.optionalString cfg.inhibitsSleep ''\
${pkgs.systemd}/bin/systemd-inhibit \
--who="borgbackup" \
--what="sleep" \
--why="Scheduled backup" \
'' + backupScript;
unitConfig = optionalAttrs (isLocalPath cfg.repo) {
unitConfig = lib.optionalAttrs (isLocalPath cfg.repo) {
RequiresMountsFor = [ cfg.repo ];
};
serviceConfig = {
@ -118,7 +115,7 @@ let
[ "${userHome}/.config/borg" "${userHome}/.cache/borg" ]
++ cfg.readWritePaths
# Borg needs write access to repo if it is not remote
++ optional (isLocalPath cfg.repo) cfg.repo;
++ lib.optional (isLocalPath cfg.repo) cfg.repo;
PrivateTmp = cfg.privateTmp;
};
environment = {
@ -128,7 +125,7 @@ let
};
mkBackupTimers = name: cfg:
nameValuePair "borgbackup-job-${name}" {
lib.nameValuePair "borgbackup-job-${name}" {
description = "BorgBackup job ${name} timer";
wantedBy = [ "timers.target" ];
timerConfig = {
@ -136,8 +133,8 @@ let
OnCalendar = cfg.startAt;
};
# if remote-backup wait for network
after = optional (cfg.persistentTimer && !isLocalPath cfg.repo) "network-online.target";
wants = optional (cfg.persistentTimer && !isLocalPath cfg.repo) "network-online.target";
after = lib.optional (cfg.persistentTimer && !isLocalPath cfg.repo) "network-online.target";
wants = lib.optional (cfg.persistentTimer && !isLocalPath cfg.repo) "network-online.target";
};
# utility function around makeWrapper
@ -148,11 +145,11 @@ let
nativeBuildInputs = [ pkgs.makeWrapper ];
} (with lib; ''
makeWrapper "${original}" "$out/bin/${name}" \
${concatStringsSep " \\\n " (mapAttrsToList (name: value: ''--set ${name} "${value}"'') set)}
${lib.concatStringsSep " \\\n " (lib.mapAttrsToList (name: value: ''--set ${name} "${value}"'') set)}
'');
mkBorgWrapper = name: cfg: mkWrapperDrv {
original = getExe config.services.borgbackup.package;
original = lib.getExe config.services.borgbackup.package;
name = "borg-job-${name}";
set = { BORG_REPO = cfg.repo; } // (mkPassEnv cfg) // cfg.environment;
};
@ -167,7 +164,7 @@ let
"${config.users.users."${cfg.user}".home}/.cache".d = settings;
"${config.users.users."${cfg.user}".home}/.config/borg".d = settings;
"${config.users.users."${cfg.user}".home}/.cache/borg".d = settings;
} // optionalAttrs (isLocalPath cfg.repo && !cfg.removableDevice) {
} // lib.optionalAttrs (isLocalPath cfg.repo && !cfg.removableDevice) {
"${cfg.repo}".d = settings;
});
@ -180,11 +177,11 @@ let
};
mkRepoService = name: cfg:
nameValuePair "borgbackup-repo-${name}" {
lib.nameValuePair "borgbackup-repo-${name}" {
description = "Create BorgBackup repository ${name} directory";
script = ''
mkdir -p ${escapeShellArg cfg.path}
chown ${cfg.user}:${cfg.group} ${escapeShellArg cfg.path}
mkdir -p ${lib.escapeShellArg cfg.path}
chown ${cfg.user}:${cfg.group} ${lib.escapeShellArg cfg.path}
'';
serviceConfig = {
# The service's only task is to ensure that the specified path exists
@ -196,10 +193,10 @@ let
mkAuthorizedKey = cfg: appendOnly: key:
let
# Because of the following line, clients do not need to specify an absolute repo path
cdCommand = "cd ${escapeShellArg cfg.path}";
cdCommand = "cd ${lib.escapeShellArg cfg.path}";
restrictedArg = "--restrict-to-${if cfg.allowSubRepos then "path" else "repository"} .";
appendOnlyArg = optionalString appendOnly "--append-only";
quotaArg = optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
appendOnlyArg = lib.optionalString appendOnly "--append-only";
quotaArg = lib.optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
serveCommand = "borg serve ${restrictedArg} ${appendOnlyArg} ${quotaArg}";
in
''command="${cdCommand} && ${serveCommand}",restrict ${key}'';
@ -224,7 +221,7 @@ let
};
mkSourceAssertions = name: cfg: {
assertion = count isNull [ cfg.dumpCommand cfg.paths ] == 1;
assertion = lib.count isNull [ cfg.dumpCommand cfg.paths ] == 1;
message = ''
Exactly one of borgbackup.jobs.${name}.paths or borgbackup.jobs.${name}.dumpCommand
must be set.
@ -239,14 +236,14 @@ let
};
in {
meta.maintainers = with maintainers; [ dotlambda ];
meta.maintainers = with lib.maintainers; [ dotlambda ];
meta.doc = ./borgbackup.md;
###### interface
options.services.borgbackup.package = mkPackageOption pkgs "borgbackup" { };
options.services.borgbackup.package = lib.mkPackageOption pkgs "borgbackup" { };
options.services.borgbackup.jobs = mkOption {
options.services.borgbackup.jobs = lib.mkOption {
description = ''
Deduplicating backups using BorgBackup.
Adding a job will cause a borg-job-NAME wrapper to be added
@ -254,7 +251,7 @@ in {
See also the chapter about BorgBackup in the NixOS manual.
'';
default = { };
example = literalExpression ''
example = lib.literalExpression ''
{ # for a local backup
rootBackup = {
paths = "/";
@ -286,12 +283,12 @@ in {
startAt = "daily";
};
'';
type = types.attrsOf (types.submodule (let globalConfig = config; in
type = lib.types.attrsOf (lib.types.submodule (let globalConfig = config; in
{ name, config, ... }: {
options = {
paths = mkOption {
type = with types; nullOr (coercedTo str lib.singleton (listOf str));
paths = lib.mkOption {
type = with lib.types; nullOr (coercedTo str lib.singleton (listOf str));
default = null;
description = ''
Path(s) to back up.
@ -300,8 +297,8 @@ in {
example = "/home/user";
};
dumpCommand = mkOption {
type = with types; nullOr path;
dumpCommand = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Backup the stdout of this program instead of filesystem paths.
@ -310,22 +307,22 @@ in {
example = "/path/to/createZFSsend.sh";
};
repo = mkOption {
type = types.str;
repo = lib.mkOption {
type = lib.types.str;
description = "Remote or local repository to back up to.";
example = "user@machine:/path/to/repo";
};
removableDevice = mkOption {
type = types.bool;
removableDevice = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the repo (which must be local) is a removable device.";
};
archiveBaseName = mkOption {
type = types.nullOr (types.strMatching "[^/{}]+");
archiveBaseName = lib.mkOption {
type = lib.types.nullOr (lib.types.strMatching "[^/{}]+");
default = "${globalConfig.networking.hostName}-${name}";
defaultText = literalExpression ''"''${config.networking.hostName}-<name>"'';
defaultText = lib.literalExpression ''"''${config.networking.hostName}-<name>"'';
description = ''
How to name the created archives. A timestamp, whose format is
determined by {option}`dateFormat`, will be appended. The full
@ -335,8 +332,8 @@ in {
'';
};
dateFormat = mkOption {
type = types.str;
dateFormat = lib.mkOption {
type = lib.types.str;
description = ''
Arguments passed to {command}`date`
to create a timestamp suffix for the archive name.
@ -345,8 +342,8 @@ in {
example = "-u +%s";
};
startAt = mkOption {
type = with types; either str (listOf str);
startAt = lib.mkOption {
type = with lib.types; either str (listOf str);
default = "daily";
description = ''
When or how often the backup should run.
@ -359,9 +356,9 @@ in {
'';
};
persistentTimer = mkOption {
persistentTimer = lib.mkOption {
default = false;
type = types.bool;
type = lib.types.bool;
example = true;
description = ''
Set the `Persistent` option for the
@ -371,17 +368,17 @@ in {
'';
};
inhibitsSleep = mkOption {
inhibitsSleep = lib.mkOption {
default = false;
type = types.bool;
type = lib.types.bool;
example = true;
description = ''
Prevents the system from sleeping while backing up.
'';
};
user = mkOption {
type = types.str;
user = lib.mkOption {
type = lib.types.str;
description = ''
The user {command}`borg` is run as.
User or group need read permission
@ -390,8 +387,8 @@ in {
default = "root";
};
group = mkOption {
type = types.str;
group = lib.mkOption {
type = lib.types.str;
description = ''
The group borg is run as. User or group needs read permission
for the specified {option}`paths`.
@ -399,8 +396,8 @@ in {
default = "root";
};
encryption.mode = mkOption {
type = types.enum [
encryption.mode = lib.mkOption {
type = lib.types.enum [
"repokey" "keyfile"
"repokey-blake2" "keyfile-blake2"
"authenticated" "authenticated-blake2"
@ -415,8 +412,8 @@ in {
example = "repokey-blake2";
};
encryption.passCommand = mkOption {
type = with types; nullOr str;
encryption.passCommand = lib.mkOption {
type = with lib.types; nullOr str;
description = ''
A command which prints the passphrase to stdout.
Mutually exclusive with {option}`passphrase`.
@ -425,8 +422,8 @@ in {
example = "cat /path/to/passphrase_file";
};
encryption.passphrase = mkOption {
type = with types; nullOr str;
encryption.passphrase = lib.mkOption {
type = with lib.types; nullOr str;
description = ''
The passphrase the backups are encrypted with.
Mutually exclusive with {option}`passCommand`.
@ -436,11 +433,11 @@ in {
default = null;
};
compression = mkOption {
compression = lib.mkOption {
# "auto" is optional,
# compression mode must be given,
# compression level is optional
type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
type = lib.types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
description = ''
Compression method to use. Refer to
{command}`borg help compression`
@ -450,8 +447,8 @@ in {
example = "auto,lzma";
};
exclude = mkOption {
type = with types; listOf str;
exclude = lib.mkOption {
type = with lib.types; listOf str;
description = ''
Exclude paths matching any of the given patterns. See
{command}`borg help patterns` for pattern syntax.
@ -463,8 +460,8 @@ in {
];
};
patterns = mkOption {
type = with types; listOf str;
patterns = lib.mkOption {
type = with lib.types; listOf str;
description = ''
Include/exclude paths matching the given patterns. The first
matching patterns is used, so if an include pattern (prefix `+`)
@ -478,8 +475,8 @@ in {
];
};
readWritePaths = mkOption {
type = with types; listOf path;
readWritePaths = lib.mkOption {
type = with lib.types; listOf path;
description = ''
By default, borg cannot write anywhere on the system but
`$HOME/.config/borg` and `$HOME/.cache/borg`.
@ -492,8 +489,8 @@ in {
];
};
privateTmp = mkOption {
type = types.bool;
privateTmp = lib.mkOption {
type = lib.types.bool;
description = ''
Set the `PrivateTmp` option for
the systemd-service. Set to false if you need sockets
@ -502,8 +499,8 @@ in {
default = true;
};
failOnWarnings = mkOption {
type = types.bool;
failOnWarnings = lib.mkOption {
type = lib.types.bool;
description = ''
Fail the whole backup job if any borg command returns a warning
(exit code 1), for example because a file changed during backup.
@ -511,8 +508,8 @@ in {
default = true;
};
doInit = mkOption {
type = types.bool;
doInit = lib.mkOption {
type = lib.types.bool;
description = ''
Run {command}`borg init` if the
specified {option}`repo` does not exist.
@ -523,8 +520,8 @@ in {
default = true;
};
appendFailedSuffix = mkOption {
type = types.bool;
appendFailedSuffix = lib.mkOption {
type = lib.types.bool;
description = ''
Append a `.failed` suffix
to the archive name, which is only removed if
@ -533,18 +530,18 @@ in {
default = true;
};
prune.keep = mkOption {
prune.keep = lib.mkOption {
# Specifying e.g. `prune.keep.yearly = -1`
# means there is no limit of yearly archives to keep
# The regex is for use with e.g. --keep-within 1y
type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
type = with lib.types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
description = ''
Prune a repository by deleting all archives not matching any of the
specified retention options. See {command}`borg help prune`
for the available options.
'';
default = { };
example = literalExpression ''
example = lib.literalExpression ''
{
within = "1d"; # Keep all archives from the last day
daily = 7;
@ -554,19 +551,19 @@ in {
'';
};
prune.prefix = mkOption {
type = types.nullOr (types.str);
prune.prefix = lib.mkOption {
type = lib.types.nullOr (lib.types.str);
description = ''
Only consider archive names starting with this prefix for pruning.
By default, only archives created by this job are considered.
Use `""` or `null` to consider all archives.
'';
default = config.archiveBaseName;
defaultText = literalExpression "archiveBaseName";
defaultText = lib.literalExpression "archiveBaseName";
};
environment = mkOption {
type = with types; attrsOf str;
environment = lib.mkOption {
type = with lib.types; attrsOf str;
description = ''
Environment variables passed to the backup script.
You can for example specify which SSH key to use.
@ -575,8 +572,8 @@ in {
example = { BORG_RSH = "ssh -i /path/to/key"; };
};
preHook = mkOption {
type = types.lines;
preHook = lib.mkOption {
type = lib.types.lines;
description = ''
Shell commands to run before the backup.
This can for example be used to mount file systems.
@ -588,16 +585,16 @@ in {
'';
};
postInit = mkOption {
type = types.lines;
postInit = lib.mkOption {
type = lib.types.lines;
description = ''
Shell commands to run after {command}`borg init`.
'';
default = "";
};
postCreate = mkOption {
type = types.lines;
postCreate = lib.mkOption {
type = lib.types.lines;
description = ''
Shell commands to run after {command}`borg create`. The name
of the created archive is stored in `$archiveName`.
@ -605,16 +602,16 @@ in {
default = "";
};
postPrune = mkOption {
type = types.lines;
postPrune = lib.mkOption {
type = lib.types.lines;
description = ''
Shell commands to run after {command}`borg prune`.
'';
default = "";
};
postHook = mkOption {
type = types.lines;
postHook = lib.mkOption {
type = lib.types.lines;
description = ''
Shell commands to run just before exit. They are executed
even if a previous command exits with a non-zero exit code.
@ -623,8 +620,8 @@ in {
default = "";
};
extraArgs = mkOption {
type = with types; coercedTo (listOf str) escapeShellArgs str;
extraArgs = lib.mkOption {
type = with lib.types; coercedTo (listOf str) lib.escapeShellArgs str;
description = ''
Additional arguments for all {command}`borg` calls the
service has. Handle with care.
@ -633,8 +630,8 @@ in {
example = [ "--remote-path=/path/to/borg" ];
};
extraInitArgs = mkOption {
type = with types; coercedTo (listOf str) escapeShellArgs str;
extraInitArgs = lib.mkOption {
type = with lib.types; coercedTo (listOf str) lib.escapeShellArgs str;
description = ''
Additional arguments for {command}`borg init`.
Can also be set at runtime using `$extraInitArgs`.
@ -643,8 +640,8 @@ in {
example = [ "--append-only" ];
};
extraCreateArgs = mkOption {
type = with types; coercedTo (listOf str) escapeShellArgs str;
extraCreateArgs = lib.mkOption {
type = with lib.types; coercedTo (listOf str) lib.escapeShellArgs str;
description = ''
Additional arguments for {command}`borg create`.
Can also be set at runtime using `$extraCreateArgs`.
@ -656,8 +653,8 @@ in {
];
};
extraPruneArgs = mkOption {
type = with types; coercedTo (listOf str) escapeShellArgs str;
extraPruneArgs = lib.mkOption {
type = with lib.types; coercedTo (listOf str) lib.escapeShellArgs str;
description = ''
Additional arguments for {command}`borg prune`.
Can also be set at runtime using `$extraPruneArgs`.
@ -666,8 +663,8 @@ in {
example = [ "--save-space" ];
};
extraCompactArgs = mkOption {
type = with types; coercedTo (listOf str) escapeShellArgs str;
extraCompactArgs = lib.mkOption {
type = with lib.types; coercedTo (listOf str) lib.escapeShellArgs str;
description = ''
Additional arguments for {command}`borg compact`.
Can also be set at runtime using `$extraCompactArgs`.
@ -680,7 +677,7 @@ in {
));
};
options.services.borgbackup.repos = mkOption {
options.services.borgbackup.repos = lib.mkOption {
description = ''
Serve BorgBackup repositories to given public SSH keys,
restricting their access to the repository only.
@ -689,11 +686,11 @@ in {
i.e. `user@machine:.` is enough. (Note colon and dot.)
'';
default = { };
type = types.attrsOf (types.submodule (
type = lib.types.attrsOf (lib.types.submodule (
{ ... }: {
options = {
path = mkOption {
type = types.path;
path = lib.mkOption {
type = lib.types.path;
description = ''
Where to store the backups. Note that the directory
is created automatically, with correct permissions.
@ -701,8 +698,8 @@ in {
default = "/var/lib/borgbackup";
};
user = mkOption {
type = types.str;
user = lib.mkOption {
type = lib.types.str;
description = ''
The user {command}`borg serve` is run as.
User or group needs write permission
@ -711,8 +708,8 @@ in {
default = "borg";
};
group = mkOption {
type = types.str;
group = lib.mkOption {
type = lib.types.str;
description = ''
The group {command}`borg serve` is run as.
User or group needs write permission
@ -721,8 +718,8 @@ in {
default = "borg";
};
authorizedKeys = mkOption {
type = with types; listOf str;
authorizedKeys = lib.mkOption {
type = with lib.types; listOf str;
description = ''
Public SSH keys that are given full write access to this repository.
You should use a different SSH key for each repository you write to, because
@ -732,8 +729,8 @@ in {
default = [ ];
};
authorizedKeysAppendOnly = mkOption {
type = with types; listOf str;
authorizedKeysAppendOnly = lib.mkOption {
type = with lib.types; listOf str;
description = ''
Public SSH keys that can only be used to append new data (archives) to the repository.
Note that archives can still be marked as deleted and are subsequently removed from disk
@ -742,8 +739,8 @@ in {
default = [ ];
};
allowSubRepos = mkOption {
type = types.bool;
allowSubRepos = lib.mkOption {
type = lib.types.bool;
description = ''
Allow clients to create repositories in subdirectories of the
specified {option}`path`. These can be accessed using
@ -755,9 +752,9 @@ in {
default = false;
};
quota = mkOption {
quota = lib.mkOption {
# See the definition of parse_file_size() in src/borg/helpers/parseformat.py
type = with types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
type = with lib.types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
description = ''
Storage quota for the repository. This quota is ensured for all
sub-repositories if {option}`allowSubRepos` is enabled
@ -774,29 +771,29 @@ in {
###### implementation
config = mkIf (with config.services.borgbackup; jobs != { } || repos != { })
config = lib.mkIf (with config.services.borgbackup; jobs != { } || repos != { })
(with config.services.borgbackup; {
assertions =
mapAttrsToList mkPassAssertion jobs
++ mapAttrsToList mkKeysAssertion repos
++ mapAttrsToList mkSourceAssertions jobs
++ mapAttrsToList mkRemovableDeviceAssertions jobs;
lib.mapAttrsToList mkPassAssertion jobs
++ lib.mapAttrsToList mkKeysAssertion repos
++ lib.mapAttrsToList mkSourceAssertions jobs
++ lib.mapAttrsToList mkRemovableDeviceAssertions jobs;
systemd.tmpfiles.settings = mapAttrs' mkTmpfiles jobs;
systemd.tmpfiles.settings = lib.mapAttrs' mkTmpfiles jobs;
systemd.services =
# A job named "foo" is mapped to systemd.services.borgbackup-job-foo
mapAttrs' mkBackupService jobs
lib.mapAttrs' mkBackupService jobs
# A repo named "foo" is mapped to systemd.services.borgbackup-repo-foo
// mapAttrs' mkRepoService repos;
// lib.mapAttrs' mkRepoService repos;
# A job named "foo" is mapped to systemd.timers.borgbackup-job-foo
# only generate the timer if interval (startAt) is set
systemd.timers = mapAttrs' mkBackupTimers (filterAttrs (_: cfg: cfg.startAt != []) jobs);
systemd.timers = lib.mapAttrs' mkBackupTimers (lib.filterAttrs (_: cfg: cfg.startAt != []) jobs);
users = mkMerge (mapAttrsToList mkUsersConfig repos);
users = lib.mkMerge (lib.mapAttrsToList mkUsersConfig repos);
environment.systemPackages =
[ config.services.borgbackup.package ] ++ (mapAttrsToList mkBorgWrapper jobs);
[ config.services.borgbackup.package ] ++ (lib.mapAttrsToList mkBorgWrapper jobs);
});
}