From 166d5cc8514427125c62b6247670d7439656ced6 Mon Sep 17 00:00:00 2001 From: Guillaume Girol Date: Sun, 25 Apr 2021 12:00:00 +0000 Subject: [PATCH 1/3] nixos/duplicity: format --- nixos/modules/services/backup/duplicity.nix | 37 +++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix index a8d564248623..7bf81a938f10 100644 --- a/nixos/modules/services/backup/duplicity.nix +++ b/nixos/modules/services/backup/duplicity.nix @@ -1,16 +1,17 @@ -{ config, lib, pkgs, ...}: +{ config, lib, pkgs, ... }: with lib; - let cfg = config.services.duplicity; stateDirectory = "/var/lib/duplicity"; - localTarget = if hasPrefix "file://" cfg.targetUrl + localTarget = + if hasPrefix "file://" cfg.targetUrl then removePrefix "file://" cfg.targetUrl else null; -in { +in +{ options.services.duplicity = { enable = mkEnableOption "backups with duplicity"; @@ -24,7 +25,7 @@ in { include = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; example = [ "/home" ]; description = '' List of paths to include into the backups. See the FILE SELECTION @@ -35,7 +36,7 @@ in { exclude = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; description = '' List of paths to exclude from backups. See the FILE SELECTION section in duplicity @@ -82,7 +83,7 @@ in { extraFlags = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; example = [ "--full-if-older-than" "1M" ]; description = '' Extra command-line flags passed to duplicity. See @@ -101,15 +102,17 @@ in { serviceConfig = { ExecStart = '' - ${pkgs.duplicity}/bin/duplicity ${escapeShellArgs ( - [ - cfg.root - cfg.targetUrl - "--archive-dir" stateDirectory - ] - ++ concatMap (p: [ "--include" p ]) cfg.include - ++ concatMap (p: [ "--exclude" p ]) cfg.exclude - ++ cfg.extraFlags)} + ${pkgs.duplicity}/bin/duplicity ${escapeShellArgs ( + [ + cfg.root + cfg.targetUrl + "--archive-dir" + stateDirectory + ] + ++ concatMap (p: [ "--include" p ]) cfg.include + ++ concatMap (p: [ "--exclude" p ]) cfg.exclude + ++ cfg.extraFlags + )} ''; PrivateTmp = true; ProtectSystem = "strict"; @@ -130,7 +133,7 @@ in { assertions = singleton { # Duplicity will fail if the last file selection option is an include. It # is not always possible to detect but this simple case can be caught. - assertion = cfg.include != [] -> cfg.exclude != [] || cfg.extraFlags != []; + assertion = cfg.include != [ ] -> cfg.exclude != [ ] || cfg.extraFlags != [ ]; message = '' Duplicity will fail if you only specify included paths ("Because the default is to include all files, the expression is redundant. Exiting From e67e79642e1091db5e964488696cfae01d787f3d Mon Sep 17 00:00:00 2001 From: Guillaume Girol Date: Sun, 25 Apr 2021 12:00:00 +0000 Subject: [PATCH 2/3] nixos/duplicity: add options to not keep backups forever Current module add backups forever, with no way to prune old ones. Add an option to remove backups after n full backups or after some amount of time. Also run duplicity cleanup to clean unused files in case some previous backup was improperly interrupted. --- nixos/modules/services/backup/duplicity.nix | 52 +++++++++++++++------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix index 7bf81a938f10..42d78376d83a 100644 --- a/nixos/modules/services/backup/duplicity.nix +++ b/nixos/modules/services/backup/duplicity.nix @@ -91,6 +91,28 @@ in 1. ''; }; + + cleanup = { + maxAge = mkOption { + type = types.nullOr types.str; + default = null; + example = "6M"; + description = '' + If non-null, delete all backup sets older than the given time. Old backup sets + will not be deleted if backup sets newer than time depend on them. + ''; + }; + maxFull = mkOption { + type = types.nullOr types.int; + default = null; + example = 2; + description = '' + If non-null, delete all backups sets that are older than the count:th last full + backup (in other words, keep the last count full backups and + associated incremental sets). + ''; + }; + }; }; config = mkIf cfg.enable { @@ -100,20 +122,24 @@ in environment.HOME = stateDirectory; - serviceConfig = { - ExecStart = '' - ${pkgs.duplicity}/bin/duplicity ${escapeShellArgs ( - [ - cfg.root - cfg.targetUrl - "--archive-dir" - stateDirectory - ] - ++ concatMap (p: [ "--include" p ]) cfg.include - ++ concatMap (p: [ "--exclude" p ]) cfg.exclude - ++ cfg.extraFlags - )} + script = + let + target = escapeShellArg cfg.targetUrl; + extra = escapeShellArgs ([ "--archive-dir" stateDirectory ] ++ cfg.extraFlags); + dup = "${pkgs.duplicity}/bin/duplicity"; + in + '' + set -x + ${dup} cleanup ${target} --force ${extra} + ${lib.optionalString (cfg.cleanup.maxAge != null) "${dup} remove-older-than ${lib.escapeShellArg cfg.cleanup.maxAge} ${target} --force ${extra}"} + ${lib.optionalString (cfg.cleanup.maxFull != null) "${dup} remove-all-but-n-full ${toString cfg.cleanup.maxFull} ${target} --force ${extra}"} + exec ${dup} incr ${lib.escapeShellArgs ( + [ cfg.root cfg.targetUrl ] + ++ concatMap (p: [ "--include" p ]) cfg.include + ++ concatMap (p: [ "--exclude" p ]) cfg.exclude + )} ${extra} ''; + serviceConfig = { PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = "read-only"; From 41c7fa448fbc81b8345d5cf2a8495fba253b879d Mon Sep 17 00:00:00 2001 From: Guillaume Girol Date: Thu, 13 May 2021 12:00:00 +0000 Subject: [PATCH 3/3] nixos/duplicity: add options to exercise all possible verbs except restore ;) --- nixos/modules/services/backup/duplicity.nix | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix index 42d78376d83a..1f6883ed02b7 100644 --- a/nixos/modules/services/backup/duplicity.nix +++ b/nixos/modules/services/backup/duplicity.nix @@ -84,7 +84,7 @@ in extraFlags = mkOption { type = types.listOf types.str; default = [ ]; - example = [ "--full-if-older-than" "1M" ]; + example = [ "--backend-retry-delay" "100" ]; description = '' Extra command-line flags passed to duplicity. See duplicity @@ -92,6 +92,20 @@ in ''; }; + fullIfOlderThan = mkOption { + type = types.str; + default = "never"; + example = "1M"; + description = '' + If "never" (the default) always do incremental + backups (the first backup will be a full backup, of course). If + "always" always do full backups. Otherwise, this + must be a string representing a duration. Full backups will be made + when the latest full backup is older than this duration. If this is not + the case, an incremental backup is performed. + ''; + }; + cleanup = { maxAge = mkOption { type = types.nullOr types.str; @@ -112,6 +126,16 @@ in associated incremental sets). ''; }; + maxIncr = mkOption { + type = types.nullOr types.int; + default = null; + example = 1; + description = '' + If non-null, delete incremental sets of all backups sets that are + older than the count:th last full backup (in other words, keep only + old full backups and not their increments). + ''; + }; }; }; @@ -133,10 +157,12 @@ in ${dup} cleanup ${target} --force ${extra} ${lib.optionalString (cfg.cleanup.maxAge != null) "${dup} remove-older-than ${lib.escapeShellArg cfg.cleanup.maxAge} ${target} --force ${extra}"} ${lib.optionalString (cfg.cleanup.maxFull != null) "${dup} remove-all-but-n-full ${toString cfg.cleanup.maxFull} ${target} --force ${extra}"} - exec ${dup} incr ${lib.escapeShellArgs ( + ${lib.optionalString (cfg.cleanup.maxIncr != null) "${dup} remove-all-incr-but-n-full ${toString cfg.cleanup.maxIncr} ${target} --force ${extra}"} + exec ${dup} ${if cfg.fullIfOlderThan == "always" then "full" else "incr"} ${lib.escapeShellArgs ( [ cfg.root cfg.targetUrl ] ++ concatMap (p: [ "--include" p ]) cfg.include ++ concatMap (p: [ "--exclude" p ]) cfg.exclude + ++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ]) )} ${extra} ''; serviceConfig = {