2024-09-23 04:54:20 +00:00
|
|
|
# A home-manager port of NixOS' `services.borgbackup` module. I tried to make
|
|
|
|
# it as close to it as possible plus some other options such as adding pattern
|
|
|
|
# files (since it is my preference of indicating which files are included).
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.services.borgbackup;
|
|
|
|
|
|
|
|
borgJobsModule = { name, lib, config, ... }: {
|
|
|
|
options = {
|
2025-01-29 04:48:19 +00:00
|
|
|
exportWrapperScript =
|
|
|
|
lib.mkEnableOption "export wrapper script as part of the environment"
|
|
|
|
// {
|
|
|
|
default = true;
|
|
|
|
};
|
2024-09-23 04:54:20 +00:00
|
|
|
|
|
|
|
extraArgs = lib.mkOption {
|
|
|
|
type = with lib.types; listOf str;
|
|
|
|
description = ''
|
|
|
|
Extra arguments to be passed to all Borg procedures in the resulting
|
|
|
|
script.
|
|
|
|
|
|
|
|
::: {.caution}
|
|
|
|
Be careful with this option as it can affect all commands. See the
|
|
|
|
`extraArgs` equivalent of those specific operations first because
|
|
|
|
adding values here.
|
|
|
|
:::
|
|
|
|
'';
|
|
|
|
default = [ ];
|
2025-01-29 04:48:19 +00:00
|
|
|
example = [ "--remote-path=/path/to/borg/repo" ];
|
2024-09-23 04:54:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
extraCreateArgs = lib.mkOption {
|
|
|
|
type = with lib.types; listOf str;
|
|
|
|
description = ''
|
|
|
|
Additional arguments for `borg create`.
|
|
|
|
'';
|
|
|
|
default = [ ];
|
2025-01-29 04:48:19 +00:00
|
|
|
example = [ "--stats" "--checkpoint-interval" "600" ];
|
2024-09-23 04:54:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
extraInitArgs = lib.mkOption {
|
|
|
|
type = with lib.types; listOf str;
|
|
|
|
description = ''
|
|
|
|
Extra arguments to be passed to `borg init`, when applicable.
|
|
|
|
'';
|
|
|
|
default = [ ];
|
2025-01-29 04:48:19 +00:00
|
|
|
example = [ "--make-parent-dirs" "--append-only" ];
|
2024-09-23 04:54:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
patternFiles = lib.mkOption {
|
|
|
|
type = with lib.types; listOf path;
|
|
|
|
description = ''
|
|
|
|
List of paths containing patterns for the Borg job.
|
|
|
|
'';
|
|
|
|
default = [ ];
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
[
|
|
|
|
./config/borg/patterns/home
|
|
|
|
./config/borg/patterns/server
|
|
|
|
./config/borg/patterns/games
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
doInit = lib.mkEnableOption "initialization of the BorgBackup repo";
|
|
|
|
|
|
|
|
startAt = lib.mkOption {
|
|
|
|
type = lib.types.nonEmptyStr;
|
|
|
|
description = ''
|
|
|
|
Indicates how much the backup procedure occurs.
|
|
|
|
'';
|
|
|
|
default = "daily";
|
|
|
|
example = "04:30";
|
|
|
|
};
|
|
|
|
|
|
|
|
environment = lib.mkOption {
|
|
|
|
type = with lib.types; attrsOf str;
|
|
|
|
description = ''
|
|
|
|
Extra environment variables to be set along to the backup service.
|
|
|
|
You could indicate SSH-related settings here for example.
|
|
|
|
'';
|
|
|
|
default = { };
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
{
|
|
|
|
BORG_RSH = "ssh -i ''${config.home.homeDirectory}/.ssh/borg-key.pub";
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
encryption.passCommand = lib.mkOption {
|
|
|
|
type = with lib.types; nullOr str;
|
|
|
|
description = ''
|
|
|
|
Command used to retrieve the password of the repository.
|
|
|
|
|
|
|
|
::: {.note}
|
|
|
|
Mutually exclusive with {option}`encryption.passphrase`.
|
|
|
|
:::
|
|
|
|
'';
|
|
|
|
default = null;
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
cat ''${config.home.homeDirectory}/borg-secret
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
encryption.passphrase = lib.mkOption {
|
|
|
|
type = with lib.types; nullOr str;
|
|
|
|
description = ''
|
|
|
|
Passphrase used to lock the repository.
|
|
|
|
|
|
|
|
::: {.note}
|
|
|
|
This will also store the password as plain-text file in the Nix store
|
|
|
|
directory. If you don't want that, use
|
|
|
|
{option}`encryption.passCommand` instead.
|
|
|
|
|
|
|
|
Mutually exclusive with {option}`encryption.passCommand`.
|
|
|
|
:::
|
|
|
|
'';
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
mkPassEnv = v:
|
|
|
|
# Prefer the pass command option since it is the safer option.
|
2025-01-29 04:48:19 +00:00
|
|
|
if v.encryption.passCommand != null then {
|
|
|
|
BORG_PASSCOMMAND = v.encryption.passCommand;
|
|
|
|
} else if v.encryption.passphrase != null then {
|
|
|
|
BORG_PASSPHRASE = v.encryption.passphrase;
|
|
|
|
} else
|
|
|
|
{ };
|
2024-09-23 04:54:20 +00:00
|
|
|
makeJobName = name: "borg-job-${name}";
|
|
|
|
|
|
|
|
mkBorgWrapperScripts = n: v:
|
|
|
|
let
|
|
|
|
executableName = makeJobName n;
|
|
|
|
setEnv = { BORG_REPO = v.repo; } // (mkPassEnv v) // v.environment;
|
2025-01-29 04:48:19 +00:00
|
|
|
mkWrapperFlag = n: v: ''--set ${lib.escapeShellArg n} "${v}"'';
|
|
|
|
in pkgs.runCommand "${n}-wrapper" {
|
2024-09-23 04:54:20 +00:00
|
|
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
|
|
|
} ''
|
2025-01-29 04:48:19 +00:00
|
|
|
makeWrapper "${
|
|
|
|
lib.getExe' cfg.package "borg"
|
|
|
|
} "$out/bin/${executableName}" \
|
|
|
|
${
|
|
|
|
lib.concatStringsSep " \\\n" (lib.mapAttrsToList mkWrapperFlag setEnv)
|
|
|
|
}
|
2024-09-23 04:54:20 +00:00
|
|
|
'';
|
|
|
|
|
|
|
|
mkBorgServiceUnit = n: v:
|
|
|
|
lib.nameValuePair (makeJobName n) {
|
2025-01-29 04:48:19 +00:00
|
|
|
Unit = { Description = "Periodic BorgBackup job '${n}'"; };
|
2024-09-23 04:54:20 +00:00
|
|
|
|
|
|
|
Service = {
|
|
|
|
CPUSchedulingPolicy = "idle";
|
|
|
|
IOSchedulingClass = "idle";
|
2025-01-29 04:48:19 +00:00
|
|
|
Environment = lib.attrsToList (n: v: "${n}=${v}") ({
|
|
|
|
inherit (v) extraArgs extraInitArgs extraCreateArgs;
|
|
|
|
} // v.environment // (mkPassEnv v)) ++ [ "BORG_REPO=${v.repo}" ];
|
|
|
|
|
|
|
|
ExecStart = let
|
|
|
|
borgScript = pkgs.writeShellApplication {
|
|
|
|
name = "borg-job-${n}-script";
|
|
|
|
runtimeInputs = [ cfg.package ];
|
|
|
|
text = ''
|
|
|
|
on_exit() {
|
|
|
|
exitStatus=$?
|
|
|
|
${cfg.postHook}
|
|
|
|
exit $exitStatus
|
|
|
|
}
|
|
|
|
trap on_exit EXIT
|
|
|
|
|
|
|
|
borgWrapper () {
|
|
|
|
local result
|
|
|
|
borg "$@" && result=$? || result=$?
|
|
|
|
if [[ -z "${
|
|
|
|
toString cfg.failOnWarnings
|
|
|
|
}" ]] && [[ "$result" == 1 ]]; then
|
|
|
|
echo "ignoring warning return value 1"
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
return "$result"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
archiveName="${
|
|
|
|
lib.optionalString (cfg.archiveBaseName != null)
|
|
|
|
(cfg.archiveBaseName + "-")
|
|
|
|
}$(date ${cfg.dateFormat})"
|
|
|
|
archiveSuffix="${
|
|
|
|
lib.optionalString cfg.appendFailedSuffix ".failed"
|
|
|
|
}"
|
|
|
|
${cfg.preHook}
|
|
|
|
'' + lib.optionalString cfg.doInit ''
|
|
|
|
# Run borg init if the repo doesn't exist yet
|
|
|
|
if ! borgWrapper list $extraArgs > /dev/null; then
|
|
|
|
borgWrapper init $extraArgs \
|
|
|
|
--encryption ${cfg.encryption.mode} \
|
|
|
|
$extraInitArgs
|
|
|
|
${cfg.postInit}
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
in lib.getExe borgScript;
|
2024-09-23 04:54:20 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
mkBorgTimerUnit = n: v:
|
|
|
|
lib.nameValuePair (makeJobName n) {
|
|
|
|
Unit.Description = "Periodic BorgBackup job '${n}'";
|
|
|
|
|
|
|
|
Timer = {
|
|
|
|
Persistent = true;
|
|
|
|
RandomizedDelaySec = "1min";
|
|
|
|
OnCalendar = v.startAt;
|
|
|
|
};
|
|
|
|
|
|
|
|
Install.WantedBy = [ "timers.target" ];
|
|
|
|
};
|
2025-01-29 04:48:19 +00:00
|
|
|
in {
|
2024-09-23 04:54:20 +00:00
|
|
|
options.services.borgbackup = {
|
|
|
|
enable = lib.mkEnableOption "periodic backups with BorgBackup";
|
|
|
|
|
|
|
|
package = lib.mkPackageOption pkgs "borgbackup" { };
|
|
|
|
|
|
|
|
jobs = lib.mkOption {
|
|
|
|
type = with lib.types; attrsOf (submodule borgJobsModule);
|
|
|
|
description = ''
|
|
|
|
A set of Borg backup jobs to be done within the home environment.
|
|
|
|
|
|
|
|
Each job can have a wrapper script `borg-job-{name}` as part of your
|
|
|
|
home environment to make maintenance easier.
|
|
|
|
'';
|
|
|
|
default = { };
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
{
|
|
|
|
personal = {
|
|
|
|
doInit = true;
|
|
|
|
encryption = {
|
|
|
|
mode = "repokey";
|
|
|
|
passCommand = "cat ''${config.xdg.configHome}/backup/secret";
|
|
|
|
};
|
|
|
|
patternFiles = [
|
|
|
|
./config/borg/patterns/data
|
|
|
|
./config/borg/patterns/games
|
|
|
|
];
|
|
|
|
startAt = "05:30;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
|
|
home.packages =
|
2025-01-29 04:48:19 +00:00
|
|
|
let jobs' = lib.filterAttrs (n: v: v.exportWrapperScript) cfg.jobs;
|
|
|
|
in lib.mapAttrsToList mkBorgWrapperScripts jobs';
|
2024-09-23 04:54:20 +00:00
|
|
|
|
2025-01-29 04:48:19 +00:00
|
|
|
systemd.user.services = lib.mapAttrs' mkBorgServiceUnit cfg.jobs;
|
2024-09-23 04:54:20 +00:00
|
|
|
|
2025-01-29 04:48:19 +00:00
|
|
|
systemd.user.timers = lib.mapAttrs' mkBorgTimerUnit cfg.jobs;
|
2024-09-23 04:54:20 +00:00
|
|
|
};
|
|
|
|
}
|