{ config, lib, pkgs, ... }: with lib; let cfg = config.clerie.backup; jobTargetPairs = flatten ( mapAttrsToList (jobName: jobOptions: let jobTargets = if jobOptions.targets == null then cfg.targets else filterAttrs (targetName: targetOptions: any (map (jobOptionTarget: jobOptionTarget == targetName) jobOptions.targets) ) cfg.targets; in mapAttrsToList (targetName: targetOptions: { inherit jobName jobOptions targetName targetOptions; } ) jobTargets ) cfg.jobs ); backupServiceUnits = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}: let jobPasswordFile = if jobOptions.passwordFile == null then config.age.secrets."clerie-backup-job-${jobName}".path else jobOptions.passwordFile; repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath; targetPasswordFile = if targetOptions.passwordFile == null then config.age.secrets."clerie-backup-target-${targetName}".path else targetOptions.passwordFile; targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username; in nameValuePair "clerie-backup-${jobName}-${targetName}" { requires = [ "network.target" "local-fs.target" ]; path = [ pkgs.restic ]; serviceConfig = { Type = "oneshot"; }; script = '' set -euo pipefail export RESTIC_PASSWORD_FILE=${jobPasswordFile} export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}" restic snapshots || restic init restic backup ${escapeShellArgs jobOptions.paths} restic check ''; } ) jobTargetPairs); backupServiceTimers = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}: nameValuePair "clerie-backup-${jobName}-${targetName}" { wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "hourly"; RandomizedDelaySec = "1h"; }; after = [ "network-online.target" ]; } ) jobTargetPairs); targetOptions = { ... }: { options = { passwordFile = mkOption { type = with types; nullOr str; default = null; }; username = mkOption { type = with types; nullOr str; default = null; }; serverName = mkOption { type = types.str; }; }; }; jobOptions = { ... }: { options = { passwordFile = mkOption { type = with types; nullOr str; default = null; }; repoPath = mkOption { type = with types; nullOr str; default = null; }; targets = mkOption { type = with types; nullOr (listOf str); default = null; }; paths = mkOption { type = with types; listOf str; }; }; }; in { options = { clerie.backup = { enable = mkEnableOption "clerie's Backup"; targets = mkOption { type = with types; attrsOf (submodule targetOptions); default = {}; }; jobs = mkOption { type = with types; attrsOf (submodule jobOptions); default = {}; }; }; }; config = mkIf cfg.enable { systemd.services = backupServiceUnits; systemd.timers = backupServiceTimers; }; }