nixos-config/modules/home-manager/services/borgbackup.nix

264 lines
8.0 KiB
Nix
Raw Permalink Normal View History

# 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;
};
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" ];
};
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" ];
};
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" ];
};
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
{ };
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" {
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)
}
'';
mkBorgServiceUnit = n: v:
lib.nameValuePair (makeJobName n) {
2025-01-29 04:48:19 +00:00
Unit = { Description = "Periodic BorgBackup job '${n}'"; };
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;
};
};
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 {
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';
2025-01-29 04:48:19 +00:00
systemd.user.services = lib.mapAttrs' mkBorgServiceUnit cfg.jobs;
2025-01-29 04:48:19 +00:00
systemd.user.timers = lib.mapAttrs' mkBorgTimerUnit cfg.jobs;
};
}