diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix index 2b4188492e5..66bfc7abdb3 100644 --- a/nixos/modules/services/backup/restic.nix +++ b/nixos/modules/services/backup/restic.nix @@ -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 { type = types.attrsOf unitOption; default = { @@ -249,6 +264,7 @@ in example = { localbackup = { paths = [ "/home" ]; + exclude = [ "/home/*/.cache" ]; repository = "/mnt/backup-hdd"; passwordFile = "/etc/nixos/secrets/restic-password"; initialize = true; @@ -280,6 +296,7 @@ in let extraOptions = concatMapStrings (arg: " -o ${arg}") backup.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"; backupPaths = if (backup.dynamicFilesFrom == null) @@ -315,7 +332,7 @@ in restartIfChanged = false; serviceConfig = { 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; User = backup.user; RuntimeDirectory = "restic-backups-${name}"; diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix index 0a4fc4c6455..42af0783863 100644 --- a/nixos/tests/restic.nix +++ b/nixos/tests/restic.nix @@ -27,6 +27,7 @@ import ./make-test-python.nix ( passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}"; paths = [ "/opt" ]; + exclude = [ "/opt/excluded_file_*" ]; pruneOpts = [ "--keep-daily 2" "--keep-weekly 1" @@ -47,17 +48,17 @@ import ./make-test-python.nix ( { services.restic.backups = { remotebackup = { - inherit passwordFile paths pruneOpts backupPrepareCommand backupCleanupCommand; + inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand; repository = remoteRepository; initialize = true; }; remote-from-file-backup = { - inherit passwordFile paths pruneOpts; + inherit passwordFile paths exclude pruneOpts; initialize = true; repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository; }; rclonebackup = { - inherit passwordFile paths pruneOpts; + inherit passwordFile paths exclude pruneOpts; initialize = true; repository = rcloneRepository; rcloneConfig = { @@ -104,6 +105,7 @@ import ./make-test-python.nix ( server.succeed( # set up "cp -rT ${testDir} /opt", + "touch /opt/excluded_file_1 /opt/excluded_file_2", "mkdir -p /tmp/restic-rclone-backup", # test that remotebackup runs custom commands and produces a snapshot