Merge pull request #204386 from robryk/resticassert

nixos/restic: small enhancements
This commit is contained in:
Nick Cao 2023-01-22 16:23:04 +08:00 committed by GitHub
commit 99f9998309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 16 deletions

View File

@ -126,6 +126,21 @@ in
]; ];
}; };
exclude = mkOption {
type = types.listOf types.str;
default = [ ];
description = lib.mdDoc ''
Patterns to exclude when backing up. See
https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for
details on syntax.
'';
example = [
"/var/cache"
"/home/*/.cache"
".git"
];
};
timerConfig = mkOption { timerConfig = mkOption {
type = types.attrsOf unitOption; type = types.attrsOf unitOption;
default = { default = {
@ -249,6 +264,7 @@ in
example = { example = {
localbackup = { localbackup = {
paths = [ "/home" ]; paths = [ "/home" ];
exclude = [ "/home/*/.cache" ];
repository = "/mnt/backup-hdd"; repository = "/mnt/backup-hdd";
passwordFile = "/etc/nixos/secrets/restic-password"; passwordFile = "/etc/nixos/secrets/restic-password";
initialize = true; initialize = true;
@ -270,12 +286,17 @@ in
config = { config = {
warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups); warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
assertions = mapAttrsToList (n: v: {
assertion = (v.repository == null) != (v.repositoryFile == null);
message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
}) config.services.restic.backups;
systemd.services = systemd.services =
mapAttrs' mapAttrs'
(name: backup: (name: backup:
let let
extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions; extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
resticCmd = "${backup.package}/bin/restic${extraOptions}"; resticCmd = "${backup.package}/bin/restic${extraOptions}";
excludeFlags = if (backup.exclude != []) then ["--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}"] else [];
filesFromTmpFile = "/run/restic-backups-${name}/includes"; filesFromTmpFile = "/run/restic-backups-${name}/includes";
backupPaths = backupPaths =
if (backup.dynamicFilesFrom == null) if (backup.dynamicFilesFrom == null)
@ -311,7 +332,7 @@ in
restartIfChanged = false; restartIfChanged = false;
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ]) ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
++ pruneCmd; ++ pruneCmd;
User = backup.user; User = backup.user;
RuntimeDirectory = "restic-backups-${name}"; RuntimeDirectory = "restic-backups-${name}";

View File

@ -7,17 +7,27 @@ import ./make-test-python.nix (
rcloneRepository = "rclone:local:/tmp/restic-rclone-backup"; rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
backupPrepareCommand = '' backupPrepareCommand = ''
touch /opt/backupPrepareCommand touch /tmp/backupPrepareCommand
test ! -e /opt/backupCleanupCommand test ! -e /tmp/backupCleanupCommand
''; '';
backupCleanupCommand = '' backupCleanupCommand = ''
rm /opt/backupPrepareCommand rm /tmp/backupPrepareCommand
touch /opt/backupCleanupCommand touch /tmp/backupCleanupCommand
''; '';
testDir = pkgs.stdenvNoCC.mkDerivation {
name = "test-files-to-backup";
unpackPhase = "true";
installPhase = ''
mkdir $out
touch $out/some_file
'';
};
passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}"; passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
paths = [ "/opt" ]; paths = [ "/opt" ];
exclude = [ "/opt/excluded_file_*" ];
pruneOpts = [ pruneOpts = [
"--keep-daily 2" "--keep-daily 2"
"--keep-weekly 1" "--keep-weekly 1"
@ -38,17 +48,17 @@ import ./make-test-python.nix (
{ {
services.restic.backups = { services.restic.backups = {
remotebackup = { remotebackup = {
inherit passwordFile paths pruneOpts backupPrepareCommand backupCleanupCommand; inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand;
repository = remoteRepository; repository = remoteRepository;
initialize = true; initialize = true;
}; };
remote-from-file-backup = { remote-from-file-backup = {
inherit passwordFile paths pruneOpts; inherit passwordFile paths exclude pruneOpts;
initialize = true; initialize = true;
repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository; repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
}; };
rclonebackup = { rclonebackup = {
inherit passwordFile paths pruneOpts; inherit passwordFile paths exclude pruneOpts;
initialize = true; initialize = true;
repository = rcloneRepository; repository = rcloneRepository;
rcloneConfig = { rcloneConfig = {
@ -94,16 +104,21 @@ import ./make-test-python.nix (
) )
server.succeed( server.succeed(
# set up # set up
"mkdir -p /opt", "cp -rT ${testDir} /opt",
"touch /opt/some_file", "touch /opt/excluded_file_1 /opt/excluded_file_2",
"mkdir -p /tmp/restic-rclone-backup", "mkdir -p /tmp/restic-rclone-backup",
# test that remotebackup runs custom commands and produces a snapshot # test that remotebackup runs custom commands and produces a snapshot
"timedatectl set-time '2016-12-13 13:45'", "timedatectl set-time '2016-12-13 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
'${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
# test that restoring that snapshot produces the same directory
"mkdir /tmp/restore-1",
"${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-1",
"diff -ru ${testDir} /tmp/restore-1/opt",
# test that remote-from-file-backup produces a snapshot # test that remote-from-file-backup produces a snapshot
"systemctl start restic-backups-remote-from-file-backup.service", "systemctl start restic-backups-remote-from-file-backup.service",
'${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"', '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
@ -120,27 +135,27 @@ import ./make-test-python.nix (
# test that we can create four snapshots in remotebackup and rclonebackup # test that we can create four snapshots in remotebackup and rclonebackup
"timedatectl set-time '2017-12-13 13:45'", "timedatectl set-time '2017-12-13 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
"systemctl start restic-backups-rclonebackup.service", "systemctl start restic-backups-rclonebackup.service",
"timedatectl set-time '2018-12-13 13:45'", "timedatectl set-time '2018-12-13 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
"systemctl start restic-backups-rclonebackup.service", "systemctl start restic-backups-rclonebackup.service",
"timedatectl set-time '2018-12-14 13:45'", "timedatectl set-time '2018-12-14 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
"systemctl start restic-backups-rclonebackup.service", "systemctl start restic-backups-rclonebackup.service",
"timedatectl set-time '2018-12-15 13:45'", "timedatectl set-time '2018-12-15 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
"systemctl start restic-backups-rclonebackup.service", "systemctl start restic-backups-rclonebackup.service",
"timedatectl set-time '2018-12-16 13:45'", "timedatectl set-time '2018-12-16 13:45'",
"systemctl start restic-backups-remotebackup.service", "systemctl start restic-backups-remotebackup.service",
"rm /opt/backupCleanupCommand", "rm /tmp/backupCleanupCommand",
"systemctl start restic-backups-rclonebackup.service", "systemctl start restic-backups-rclonebackup.service",
'${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"', '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',