{ 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}: nameValuePair "clerie-backup-${jobName}-${targetName}" { requires = [ "network.target" "local-fs.target" ]; after = [ "network.target" "local-fs.target" ]; path = [ pkgs.clerie-backup ]; serviceConfig = { Type = "oneshot"; }; script = '' set -euo pipefail clerie-backup "${jobName}-${targetName}" backup ${optionalString (config.clerie.monitoring.enable) '' echo "clerie_backup_last_successful_run_time{backup_job=\"${jobName}\", backup_target=\"${targetName}\"} $(date +%s)" > /var/lib/prometheus-node-exporter/textfiles/clerie-backup-${jobName}-${targetName}.prom ''} ''; } ) jobTargetPairs); backupServiceTimers = listToAttrs (map ({jobName, jobOptions, targetName, targetOptions}: nameValuePair "clerie-backup-${jobName}-${targetName}" { wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "hourly"; RandomizedDelaySec = "1h"; }; requires = [ "network-online.target" ]; after = [ "network-online.target" ]; } ) jobTargetPairs); backupConfigs = mergeAttrsList (map ({jobName, jobOptions, targetName, targetOptions}: let jobPasswordFile = if jobOptions.passwordFile != null then jobOptions.passwordFile else config.sops.secrets."clerie-backup-job-${jobName}".path; repoPath = if jobOptions.repoPath == null then "/${config.networking.hostName}/${jobName}" else jobOptions.repoPath; targetPasswordFile = if targetOptions.passwordFile != null then targetOptions.passwordFile else config.sops.secrets."clerie-backup-target-${targetName}".path; targetUsername = if targetOptions.username == null then config.networking.hostName else targetOptions.username; in { "clerie-backup/${jobName}-${targetName}/repo_password".source = jobPasswordFile; "clerie-backup/${jobName}-${targetName}/repo_url".text = "https://${targetOptions.serverName}${repoPath}"; "clerie-backup/${jobName}-${targetName}/auth_username".text = targetUsername; "clerie-backup/${jobName}-${targetName}/auth_password".source = targetPasswordFile; "clerie-backup/${jobName}-${targetName}/files".text = concatStringsSep "\n" jobOptions.paths; "clerie-backup/${jobName}-${targetName}/excludes".text = concatStringsSep "\n" jobOptions.exclude; } ) 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; }; exclude = mkOption { type = with types; listOf str; default = []; }; }; }; 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; systemd.tmpfiles.rules = [ "d /var/cache/restic - - - - -" ]; environment.systemPackages = [ pkgs.clerie-backup ]; environment.etc = backupConfigs; }; }