forked from mirrors/nixpkgs
Merge pull request #120622 from symphorien/duplicity-master
nixos/duplicity: enable to prevent backup from growing infinitely
This commit is contained in:
commit
0d5fa1cff3
|
@ -1,16 +1,17 @@
|
||||||
{ config, lib, pkgs, ...}:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.duplicity;
|
cfg = config.services.duplicity;
|
||||||
|
|
||||||
stateDirectory = "/var/lib/duplicity";
|
stateDirectory = "/var/lib/duplicity";
|
||||||
|
|
||||||
localTarget = if hasPrefix "file://" cfg.targetUrl
|
localTarget =
|
||||||
|
if hasPrefix "file://" cfg.targetUrl
|
||||||
then removePrefix "file://" cfg.targetUrl else null;
|
then removePrefix "file://" cfg.targetUrl else null;
|
||||||
|
|
||||||
in {
|
in
|
||||||
|
{
|
||||||
options.services.duplicity = {
|
options.services.duplicity = {
|
||||||
enable = mkEnableOption "backups with duplicity";
|
enable = mkEnableOption "backups with duplicity";
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ in {
|
||||||
|
|
||||||
include = mkOption {
|
include = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
default = [ ];
|
||||||
example = [ "/home" ];
|
example = [ "/home" ];
|
||||||
description = ''
|
description = ''
|
||||||
List of paths to include into the backups. See the FILE SELECTION
|
List of paths to include into the backups. See the FILE SELECTION
|
||||||
|
@ -35,7 +36,7 @@ in {
|
||||||
|
|
||||||
exclude = mkOption {
|
exclude = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
List of paths to exclude from backups. See the FILE SELECTION section in
|
List of paths to exclude from backups. See the FILE SELECTION section in
|
||||||
<citerefentry><refentrytitle>duplicity</refentrytitle>
|
<citerefentry><refentrytitle>duplicity</refentrytitle>
|
||||||
|
@ -82,14 +83,60 @@ in {
|
||||||
|
|
||||||
extraFlags = mkOption {
|
extraFlags = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
default = [ ];
|
||||||
example = [ "--full-if-older-than" "1M" ];
|
example = [ "--backend-retry-delay" "100" ];
|
||||||
description = ''
|
description = ''
|
||||||
Extra command-line flags passed to duplicity. See
|
Extra command-line flags passed to duplicity. See
|
||||||
<citerefentry><refentrytitle>duplicity</refentrytitle>
|
<citerefentry><refentrytitle>duplicity</refentrytitle>
|
||||||
<manvolnum>1</manvolnum></citerefentry>.
|
<manvolnum>1</manvolnum></citerefentry>.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fullIfOlderThan = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "never";
|
||||||
|
example = "1M";
|
||||||
|
description = ''
|
||||||
|
If <literal>"never"</literal> (the default) always do incremental
|
||||||
|
backups (the first backup will be a full backup, of course). If
|
||||||
|
<literal>"always"</literal> 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;
|
||||||
|
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).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
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).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
@ -99,18 +146,26 @@ in {
|
||||||
|
|
||||||
environment.HOME = stateDirectory;
|
environment.HOME = stateDirectory;
|
||||||
|
|
||||||
serviceConfig = {
|
script =
|
||||||
ExecStart = ''
|
let
|
||||||
${pkgs.duplicity}/bin/duplicity ${escapeShellArgs (
|
target = escapeShellArg cfg.targetUrl;
|
||||||
[
|
extra = escapeShellArgs ([ "--archive-dir" stateDirectory ] ++ cfg.extraFlags);
|
||||||
cfg.root
|
dup = "${pkgs.duplicity}/bin/duplicity";
|
||||||
cfg.targetUrl
|
in
|
||||||
"--archive-dir" stateDirectory
|
''
|
||||||
]
|
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}"}
|
||||||
|
${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: [ "--include" p ]) cfg.include
|
||||||
++ concatMap (p: [ "--exclude" p ]) cfg.exclude
|
++ concatMap (p: [ "--exclude" p ]) cfg.exclude
|
||||||
++ cfg.extraFlags)}
|
++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ])
|
||||||
|
)} ${extra}
|
||||||
'';
|
'';
|
||||||
|
serviceConfig = {
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
ProtectSystem = "strict";
|
ProtectSystem = "strict";
|
||||||
ProtectHome = "read-only";
|
ProtectHome = "read-only";
|
||||||
|
@ -130,7 +185,7 @@ in {
|
||||||
assertions = singleton {
|
assertions = singleton {
|
||||||
# Duplicity will fail if the last file selection option is an include. It
|
# 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.
|
# 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 = ''
|
message = ''
|
||||||
Duplicity will fail if you only specify included paths ("Because the
|
Duplicity will fail if you only specify included paths ("Because the
|
||||||
default is to include all files, the expression is redundant. Exiting
|
default is to include all files, the expression is redundant. Exiting
|
||||||
|
|
Loading…
Reference in a new issue