1
0
nixfiles/modules/backup/default.nix

160 lines
5.3 KiB
Nix

{ 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" ];
after = [ "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}"
export RESTIC_PROGRESS_FPS=0.1
export RESTIC_CACHE_DIR=/var/cache/restic
restic snapshots --latest 1 || restic init
restic backup ${optionalString (jobOptions.exclude != []) "--exclude-file ${pkgs.writeText "clerie-backup-${jobName}-${targetName}-excludes" (concatStringsSep "\n" jobOptions.exclude)}"} ${escapeShellArgs jobOptions.paths}
${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);
backupCommands = 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 pkgs.writeShellApplication {
name = "clerie-backup-${jobName}-${targetName}";
runtimeInputs = [ pkgs.restic ];
text = ''
set -euo pipefail
export RESTIC_PASSWORD_FILE=${jobPasswordFile}
export RESTIC_REPOSITORY="rest:https://${targetUsername}:$(cat ${targetPasswordFile})@${targetOptions.serverName}${repoPath}"
export RESTIC_PROGRESS_FPS=0.1
export RESTIC_CACHE_DIR=/var/cache/restic
restic "$@"
'';
checkPhase = "";
}
) 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 = backupCommands;
};
}