mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 19:46:05 +01:00
restic: decouple helper script from linux-specific systemd logic
And make platform support explicit.
This commit is contained in:
parent
12bca6d40a
commit
c199de6cd8
1 changed files with 405 additions and 343 deletions
|
|
@ -43,6 +43,40 @@ let
|
||||||
gnugrep
|
gnugrep
|
||||||
which
|
which
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Create the base restic environment variables for both systemd
|
||||||
|
# and the helper script
|
||||||
|
mkEnvironment =
|
||||||
|
backup:
|
||||||
|
lib.flatten [
|
||||||
|
"PATH=${backup.ssh-package}/bin"
|
||||||
|
|
||||||
|
(attrsToEnvs (
|
||||||
|
{
|
||||||
|
RESTIC_PROGRESS_FPS = backup.progressFps;
|
||||||
|
RESTIC_PASSWORD_FILE = backup.passwordFile;
|
||||||
|
RESTIC_REPOSITORY = backup.repository;
|
||||||
|
RESTIC_REPOSITORY_FILE = backup.repositoryFile;
|
||||||
|
}
|
||||||
|
// backup.rcloneOptions
|
||||||
|
))
|
||||||
|
];
|
||||||
|
|
||||||
|
inherit (pkgs.stdenv.hostPlatform) isLinux;
|
||||||
|
|
||||||
|
# Until we have launchd support (#7924), mark the options
|
||||||
|
# not used in the helper script as "linux exclusive"
|
||||||
|
linuxExclusive =
|
||||||
|
option:
|
||||||
|
option
|
||||||
|
// {
|
||||||
|
readOnly = pkgs.stdenv.hostPlatform.isDarwin;
|
||||||
|
|
||||||
|
description = option.description + ''
|
||||||
|
|
||||||
|
This option is only supported on linux.
|
||||||
|
'';
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.restic = {
|
options.services.restic = {
|
||||||
|
|
@ -50,7 +84,14 @@ in
|
||||||
|
|
||||||
backups = lib.mkOption {
|
backups = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Periodic backups to create with Restic.
|
Backup configurations for Restic.
|
||||||
|
|
||||||
|
On Linux systems, a corresponding systemd user service
|
||||||
|
(and optionally a systemd timer for automatic scheduling)
|
||||||
|
will be created, along with a helper wrapper script.
|
||||||
|
|
||||||
|
On non-Linux platforms, only the helper wrapper script
|
||||||
|
will be created.
|
||||||
'';
|
'';
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
lib.types.submodule (
|
lib.types.submodule (
|
||||||
|
|
@ -102,16 +143,18 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
inhibitsSleep = lib.mkOption {
|
inhibitsSleep = linuxExclusive (
|
||||||
default = false;
|
lib.mkOption {
|
||||||
type = lib.types.bool;
|
default = false;
|
||||||
example = true;
|
type = lib.types.bool;
|
||||||
description = ''
|
example = true;
|
||||||
Prevents the system from sleeping while backing up. This uses systemd-inhibit
|
description = ''
|
||||||
to block system idling so you may need to enable polkitd with
|
Prevents the system from sleeping while backing up. This uses systemd-inhibit
|
||||||
{option}`security.polkit.enable`.
|
to block system idling so you may need to enable polkitd with
|
||||||
'';
|
{option}`security.polkit.enable`.
|
||||||
};
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
repository = lib.mkOption {
|
repository = lib.mkOption {
|
||||||
type = with lib.types; nullOr str;
|
type = with lib.types; nullOr str;
|
||||||
|
|
@ -136,63 +179,71 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
paths = lib.mkOption {
|
paths = linuxExclusive (
|
||||||
type = with lib.types; listOf str;
|
lib.mkOption {
|
||||||
default = [ ];
|
type = with lib.types; listOf str;
|
||||||
description = ''
|
default = [ ];
|
||||||
Paths to back up, alongside those defined by the {option}`dynamicFilesFrom`
|
description = ''
|
||||||
option. If left empty and {option}`dynamicFilesFrom` is also not specified, no
|
Paths to back up, alongside those defined by the {option}`dynamicFilesFrom`
|
||||||
backup command will be run. This can be used to create a prune-only job.
|
option. If left empty and {option}`dynamicFilesFrom` is also not specified, no
|
||||||
'';
|
backup command will be run. This can be used to create a prune-only job.
|
||||||
example = [
|
'';
|
||||||
"/var/lib/postgresql"
|
example = [
|
||||||
"/home/user/backup"
|
"/var/lib/postgresql"
|
||||||
];
|
"/home/user/backup"
|
||||||
};
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
exclude = lib.mkOption {
|
exclude = linuxExclusive (
|
||||||
type = with lib.types; listOf str;
|
lib.mkOption {
|
||||||
default = [ ];
|
type = with lib.types; listOf str;
|
||||||
description = ''
|
default = [ ];
|
||||||
Patterns to exclude when backing up. See
|
description = ''
|
||||||
<https://restic.readthedocs.io/en/stable/040_backup.html#excluding-files> for
|
Patterns to exclude when backing up. See
|
||||||
details on syntax.
|
<https://restic.readthedocs.io/en/stable/040_backup.html#excluding-files> for
|
||||||
'';
|
details on syntax.
|
||||||
example = [
|
'';
|
||||||
"/var/cache"
|
example = [
|
||||||
"/home/*/.cache"
|
"/var/cache"
|
||||||
".git"
|
"/home/*/.cache"
|
||||||
];
|
".git"
|
||||||
};
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
timerConfig = lib.mkOption {
|
timerConfig = linuxExclusive (
|
||||||
type = lib.types.nullOr unitType;
|
lib.mkOption {
|
||||||
default = {
|
type = lib.types.nullOr unitType;
|
||||||
OnCalendar = "daily";
|
default = {
|
||||||
Persistent = true;
|
OnCalendar = "daily";
|
||||||
};
|
Persistent = true;
|
||||||
description = ''
|
};
|
||||||
When to run the backup. See {manpage}`systemd.timer(5)` for details. If null
|
description = ''
|
||||||
no timer is created and the backup will only run when explicitly started.
|
When to run the backup. See {manpage}`systemd.timer(5)` for details. If null
|
||||||
'';
|
no timer is created and the backup will only run when explicitly started.
|
||||||
example = {
|
'';
|
||||||
OnCalendar = "00:05";
|
example = {
|
||||||
RandomizedDelaySec = "5h";
|
OnCalendar = "00:05";
|
||||||
Persistent = true;
|
RandomizedDelaySec = "5h";
|
||||||
};
|
Persistent = true;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
extraBackupArgs = lib.mkOption {
|
extraBackupArgs = linuxExclusive (
|
||||||
type = with lib.types; listOf str;
|
lib.mkOption {
|
||||||
default = [ ];
|
type = with lib.types; listOf str;
|
||||||
description = ''
|
default = [ ];
|
||||||
Extra arguments passed to restic backup.
|
description = ''
|
||||||
'';
|
Extra arguments passed to restic backup.
|
||||||
example = [
|
'';
|
||||||
"--cleanup-cache"
|
example = [
|
||||||
"--exclude-file=/etc/nixos/restic-ignore"
|
"--cleanup-cache"
|
||||||
];
|
"--exclude-file=/etc/nixos/restic-ignore"
|
||||||
};
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
extraOptions = lib.mkOption {
|
extraOptions = lib.mkOption {
|
||||||
type = with lib.types; listOf str;
|
type = with lib.types; listOf str;
|
||||||
|
|
@ -206,77 +257,91 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
initialize = lib.mkOption {
|
initialize = linuxExclusive (
|
||||||
type = lib.types.bool;
|
lib.mkOption {
|
||||||
default = false;
|
type = lib.types.bool;
|
||||||
description = ''
|
default = false;
|
||||||
Create the repository if it does not already exist.
|
description = ''
|
||||||
'';
|
Create the repository if it does not already exist.
|
||||||
};
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
pruneOpts = lib.mkOption {
|
pruneOpts = linuxExclusive (
|
||||||
type = with lib.types; listOf str;
|
lib.mkOption {
|
||||||
default = [ ];
|
type = with lib.types; listOf str;
|
||||||
description = ''
|
default = [ ];
|
||||||
A list of policy options for 'restic forget --prune', to automatically
|
description = ''
|
||||||
prune old snapshots. See
|
A list of policy options for 'restic forget --prune', to automatically
|
||||||
<https://restic.readthedocs.io/en/latest/060_forget.html#removing-snapshots-according-to-a-policy>
|
prune old snapshots. See
|
||||||
for a full list of options.
|
<https://restic.readthedocs.io/en/latest/060_forget.html#removing-snapshots-according-to-a-policy>
|
||||||
|
for a full list of options.
|
||||||
|
|
||||||
Note: The 'forget' command is run *after* the 'backup' command, so keep
|
Note: The 'forget' command is run *after* the 'backup' command, so keep
|
||||||
that in mind when constructing the --keep-\* options.
|
that in mind when constructing the --keep-\* options.
|
||||||
'';
|
'';
|
||||||
example = [
|
example = [
|
||||||
"--keep-daily 7"
|
"--keep-daily 7"
|
||||||
"--keep-weekly 5"
|
"--keep-weekly 5"
|
||||||
"--keep-monthly 12"
|
"--keep-monthly 12"
|
||||||
"--keep-yearly 75"
|
"--keep-yearly 75"
|
||||||
];
|
];
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
runCheck = lib.mkOption {
|
runCheck = linuxExclusive (
|
||||||
type = lib.types.bool;
|
lib.mkOption {
|
||||||
default = lib.length config.checkOpts > 0 || lib.length config.pruneOpts > 0;
|
type = lib.types.bool;
|
||||||
defaultText = lib.literalExpression "lib.length config.checkOpts > 0 || lib.length config.pruneOpts > 0";
|
default = lib.length config.checkOpts > 0 || lib.length config.pruneOpts > 0;
|
||||||
description = "Whether to run 'restic check' with the provided `checkOpts` options.";
|
defaultText = lib.literalExpression "lib.length config.checkOpts > 0 || lib.length config.pruneOpts > 0";
|
||||||
example = true;
|
description = "Whether to run 'restic check' with the provided `checkOpts` options.";
|
||||||
};
|
example = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
checkOpts = lib.mkOption {
|
checkOpts = linuxExclusive (
|
||||||
type = with lib.types; listOf str;
|
lib.mkOption {
|
||||||
default = [ ];
|
type = with lib.types; listOf str;
|
||||||
description = ''
|
default = [ ];
|
||||||
A list of options for 'restic check'.
|
description = ''
|
||||||
'';
|
A list of options for 'restic check'.
|
||||||
example = [ "--with-cache" ];
|
'';
|
||||||
};
|
example = [ "--with-cache" ];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
dynamicFilesFrom = lib.mkOption {
|
dynamicFilesFrom = linuxExclusive (
|
||||||
type = with lib.types; nullOr str;
|
lib.mkOption {
|
||||||
default = null;
|
type = with lib.types; nullOr str;
|
||||||
description = ''
|
default = null;
|
||||||
A script that produces a list of files to back up. The results of
|
description = ''
|
||||||
this command, along with the paths specified via {option}`paths`,
|
A script that produces a list of files to back up. The results of
|
||||||
are given to the '--files-from' option.
|
this command, along with the paths specified via {option}`paths`,
|
||||||
'';
|
are given to the '--files-from' option.
|
||||||
example = "find /home/alice/git -type d -name .git";
|
'';
|
||||||
};
|
example = "find /home/alice/git -type d -name .git";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
backupPrepareCommand = lib.mkOption {
|
backupPrepareCommand = linuxExclusive (
|
||||||
type = with lib.types; nullOr str;
|
lib.mkOption {
|
||||||
default = null;
|
type = with lib.types; nullOr str;
|
||||||
description = ''
|
default = null;
|
||||||
A script that must run before starting the backup process.
|
description = ''
|
||||||
'';
|
A script that must run before starting the backup process.
|
||||||
};
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
backupCleanupCommand = lib.mkOption {
|
backupCleanupCommand = linuxExclusive (
|
||||||
type = with lib.types; nullOr str;
|
lib.mkOption {
|
||||||
default = null;
|
type = with lib.types; nullOr str;
|
||||||
description = ''
|
default = null;
|
||||||
A script that must run after finishing the backup process.
|
description = ''
|
||||||
'';
|
A script that must run after finishing the backup process.
|
||||||
};
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
createWrapper = lib.mkOption {
|
createWrapper = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
|
|
@ -326,224 +391,221 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable (
|
||||||
assertions = lib.mapAttrsToList (n: v: {
|
lib.mkMerge [
|
||||||
assertion = lib.xor (v.repository == null) (v.repositoryFile == null);
|
{
|
||||||
message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
|
assertions = lib.mapAttrsToList (n: v: {
|
||||||
}) cfg.backups;
|
assertion = lib.xor (v.repository == null) (v.repositoryFile == null);
|
||||||
|
message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
|
||||||
systemd.user.services = lib.mapAttrs' (
|
}) cfg.backups;
|
||||||
name: backup:
|
|
||||||
let
|
|
||||||
doBackup = backup.dynamicFilesFrom != null || backup.paths != [ ];
|
|
||||||
doPrune = backup.pruneOpts != [ ];
|
|
||||||
doCheck = backup.runCheck;
|
|
||||||
serviceName = "restic-backups-${name}";
|
|
||||||
|
|
||||||
extraOptions = lib.concatMap (arg: [
|
|
||||||
"-o"
|
|
||||||
arg
|
|
||||||
]) backup.extraOptions;
|
|
||||||
|
|
||||||
excludeFile = pkgs.writeText "exclude-patterns" (lib.concatLines backup.exclude);
|
|
||||||
excludeFileFlag = "--exclude-file=${excludeFile}";
|
|
||||||
|
|
||||||
filesFromTmpFile = "/run/user/$UID/${serviceName}/includes";
|
|
||||||
filesFromFlag = "--files-from=${filesFromTmpFile}";
|
|
||||||
|
|
||||||
inhibitCmd = lib.optionals backup.inhibitsSleep [
|
|
||||||
"${pkgs.systemd}/bin/systemd-inhibit"
|
|
||||||
"--mode='block'"
|
|
||||||
"--who='restic'"
|
|
||||||
"--what='idle'"
|
|
||||||
"--why=${lib.escapeShellArg "Scheduled backup ${name}"}"
|
|
||||||
];
|
|
||||||
|
|
||||||
mkResticCmd' =
|
|
||||||
pre: args:
|
|
||||||
lib.concatStringsSep " " (
|
|
||||||
pre ++ lib.singleton (lib.getExe backup.package) ++ extraOptions ++ lib.flatten args
|
|
||||||
);
|
|
||||||
mkResticCmd = mkResticCmd' [ ];
|
|
||||||
|
|
||||||
backupCmd =
|
|
||||||
"${lib.getExe pkgs.bash} -c "
|
|
||||||
+ lib.escapeShellArg (
|
|
||||||
mkResticCmd' inhibitCmd [
|
|
||||||
"backup"
|
|
||||||
backup.extraBackupArgs
|
|
||||||
excludeFileFlag
|
|
||||||
filesFromFlag
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
forgetCmd = mkResticCmd [
|
|
||||||
"forget"
|
|
||||||
"--prune"
|
|
||||||
backup.pruneOpts
|
|
||||||
];
|
|
||||||
checkCmd = mkResticCmd [
|
|
||||||
"check"
|
|
||||||
backup.checkOpts
|
|
||||||
];
|
|
||||||
unlockCmd = mkResticCmd "unlock";
|
|
||||||
in
|
|
||||||
lib.nameValuePair serviceName {
|
|
||||||
Unit = {
|
|
||||||
Description = "Restic backup service";
|
|
||||||
Wants = [ "network-online.target" ];
|
|
||||||
After = [ "network-online.target" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
Service = {
|
|
||||||
Type = "oneshot";
|
|
||||||
|
|
||||||
X-RestartIfChanged = true;
|
|
||||||
RuntimeDirectory = serviceName;
|
|
||||||
CacheDirectory = serviceName;
|
|
||||||
CacheDirectoryMode = "0700";
|
|
||||||
PrivateTmp = true;
|
|
||||||
|
|
||||||
Environment = [
|
|
||||||
"RESTIC_CACHE_DIR=%C"
|
|
||||||
"PATH=${backup.ssh-package}/bin"
|
|
||||||
]
|
|
||||||
++ attrsToEnvs (
|
|
||||||
{
|
|
||||||
RESTIC_PROGRESS_FPS = backup.progressFps;
|
|
||||||
RESTIC_PASSWORD_FILE = backup.passwordFile;
|
|
||||||
RESTIC_REPOSITORY = backup.repository;
|
|
||||||
RESTIC_REPOSITORY_FILE = backup.repositoryFile;
|
|
||||||
}
|
|
||||||
// backup.rcloneOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
ExecStart =
|
|
||||||
lib.optional doBackup backupCmd
|
|
||||||
++ lib.optionals doPrune [
|
|
||||||
unlockCmd
|
|
||||||
forgetCmd
|
|
||||||
]
|
|
||||||
++ lib.optional doCheck checkCmd;
|
|
||||||
|
|
||||||
ExecStartPre = lib.getExe (
|
|
||||||
pkgs.writeShellApplication {
|
|
||||||
name = "${serviceName}-exec-start-pre";
|
|
||||||
inherit runtimeInputs;
|
|
||||||
text = ''
|
|
||||||
set -x
|
|
||||||
|
|
||||||
${lib.optionalString (backup.backupPrepareCommand != null) ''
|
|
||||||
${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
|
|
||||||
''}
|
|
||||||
|
|
||||||
${lib.optionalString (backup.initialize) ''
|
|
||||||
${
|
|
||||||
mkResticCmd [
|
|
||||||
"cat"
|
|
||||||
"config"
|
|
||||||
]
|
|
||||||
} 2>/dev/null || ${mkResticCmd "init"}
|
|
||||||
''}
|
|
||||||
|
|
||||||
${lib.optionalString (backup.paths != null && backup.paths != [ ]) ''
|
|
||||||
cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile}
|
|
||||||
''}
|
|
||||||
|
|
||||||
${lib.optionalString (backup.dynamicFilesFrom != null) ''
|
|
||||||
${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
|
|
||||||
''}
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ExecStopPost = lib.getExe (
|
|
||||||
pkgs.writeShellApplication {
|
|
||||||
name = "${serviceName}-exec-stop-post";
|
|
||||||
inherit runtimeInputs;
|
|
||||||
text = ''
|
|
||||||
set -x
|
|
||||||
|
|
||||||
${lib.optionalString (backup.backupCleanupCommand != null) ''
|
|
||||||
${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
|
|
||||||
''}
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// lib.optionalAttrs (backup.environmentFile != null) {
|
|
||||||
EnvironmentFile = backup.environmentFile;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
) cfg.backups;
|
|
||||||
|
|
||||||
systemd.user.timers = lib.mapAttrs' (
|
(lib.mkIf isLinux {
|
||||||
name: backup:
|
systemd.user.services = lib.mapAttrs' (
|
||||||
lib.nameValuePair "restic-backups-${name}" {
|
name: backup:
|
||||||
Unit.Description = "Restic backup service";
|
let
|
||||||
Install.WantedBy = [ "timers.target" ];
|
doBackup = backup.dynamicFilesFrom != null || backup.paths != [ ];
|
||||||
|
doPrune = backup.pruneOpts != [ ];
|
||||||
|
doCheck = backup.runCheck;
|
||||||
|
serviceName = "restic-backups-${name}";
|
||||||
|
|
||||||
Timer = backup.timerConfig;
|
extraOptions = lib.concatMap (arg: [
|
||||||
|
"-o"
|
||||||
|
arg
|
||||||
|
]) backup.extraOptions;
|
||||||
|
|
||||||
|
excludeFile = pkgs.writeText "exclude-patterns" (lib.concatLines backup.exclude);
|
||||||
|
excludeFileFlag = "--exclude-file=${excludeFile}";
|
||||||
|
|
||||||
|
filesFromTmpFile = "/run/user/$UID/${serviceName}/includes";
|
||||||
|
filesFromFlag = "--files-from=${filesFromTmpFile}";
|
||||||
|
|
||||||
|
inhibitCmd = lib.optionals backup.inhibitsSleep [
|
||||||
|
"${pkgs.systemd}/bin/systemd-inhibit"
|
||||||
|
"--mode='block'"
|
||||||
|
"--who='restic'"
|
||||||
|
"--what='idle'"
|
||||||
|
"--why=${lib.escapeShellArg "Scheduled backup ${name}"}"
|
||||||
|
];
|
||||||
|
|
||||||
|
mkResticCmd' =
|
||||||
|
pre: args:
|
||||||
|
lib.concatStringsSep " " (
|
||||||
|
pre ++ lib.singleton (lib.getExe backup.package) ++ extraOptions ++ lib.flatten args
|
||||||
|
);
|
||||||
|
mkResticCmd = mkResticCmd' [ ];
|
||||||
|
|
||||||
|
backupCmd =
|
||||||
|
"${lib.getExe pkgs.bash} -c "
|
||||||
|
+ lib.escapeShellArg (
|
||||||
|
mkResticCmd' inhibitCmd [
|
||||||
|
"backup"
|
||||||
|
backup.extraBackupArgs
|
||||||
|
excludeFileFlag
|
||||||
|
filesFromFlag
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
forgetCmd = mkResticCmd [
|
||||||
|
"forget"
|
||||||
|
"--prune"
|
||||||
|
backup.pruneOpts
|
||||||
|
];
|
||||||
|
checkCmd = mkResticCmd [
|
||||||
|
"check"
|
||||||
|
backup.checkOpts
|
||||||
|
];
|
||||||
|
unlockCmd = mkResticCmd "unlock";
|
||||||
|
in
|
||||||
|
lib.nameValuePair serviceName {
|
||||||
|
Unit = {
|
||||||
|
Description = "Restic backup service";
|
||||||
|
Wants = [ "network-online.target" ];
|
||||||
|
After = [ "network-online.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
|
||||||
|
X-RestartIfChanged = true;
|
||||||
|
RuntimeDirectory = serviceName;
|
||||||
|
CacheDirectory = serviceName;
|
||||||
|
CacheDirectoryMode = "0700";
|
||||||
|
PrivateTmp = true;
|
||||||
|
|
||||||
|
Environment = mkEnvironment backup ++ [ "RESTIC_CACHE_DIR=%C" ];
|
||||||
|
|
||||||
|
ExecStart =
|
||||||
|
lib.optional doBackup backupCmd
|
||||||
|
++ lib.optionals doPrune [
|
||||||
|
unlockCmd
|
||||||
|
forgetCmd
|
||||||
|
]
|
||||||
|
++ lib.optional doCheck checkCmd;
|
||||||
|
|
||||||
|
ExecStartPre = lib.getExe (
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "${serviceName}-exec-start-pre";
|
||||||
|
inherit runtimeInputs;
|
||||||
|
text = ''
|
||||||
|
set -x
|
||||||
|
|
||||||
|
${lib.optionalString (backup.backupPrepareCommand != null) ''
|
||||||
|
${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
|
||||||
|
''}
|
||||||
|
|
||||||
|
${lib.optionalString (backup.initialize) ''
|
||||||
|
${
|
||||||
|
mkResticCmd [
|
||||||
|
"cat"
|
||||||
|
"config"
|
||||||
|
]
|
||||||
|
} 2>/dev/null || ${mkResticCmd "init"}
|
||||||
|
''}
|
||||||
|
|
||||||
|
${lib.optionalString (backup.paths != null && backup.paths != [ ]) ''
|
||||||
|
cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile}
|
||||||
|
''}
|
||||||
|
|
||||||
|
${lib.optionalString (backup.dynamicFilesFrom != null) ''
|
||||||
|
${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
|
||||||
|
''}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ExecStopPost = lib.getExe (
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "${serviceName}-exec-stop-post";
|
||||||
|
inherit runtimeInputs;
|
||||||
|
text = ''
|
||||||
|
set -x
|
||||||
|
|
||||||
|
${lib.optionalString (backup.backupCleanupCommand != null) ''
|
||||||
|
${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
|
||||||
|
''}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (backup.environmentFile != null) {
|
||||||
|
EnvironmentFile = backup.environmentFile;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) cfg.backups;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf isLinux {
|
||||||
|
systemd.user.timers = lib.mapAttrs' (
|
||||||
|
name: backup:
|
||||||
|
lib.nameValuePair "restic-backups-${name}" {
|
||||||
|
Unit.Description = "Restic backup service";
|
||||||
|
Install.WantedBy = [ "timers.target" ];
|
||||||
|
|
||||||
|
Timer = backup.timerConfig;
|
||||||
|
}
|
||||||
|
) (lib.filterAttrs (_: v: v.timerConfig != null) cfg.backups);
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
home.packages = lib.mapAttrsToList (
|
||||||
|
name: backup:
|
||||||
|
let
|
||||||
|
serviceName = "restic-backups-${name}";
|
||||||
|
environment = mkEnvironment backup;
|
||||||
|
notPathVar = x: !(lib.hasPrefix "PATH" x);
|
||||||
|
extraOptions = lib.concatMap (arg: [
|
||||||
|
"-o"
|
||||||
|
arg
|
||||||
|
]) backup.extraOptions;
|
||||||
|
restic = lib.concatStringsSep " " (
|
||||||
|
lib.flatten [
|
||||||
|
(lib.getExe backup.package)
|
||||||
|
extraOptions
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "restic-${name}";
|
||||||
|
excludeShellChecks = [
|
||||||
|
# https://github.com/koalaman/shellcheck/issues/1986
|
||||||
|
"SC2034"
|
||||||
|
# Allow sourcing environmentFile
|
||||||
|
"SC1091"
|
||||||
|
];
|
||||||
|
bashOptions = [
|
||||||
|
"errexit"
|
||||||
|
"nounset"
|
||||||
|
"allexport"
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
${lib.optionalString (backup.environmentFile != null) ''
|
||||||
|
source ${backup.environmentFile}
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Set same environment variables as the systemd service
|
||||||
|
${lib.pipe environment [
|
||||||
|
(lib.filter notPathVar)
|
||||||
|
lib.concatLines
|
||||||
|
]}
|
||||||
|
|
||||||
|
RESTIC_CACHE_DIR=$HOME/.cache/${serviceName}
|
||||||
|
|
||||||
|
PATH=${
|
||||||
|
lib.pipe environment [
|
||||||
|
(lib.filter (lib.hasPrefix "PATH="))
|
||||||
|
lib.head
|
||||||
|
(lib.removePrefix "PATH=")
|
||||||
|
]
|
||||||
|
}:$PATH
|
||||||
|
|
||||||
|
exec ${restic} "$@"
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
) (lib.filterAttrs (_: v: v.createWrapper) cfg.backups);
|
||||||
}
|
}
|
||||||
) (lib.filterAttrs (_: v: v.timerConfig != null) cfg.backups);
|
]
|
||||||
|
);
|
||||||
home.packages = lib.mapAttrsToList (
|
|
||||||
name: backup:
|
|
||||||
let
|
|
||||||
serviceName = "restic-backups-${name}";
|
|
||||||
backupService = config.systemd.user.services.${serviceName};
|
|
||||||
notPathVar = x: !(lib.hasPrefix "PATH" x);
|
|
||||||
extraOptions = lib.concatMap (arg: [
|
|
||||||
"-o"
|
|
||||||
arg
|
|
||||||
]) backup.extraOptions;
|
|
||||||
restic = lib.concatStringsSep " " (
|
|
||||||
lib.flatten [
|
|
||||||
(lib.getExe backup.package)
|
|
||||||
extraOptions
|
|
||||||
]
|
|
||||||
);
|
|
||||||
in
|
|
||||||
pkgs.writeShellApplication {
|
|
||||||
name = "restic-${name}";
|
|
||||||
excludeShellChecks = [
|
|
||||||
# https://github.com/koalaman/shellcheck/issues/1986
|
|
||||||
"SC2034"
|
|
||||||
# Allow sourcing environmentFile
|
|
||||||
"SC1091"
|
|
||||||
];
|
|
||||||
bashOptions = [
|
|
||||||
"errexit"
|
|
||||||
"nounset"
|
|
||||||
"allexport"
|
|
||||||
];
|
|
||||||
text = ''
|
|
||||||
${lib.optionalString (backup.environmentFile != null) ''
|
|
||||||
source ${backup.environmentFile}
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Set same environment variables as the systemd service
|
|
||||||
${lib.pipe backupService.Service.Environment [
|
|
||||||
(lib.filter notPathVar)
|
|
||||||
lib.concatLines
|
|
||||||
]}
|
|
||||||
|
|
||||||
# Override this as %C will not work
|
|
||||||
RESTIC_CACHE_DIR=$HOME/.cache/${serviceName}
|
|
||||||
|
|
||||||
PATH=${
|
|
||||||
lib.pipe backupService.Service.Environment [
|
|
||||||
(lib.filter (lib.hasPrefix "PATH="))
|
|
||||||
lib.head
|
|
||||||
(lib.removePrefix "PATH=")
|
|
||||||
]
|
|
||||||
}:$PATH
|
|
||||||
|
|
||||||
exec ${restic} "$@"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
) (lib.filterAttrs (_: v: v.createWrapper) cfg.backups);
|
|
||||||
};
|
|
||||||
|
|
||||||
meta.maintainers = [ lib.maintainers.jess ];
|
meta.maintainers = [ lib.maintainers.jess ];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue