mirror of
https://github.com/nix-community/home-manager.git
synced 2025-03-31 04:04:32 +00:00
Merge 761af0bf76
into b6fd653ef8
This commit is contained in:
commit
14241682ce
9 changed files with 1052 additions and 0 deletions
|
@ -398,6 +398,7 @@ let
|
||||||
./services/redshift-gammastep/gammastep.nix
|
./services/redshift-gammastep/gammastep.nix
|
||||||
./services/redshift-gammastep/redshift.nix
|
./services/redshift-gammastep/redshift.nix
|
||||||
./services/remmina.nix
|
./services/remmina.nix
|
||||||
|
./services/restic.nix
|
||||||
./services/rsibreak.nix
|
./services/rsibreak.nix
|
||||||
./services/safeeyes.nix
|
./services/safeeyes.nix
|
||||||
./services/screen-locker.nix
|
./services/screen-locker.nix
|
||||||
|
|
468
modules/services/restic.nix
Normal file
468
modules/services/restic.nix
Normal file
|
@ -0,0 +1,468 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
unitType = with lib.types;
|
||||||
|
let primitive = oneOf [ bool int str path ];
|
||||||
|
in attrsOf (either primitive (listOf primitive));
|
||||||
|
|
||||||
|
cfg = config.services.restic;
|
||||||
|
|
||||||
|
fmtRcloneOpt = opt:
|
||||||
|
lib.pipe opt [
|
||||||
|
(lib.replaceStrings [ "-" ] [ "_" ])
|
||||||
|
lib.toUpper
|
||||||
|
(lib.add "RCLONE_")
|
||||||
|
];
|
||||||
|
|
||||||
|
toEnvVal = v: if lib.isBool v then lib.boolToString v else v;
|
||||||
|
attrsToEnvs = attrs:
|
||||||
|
lib.pipe attrs [
|
||||||
|
(lib.mapAttrsToList
|
||||||
|
(k: v: if v != null then "${k}=${toEnvVal v}" else [ ]))
|
||||||
|
lib.flatten
|
||||||
|
];
|
||||||
|
|
||||||
|
runtimeInputs = with pkgs; [ coreutils findutils diffutils jq gnugrep which ];
|
||||||
|
in {
|
||||||
|
options.services.restic = {
|
||||||
|
enable = lib.mkEnableOption "restic";
|
||||||
|
|
||||||
|
backups = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Periodic backups to create with Restic.
|
||||||
|
'';
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: {
|
||||||
|
options = {
|
||||||
|
package = lib.mkPackageOption pkgs "restic" { };
|
||||||
|
|
||||||
|
ssh-package = lib.mkPackageOption pkgs "openssh" { };
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
A file containing the repository password.
|
||||||
|
'';
|
||||||
|
example = "/etc/nixos/restic-password";
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
A file containing the credentials to access the repository, in the
|
||||||
|
format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`.
|
||||||
|
See <https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html>
|
||||||
|
for the specific credentials you will need for your backend.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
rcloneOptions = lib.mkOption {
|
||||||
|
type = with lib.types; attrsOf (oneOf [ str bool ]);
|
||||||
|
default = { };
|
||||||
|
apply =
|
||||||
|
lib.mapAttrs' (opt: v: lib.nameValuePair (fmtRcloneOpt opt) v);
|
||||||
|
description = ''
|
||||||
|
Options to pass to rclone to control its behavior. See
|
||||||
|
<https://rclone.org/docs/#options> for available options. When specifying
|
||||||
|
option names, strip the leading `--`. To set a flag such as
|
||||||
|
`--drive-use-trash`, which does not take a value, set the value to the
|
||||||
|
Boolean `true`.
|
||||||
|
'';
|
||||||
|
example = {
|
||||||
|
bwlimit = "10M";
|
||||||
|
drive-use-trash = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
inhibitsSleep = lib.mkOption {
|
||||||
|
default = false;
|
||||||
|
type = lib.types.bool;
|
||||||
|
example = true;
|
||||||
|
description = ''
|
||||||
|
Prevents the system from sleeping while backing up. This uses systemd-inhibit
|
||||||
|
to block system idling so you may need to enable polkitd with
|
||||||
|
{option}`security.polkit.enable`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
repository = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Repository to backup to. This should be in the form of a backend specification as
|
||||||
|
detailed here
|
||||||
|
<https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html>.
|
||||||
|
|
||||||
|
If your using the rclone backend, you can configure your remotes with
|
||||||
|
{option}`programs.rclone.remotes` then use them in your backend specification.
|
||||||
|
'';
|
||||||
|
example = "sftp:backup@192.168.1.100:/backups/${name}";
|
||||||
|
};
|
||||||
|
|
||||||
|
repositoryFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to a file containing the repository location to backup to. This should be
|
||||||
|
in the same form as the {option}`repository` option.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
paths = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Paths to back up, alongside those defined by the {option}`dynamicFilesFrom`
|
||||||
|
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" "/home/user/backup" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
exclude = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Patterns to exclude when backing up. See
|
||||||
|
<https://restic.readthedocs.io/en/stable/040_backup.html#excluding-files> for
|
||||||
|
details on syntax.
|
||||||
|
'';
|
||||||
|
example = [ "/var/cache" "/home/*/.cache" ".git" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
timerConfig = lib.mkOption {
|
||||||
|
type = lib.types.nullOr unitType;
|
||||||
|
default = {
|
||||||
|
OnCalendar = "daily";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
description = ''
|
||||||
|
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";
|
||||||
|
RandomizedDelaySec = "5h";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extraBackupArgs = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Extra arguments passed to restic backup.
|
||||||
|
'';
|
||||||
|
example =
|
||||||
|
[ "--cleanup-cache" "--exclude-file=/etc/nixos/restic-ignore" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
extraOptions = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Extra extended options to be passed to the restic `-o` flag. See the restic
|
||||||
|
documentation for more details.
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Create the repository if it does not already exist.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
pruneOpts = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
A list of policy options for 'restic forget --prune', to automatically
|
||||||
|
prune old snapshots. See
|
||||||
|
<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
|
||||||
|
that in mind when constructing the --keep-\* options.
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"--keep-daily 7"
|
||||||
|
"--keep-weekly 5"
|
||||||
|
"--keep-monthly 12"
|
||||||
|
"--keep-yearly 75"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
runCheck = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = lib.length config.checkOpts > 0
|
||||||
|
|| lib.length config.pruneOpts > 0;
|
||||||
|
defaultText = lib.literalExpression
|
||||||
|
"lib.length config.checkOpts > 0 || lib.length config.pruneOpts > 0";
|
||||||
|
description =
|
||||||
|
"Whether to run 'restic check' with the provided `checkOpts` options.";
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkOpts = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
A list of options for 'restic check'.
|
||||||
|
'';
|
||||||
|
example = [ "--with-cache" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicFilesFrom = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
A script that produces a list of files to back up. The results of
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
backupPrepareCommand = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
A script that must run before starting the backup process.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
backupCleanupCommand = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
A script that must run after finishing the backup process.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
createWrapper = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to generate and add a script to the system path, that has the
|
||||||
|
same environment variables set as the systemd service. This can be used
|
||||||
|
to e.g. mount snapshots or perform other opterations, without having to
|
||||||
|
manually specify most options.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
progressFps = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr numbers.nonnegative;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Controls the frequency of progress reporting.
|
||||||
|
'';
|
||||||
|
example = 0.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
localbackup = {
|
||||||
|
paths = [ "/home" ];
|
||||||
|
exclude = [ "/home/*/.cache" ];
|
||||||
|
repository = "/mnt/backup-hdd";
|
||||||
|
passwordFile = "/etc/nixos/secrets/restic-password";
|
||||||
|
initialize = true;
|
||||||
|
};
|
||||||
|
remotebackup = {
|
||||||
|
paths = [ "/home" ];
|
||||||
|
repository = "sftp:backup@host:/backups/home";
|
||||||
|
passwordFile = "/etc/nixos/secrets/restic-password";
|
||||||
|
extraOptions = [
|
||||||
|
"sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'"
|
||||||
|
];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "00:05";
|
||||||
|
RandomizedDelaySec = "5h";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = lib.mapAttrsToList (n: v: {
|
||||||
|
assertion = lib.xor (v.repository == null) (v.repositoryFile == null);
|
||||||
|
message =
|
||||||
|
"services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
|
||||||
|
}) cfg.backups;
|
||||||
|
|
||||||
|
systemd.user.services = lib.mapAttrs' (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' (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}";
|
||||||
|
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}";
|
||||||
|
# https://github.com/koalaman/shellcheck/issues/1986
|
||||||
|
excludeShellChecks = [ "SC2034" ];
|
||||||
|
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.hm.maintainers.jess ];
|
||||||
|
}
|
|
@ -543,6 +543,7 @@ in import nmtSrc {
|
||||||
./modules/services/recoll
|
./modules/services/recoll
|
||||||
./modules/services/redshift-gammastep
|
./modules/services/redshift-gammastep
|
||||||
./modules/services/remmina
|
./modules/services/remmina
|
||||||
|
./modules/services/restic
|
||||||
./modules/services/screen-locker
|
./modules/services/screen-locker
|
||||||
./modules/services/signaturepdf
|
./modules/services/signaturepdf
|
||||||
./modules/services/snixembed
|
./modules/services/snixembed
|
||||||
|
|
|
@ -16,6 +16,7 @@ let
|
||||||
nh = runTest ./standalone/nh.nix;
|
nh = runTest ./standalone/nh.nix;
|
||||||
nixos-basics = runTest ./nixos/basics.nix;
|
nixos-basics = runTest ./nixos/basics.nix;
|
||||||
rclone = runTest ./standalone/rclone;
|
rclone = runTest ./standalone/rclone;
|
||||||
|
restic = runTest ./standalone/restic.nix;
|
||||||
standalone-flake-basics = runTest ./standalone/flake-basics.nix;
|
standalone-flake-basics = runTest ./standalone/flake-basics.nix;
|
||||||
standalone-standard-basics = runTest ./standalone/standard-basics.nix;
|
standalone-standard-basics = runTest ./standalone/standard-basics.nix;
|
||||||
};
|
};
|
||||||
|
|
111
tests/integration/standalone/restic-home.nix
Normal file
111
tests/integration/standalone/restic-home.nix
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
passwordFile = "/home/alice/password";
|
||||||
|
paths = [ "/home/alice/files" ];
|
||||||
|
exclude = [ "*exclude*" ];
|
||||||
|
in {
|
||||||
|
home.username = "alice";
|
||||||
|
home.homeDirectory = "/home/alice";
|
||||||
|
|
||||||
|
home.stateVersion = "24.05"; # Please read the comment before changing.
|
||||||
|
|
||||||
|
# Let Home Manager install and manage itself.
|
||||||
|
programs.home-manager.enable = true;
|
||||||
|
systemd.user.startServices = false;
|
||||||
|
|
||||||
|
programs.rclone = {
|
||||||
|
enable = true;
|
||||||
|
remotes = {
|
||||||
|
alices-computer.config = {
|
||||||
|
type = "local";
|
||||||
|
one_file_system = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.restic = {
|
||||||
|
enable = true;
|
||||||
|
backups = {
|
||||||
|
init = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/backup";
|
||||||
|
};
|
||||||
|
|
||||||
|
noinit = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = false;
|
||||||
|
repository = "/home/alice/repos/noinit";
|
||||||
|
};
|
||||||
|
|
||||||
|
basic = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/basic";
|
||||||
|
};
|
||||||
|
|
||||||
|
repo-file = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repositoryFile =
|
||||||
|
pkgs.writeText "repositoryFile" "/home/alice/repos/repo-file";
|
||||||
|
};
|
||||||
|
|
||||||
|
rclone = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "rclone:alices-computer:/home/alice/repos/rclone";
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamic-paths = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/dynamic-paths";
|
||||||
|
dynamicFilesFrom = ''
|
||||||
|
find /home/alice/dyn-files -type f ! -name "*secret*"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
inhibits-sleep = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
inhibitsSleep = true;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/inhibits-sleep";
|
||||||
|
};
|
||||||
|
|
||||||
|
pre-post-jobs = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/pre-post-jobs";
|
||||||
|
backupPrepareCommand = ''
|
||||||
|
echo "Preparing Backup..."
|
||||||
|
echo "Notifying Alice..."
|
||||||
|
echo "Ready!"
|
||||||
|
'';
|
||||||
|
backupCleanupCommand = ''
|
||||||
|
echo "Finishing Backup..."
|
||||||
|
echo "Mailing alice the results..."
|
||||||
|
echo "Done."
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
prune-me = {
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
repository = "/home/alice/repos/prune-me";
|
||||||
|
};
|
||||||
|
|
||||||
|
prune-only = {
|
||||||
|
inherit passwordFile;
|
||||||
|
repository = "/home/alice/repos/prune-me";
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-yearly 4"
|
||||||
|
"--keep-monthly 3"
|
||||||
|
"--keep-weekly 2"
|
||||||
|
"--keep-daily 2"
|
||||||
|
"--keep-hourly 3"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
265
tests/integration/standalone/restic.nix
Normal file
265
tests/integration/standalone/restic.nix
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
testDir = pkgs.runCommand "test-files-to-backup" { } ''
|
||||||
|
mkdir $out
|
||||||
|
echo some_file > $out/some_file
|
||||||
|
echo some_other_file > $out/some_other_file
|
||||||
|
mkdir $out/a_dir
|
||||||
|
echo a_file > $out/a_dir/a_file
|
||||||
|
echo a_file_2 > $out/a_dir/a_file_2
|
||||||
|
echo alices-secret-diary > $out/a_dir/excluded_file_1
|
||||||
|
echo alices-bank-details > $out/excluded_file_2
|
||||||
|
'';
|
||||||
|
|
||||||
|
dynDir = testDir.overrideAttrs (final: prev: {
|
||||||
|
buildCommand = prev.buildCommand + ''
|
||||||
|
echo more secret data > $out/top-secret
|
||||||
|
echo shhhh > $out/top-secret-v2
|
||||||
|
echo this isnt secret > $out/metadata
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
in {
|
||||||
|
name = "restic";
|
||||||
|
|
||||||
|
nodes.machine = { ... }: {
|
||||||
|
imports = [ "${pkgs.path}/nixos/modules/installer/cd-dvd/channel.nix" ];
|
||||||
|
virtualisation.memorySize = 2048;
|
||||||
|
users.users.alice = {
|
||||||
|
isNormalUser = true;
|
||||||
|
description = "Alice Foobar";
|
||||||
|
password = "foobar";
|
||||||
|
uid = 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
security.polkit.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
machine.wait_for_unit("network.target")
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_unit("dbus.socket")
|
||||||
|
|
||||||
|
home_manager = "${../../..}"
|
||||||
|
|
||||||
|
def login_as_alice():
|
||||||
|
machine.wait_until_tty_matches("1", "login: ")
|
||||||
|
machine.send_chars("alice\n")
|
||||||
|
machine.wait_until_tty_matches("1", "Password: ")
|
||||||
|
machine.send_chars("foobar\n")
|
||||||
|
machine.wait_until_tty_matches("1", "alice\\@machine")
|
||||||
|
|
||||||
|
def logout_alice():
|
||||||
|
machine.send_chars("exit\n")
|
||||||
|
|
||||||
|
def alice_cmd(cmd):
|
||||||
|
return f"su -l alice --shell /bin/sh -c $'export XDG_RUNTIME_DIR=/run/user/$UID ; {cmd}'"
|
||||||
|
|
||||||
|
def succeed_as_alice(*cmds):
|
||||||
|
return machine.succeed(*map(alice_cmd,cmds))
|
||||||
|
|
||||||
|
def fail_as_alice(*cmds):
|
||||||
|
return machine.fail(*map(alice_cmd,cmds))
|
||||||
|
|
||||||
|
def systemctl_succeed_as_alice(cmd):
|
||||||
|
status, out = machine.systemctl(cmd, "alice")
|
||||||
|
assert status == 0, f"failed to run systemctl {cmd}"
|
||||||
|
return out
|
||||||
|
|
||||||
|
def systemctl_fail_as_alice(cmd):
|
||||||
|
status, out = machine.systemctl(cmd, "alice")
|
||||||
|
assert status != 0, \
|
||||||
|
f"Successfully finished with exit-code {status}, systemctl {cmd} when its expected to fail"
|
||||||
|
return out
|
||||||
|
|
||||||
|
def assert_list(cmd, expected_list, actual):
|
||||||
|
assert all([x in actual for x in expected_list]), \
|
||||||
|
f"""Expected {cmd} to contain \
|
||||||
|
[{" and ".join([x for x in expected_list if x not in actual])}], but got {actual}"""
|
||||||
|
|
||||||
|
def spin_on(unit):
|
||||||
|
while True:
|
||||||
|
info = machine.get_unit_info(unit, "alice")
|
||||||
|
if info["ActiveState"] == "inactive":
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create a persistent login so that Alice has a systemd session.
|
||||||
|
login_as_alice()
|
||||||
|
|
||||||
|
# Set up a home-manager channel.
|
||||||
|
succeed_as_alice(" ; ".join([
|
||||||
|
"mkdir -p /home/alice/.nix-defexpr/channels",
|
||||||
|
f"ln -s {home_manager} /home/alice/.nix-defexpr/channels/home-manager"
|
||||||
|
]))
|
||||||
|
|
||||||
|
with subtest("Home Manager installation"):
|
||||||
|
succeed_as_alice("nix-shell \"<home-manager>\" -A install")
|
||||||
|
|
||||||
|
succeed_as_alice("cp ${
|
||||||
|
./restic-home.nix
|
||||||
|
} /home/alice/.config/home-manager/home.nix")
|
||||||
|
|
||||||
|
succeed_as_alice("cp -rT ${testDir} /home/alice/files")
|
||||||
|
succeed_as_alice("cp -rT ${dynDir} /home/alice/dyn-files")
|
||||||
|
succeed_as_alice("echo password123 > /home/alice/password")
|
||||||
|
|
||||||
|
succeed_as_alice("home-manager switch")
|
||||||
|
|
||||||
|
expectedIncluded = [
|
||||||
|
"/home",
|
||||||
|
"/home/alice",
|
||||||
|
"/home/alice/files",
|
||||||
|
"/home/alice/files/a_dir",
|
||||||
|
"/home/alice/files/a_dir/a_file",
|
||||||
|
"/home/alice/files/a_dir/a_file_2",
|
||||||
|
"/home/alice/files/some_file",
|
||||||
|
"/home/alice/files/some_other_file"
|
||||||
|
]
|
||||||
|
|
||||||
|
with subtest("Basic backup"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-basic.service")
|
||||||
|
actual = succeed_as_alice("restic-basic ls latest")
|
||||||
|
assert_list("restic-basic ls latest", expectedIncluded, actual)
|
||||||
|
|
||||||
|
assert "exclude" not in actual, \
|
||||||
|
f"Paths containing \"*exclude*\" got backed up incorrectly. output: {actual}"
|
||||||
|
|
||||||
|
with subtest("Basic restore"):
|
||||||
|
succeed_as_alice("restic-basic restore latest --target restore/basic")
|
||||||
|
actual = fail_as_alice("diff -urNa restore/basic/home/alice/files files")
|
||||||
|
expected1 = "alices-secret-diary"
|
||||||
|
expected2 = "alices-bank-details"
|
||||||
|
assert expected1 in actual and expected2 in actual, \
|
||||||
|
f"expected diff -ur restore/basic/home/alice/files files to contain \
|
||||||
|
{expected1} and {expected2}, but got {actual}"
|
||||||
|
|
||||||
|
with subtest("Fails to start with an un-initialized repo"):
|
||||||
|
systemctl_fail_as_alice("start restic-backups-noinit.service")
|
||||||
|
|
||||||
|
with subtest("Start with an initialized repo"):
|
||||||
|
succeed_as_alice("restic-noinit init")
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-noinit.service")
|
||||||
|
|
||||||
|
with subtest("Using a repositoryFile"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-repo-file.service")
|
||||||
|
actual = succeed_as_alice("restic-repo-file ls latest")
|
||||||
|
assert_list("restic-repo-file ls latest", expectedIncluded, actual)
|
||||||
|
|
||||||
|
assert "exclude" not in actual, \
|
||||||
|
f"Paths containing \"*exclude*\" got backed up incorrectly. output: {actual}"
|
||||||
|
|
||||||
|
with subtest("Using an rclone backend"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-rclone.service")
|
||||||
|
actual = succeed_as_alice("restic-rclone ls latest")
|
||||||
|
assert_list("restic-rclone ls latest", expectedIncluded, actual)
|
||||||
|
|
||||||
|
assert "exclude" not in actual, \
|
||||||
|
f"Paths containing \"*exclude*\" got backed up incorrectly. output: {actual}"
|
||||||
|
|
||||||
|
with subtest("Backup with prepare and cleanup commands"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-pre-post-jobs.service")
|
||||||
|
spin_on("restic-backups-pre-post-jobs.service")
|
||||||
|
actual = succeed_as_alice("journalctl --no-pager --user -u restic-backups-pre-post-jobs.service")
|
||||||
|
|
||||||
|
expected_list = [
|
||||||
|
"Preparing Backup...",
|
||||||
|
"Notifying Alice...",
|
||||||
|
"Ready!",
|
||||||
|
"Finishing Backup...",
|
||||||
|
"Mailing alice the results...",
|
||||||
|
"Done."
|
||||||
|
]
|
||||||
|
assert_list("journalctl --no-pager --user -u restic-backups-pre-post-jobs.service", \
|
||||||
|
expected_list, \
|
||||||
|
actual)
|
||||||
|
|
||||||
|
expectedIncludedDyn = expectedIncluded + [
|
||||||
|
"/home/alice/dyn-files",
|
||||||
|
"/home/alice/dyn-files/a_dir",
|
||||||
|
"/home/alice/dyn-files/a_dir/a_file",
|
||||||
|
"/home/alice/dyn-files/a_dir/a_file_2",
|
||||||
|
"/home/alice/dyn-files/metadata",
|
||||||
|
"/home/alice/dyn-files/some_file",
|
||||||
|
"/home/alice/dyn-files/some_other_file"
|
||||||
|
]
|
||||||
|
|
||||||
|
with subtest("Dynamic paths"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-dynamic-paths.service")
|
||||||
|
actual = succeed_as_alice("restic-dynamic-paths ls latest")
|
||||||
|
assert_list("restic-dynamic-paths ls latest", expectedIncludedDyn, actual)
|
||||||
|
|
||||||
|
assert "secret" not in actual, \
|
||||||
|
f"Paths containing \"*secret*\" got backed up incorrectly. output: {actual}"
|
||||||
|
|
||||||
|
with subtest("Inhibit Sleep"):
|
||||||
|
systemctl_succeed_as_alice("start --no-block restic-backups-inhibits-sleep.service")
|
||||||
|
machine.wait_until_succeeds("systemd-inhibit --no-legend --no-pager | grep -q restic", 30)
|
||||||
|
|
||||||
|
spin_on("restic-backups-inhibits-sleep.service")
|
||||||
|
|
||||||
|
actual = succeed_as_alice("restic-inhibits-sleep ls latest")
|
||||||
|
assert_list("restic-inhibits-sleep ls latest", expectedIncluded, actual)
|
||||||
|
|
||||||
|
assert "exclude" not in actual, \
|
||||||
|
f"Paths containing \"*exclude*\" got backed up incorrectly. output: {actual}"
|
||||||
|
|
||||||
|
with subtest("Create a few backups at different times"):
|
||||||
|
snapshot_count = 0
|
||||||
|
|
||||||
|
def make_backup(time):
|
||||||
|
global snapshot_count
|
||||||
|
machine.succeed(f"timedatectl set-time '{time}'")
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-prune-me.service")
|
||||||
|
snapshot_count += 1
|
||||||
|
actual = \
|
||||||
|
succeed_as_alice("restic-prune-me snapshots --json | ${
|
||||||
|
lib.getExe pkgs.jq
|
||||||
|
} length")
|
||||||
|
assert int(actual) == snapshot_count, \
|
||||||
|
f"Expected a snapshot count of {snapshot_count} but got {actual}"
|
||||||
|
|
||||||
|
# a year with 3 snapshots
|
||||||
|
make_backup("1970-01-01 12:34")
|
||||||
|
make_backup("1970-06-01 12:34")
|
||||||
|
make_backup("1970-12-01 12:34")
|
||||||
|
# a year with 2
|
||||||
|
make_backup("1971-02-11 12:34")
|
||||||
|
make_backup("1971-03-10 12:34")
|
||||||
|
# a year with 3
|
||||||
|
make_backup("1972-01-02 12:34")
|
||||||
|
make_backup("1972-03-01 12:34")
|
||||||
|
make_backup("1972-04-02 12:34")
|
||||||
|
# a month with 2
|
||||||
|
make_backup("1973-04-01 12:34")
|
||||||
|
make_backup("1973-04-02 12:34")
|
||||||
|
# a week with 3
|
||||||
|
make_backup("1973-06-4 12:34")
|
||||||
|
make_backup("1973-06-6 12:56")
|
||||||
|
make_backup("1973-06-9 12:56")
|
||||||
|
# a week with 2
|
||||||
|
make_backup("1973-06-12 12:56")
|
||||||
|
make_backup("1973-06-13 12:56")
|
||||||
|
# a day with 3
|
||||||
|
make_backup("1973-06-18 01:00")
|
||||||
|
make_backup("1973-06-18 12:25")
|
||||||
|
make_backup("1973-06-18 23:01")
|
||||||
|
# an hour with 3
|
||||||
|
make_backup("1973-06-19 21:11")
|
||||||
|
make_backup("1973-06-19 21:31")
|
||||||
|
make_backup("1973-06-19 21:41")
|
||||||
|
# an hour with 2
|
||||||
|
make_backup("1973-06-19 23:10")
|
||||||
|
make_backup("1973-06-19 23:30")
|
||||||
|
|
||||||
|
with subtest("Prune snapshots"):
|
||||||
|
systemctl_succeed_as_alice("start restic-backups-prune-only.service")
|
||||||
|
actual = \
|
||||||
|
succeed_as_alice("restic-prune-only snapshots --json | ${
|
||||||
|
lib.getExe pkgs.jq
|
||||||
|
} length")
|
||||||
|
assert int(actual) == 8, \
|
||||||
|
f"Expected a snapshot count of 8 but got {actual}"
|
||||||
|
|
||||||
|
logout_alice()
|
||||||
|
'';
|
||||||
|
}
|
78
tests/modules/services/restic/backup-configs.nix
Normal file
78
tests/modules/services/restic/backup-configs.nix
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
repository = "/root/restic-backup";
|
||||||
|
passwordFile = "/path/to/password";
|
||||||
|
paths = [ "/etc" ];
|
||||||
|
exclude = [ "/etc/*.cache" "/opt/excluded_file_*" ];
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-daily 2"
|
||||||
|
"--keep-weekly 1"
|
||||||
|
"--keep-monthly 1"
|
||||||
|
"--keep-yearly 99"
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
local-backup = {
|
||||||
|
repository = "/mnt/backup-hdd";
|
||||||
|
inherit passwordFile paths exclude;
|
||||||
|
initialize = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
remote-backup = {
|
||||||
|
repository = "sftp:backup@host:/backups/home";
|
||||||
|
inherit passwordFile paths;
|
||||||
|
extraOptions = [
|
||||||
|
"sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'"
|
||||||
|
];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "00:05";
|
||||||
|
RandomizedDelaySec = "5h";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
no-timer = {
|
||||||
|
inherit passwordFile paths exclude pruneOpts repository;
|
||||||
|
backupPrepareCommand = "dummy-prepare";
|
||||||
|
backupCleanupCommand = "dummy-cleanup";
|
||||||
|
initialize = true;
|
||||||
|
timerConfig = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
repo-file = {
|
||||||
|
inherit passwordFile exclude pruneOpts paths;
|
||||||
|
initialize = true;
|
||||||
|
repositoryFile = pkgs.writeText "repositoryFile" repository;
|
||||||
|
dynamicFilesFrom = "find alices files";
|
||||||
|
};
|
||||||
|
|
||||||
|
inhibit-sleep = {
|
||||||
|
inherit passwordFile paths exclude pruneOpts repository;
|
||||||
|
initialize = true;
|
||||||
|
inhibitsSleep = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
noinit = {
|
||||||
|
inherit passwordFile exclude pruneOpts paths repository;
|
||||||
|
initialize = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
rclone = {
|
||||||
|
inherit passwordFile paths exclude pruneOpts;
|
||||||
|
initialize = true;
|
||||||
|
repository = "rclone:local:/root/restic-rclone-backup";
|
||||||
|
};
|
||||||
|
|
||||||
|
prune = {
|
||||||
|
inherit passwordFile repository;
|
||||||
|
pruneOpts = [ "--keep-last 1" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
custom-package = {
|
||||||
|
inherit passwordFile paths;
|
||||||
|
repository = "some-fake-repository";
|
||||||
|
package = pkgs.writeShellScriptBin "my-cool-restic" ''
|
||||||
|
echo "$@" >> /root/fake-restic.log;
|
||||||
|
'';
|
||||||
|
pruneOpts = [ "--keep-last 1" ];
|
||||||
|
checkOpts = [ "--some-check-option" ];
|
||||||
|
};
|
||||||
|
}
|
1
tests/modules/services/restic/default.nix
Normal file
1
tests/modules/services/restic/default.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{ restic-unit-files = ./unit-files.nix; }
|
126
tests/modules/services/restic/unit-files.nix
Normal file
126
tests/modules/services/restic/unit-files.nix
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
let backups = import ./backup-configs.nix { inherit pkgs; };
|
||||||
|
in {
|
||||||
|
services.restic = {
|
||||||
|
enable = true;
|
||||||
|
inherit backups;
|
||||||
|
};
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
backups=(
|
||||||
|
${lib.concatLines (lib.attrNames backups)}
|
||||||
|
)
|
||||||
|
|
||||||
|
serviceFiles=./home-files/.config/systemd/user
|
||||||
|
defaultPruneOpts=("forget" "prune" "keep-daily" "2" "keep-weekly" "1" "keep-monthly" "1" "keep-yearly" "99")
|
||||||
|
inhibitString=("systemd-inhibit" "mode" "block" "who" "restic" "what" "sleep" "why" "Scheduled backup inhibit-sleep")
|
||||||
|
sftpStrings=("sftp.command=" "ssh" "backup@host" "/etc/nixos/secrets/backup-private-key" "sftp")
|
||||||
|
|
||||||
|
# General prelim tests
|
||||||
|
for backup in ''${backups[@]};
|
||||||
|
do
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
|
||||||
|
# these two are the only ones without pruneOpts
|
||||||
|
if [ "$backup" != "local-backup" ] && [ "$backup" != "remote-backup" ]; then
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*unlock"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*forget.*--prune"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*check"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$backup" != "prune" ]; then
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*--exclude-file"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*--files-from"
|
||||||
|
fi
|
||||||
|
|
||||||
|
assertFileExists $serviceFile
|
||||||
|
assertFileRegex $serviceFile "CacheDirectory=restic-backups-$backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=.*PATH=.*openssh.*/bin"
|
||||||
|
assertFileRegex $serviceFile "ExecStartPre"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*restic"
|
||||||
|
assertFileRegex $serviceFile "ExecStopPost"
|
||||||
|
assertFileRegex $serviceFile "Description=Restic backup service"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=local-backup
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=/mnt/backup-hdd"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
|
||||||
|
backup=remote-backup
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=sftp:backup@host:/backups/home"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*"
|
||||||
|
for part in ''${sftpStrings[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=no-timer
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=/root/restic-backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$defaultPruneOpts"
|
||||||
|
for part in ''${defaultPruneOpts[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
# TODO: assertFileNotExists
|
||||||
|
timerUnit=$serviceFiles/timers.target.wants/restic-backups-"$backup".timer
|
||||||
|
if [ -f $(_abs $timerUnit) ]; then
|
||||||
|
fail "restic backup config: \"$backup\" made a timer unit: $timerUnit when \`timerConfig = null\`"
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup=repo-file
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY_FILE=.*repositoryFile"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$defaultPruneOpts"
|
||||||
|
for part in ''${defaultPruneOpts[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=inhibit-sleep
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=/root/restic-backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$defaultPruneOpts"
|
||||||
|
for part in ''${defaultPruneOpts[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
for part in ''${inhibitStrings[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=noinit
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=/root/restic-backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$defaultPruneOpts"
|
||||||
|
for part in ''${defaultPruneOpts[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=rclone
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=rclone:local:/root/restic-rclone-backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$defaultPruneOpts"
|
||||||
|
for part in ''${defaultPruneOpts[@]}; do
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*$part"
|
||||||
|
done
|
||||||
|
|
||||||
|
backup=prune
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=/root/restic-backup"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*--prune --keep-last 1"
|
||||||
|
|
||||||
|
backup=custom-package
|
||||||
|
serviceFile=$serviceFiles/restic-backups-"$backup".service
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_REPOSITORY=some-fake-repository"
|
||||||
|
assertFileRegex $serviceFile "Environment=RESTIC_PASSWORD_FILE"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*forget --prune --keep-last 1"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*my-cool-restic"
|
||||||
|
assertFileRegex $serviceFile "ExecStart=.*check --some-check-option"
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue