diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index abb7925e0935..36f2274d5eaa 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -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}-"''; + defaultText = lib.literalExpression ''"''${config.networking.hostName}-"''; 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); }); }