nixpkgs/nixos/modules/services/backup/duplicity.nix

142 lines
4.4 KiB
Nix
Raw Normal View History

{ config, lib, pkgs, ...}:
with lib;
let
cfg = config.services.duplicity;
stateDirectory = "/var/lib/duplicity";
localTarget = if hasPrefix "file://" cfg.targetUrl
then removePrefix "file://" cfg.targetUrl else null;
in {
options.services.duplicity = {
enable = mkEnableOption "backups with duplicity";
root = mkOption {
type = types.path;
default = "/";
description = ''
Root directory to backup.
'';
};
include = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/home" ];
description = ''
List of paths to include into the backups. See the FILE SELECTION
section in <citerefentry><refentrytitle>duplicity</refentrytitle>
<manvolnum>1</manvolnum></citerefentry> for details on the syntax.
'';
};
exclude = mkOption {
type = types.listOf types.str;
default = [];
description = ''
List of paths to exclude from backups. See the FILE SELECTION section in
<citerefentry><refentrytitle>duplicity</refentrytitle>
<manvolnum>1</manvolnum></citerefentry> for details on the syntax.
'';
};
targetUrl = mkOption {
type = types.str;
example = "s3://host:port/prefix";
description = ''
Target url to backup to. See the URL FORMAT section in
<citerefentry><refentrytitle>duplicity</refentrytitle>
<manvolnum>1</manvolnum></citerefentry> for supported urls.
'';
};
secretFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path of a file containing secrets (gpg passphrase, access key...) in
the format of EnvironmentFile as described by
<citerefentry><refentrytitle>systemd.exec</refentrytitle>
<manvolnum>5</manvolnum></citerefentry>. For example:
<programlisting>
PASSPHRASE=<replaceable>...</replaceable>
AWS_ACCESS_KEY_ID=<replaceable>...</replaceable>
AWS_SECRET_ACCESS_KEY=<replaceable>...</replaceable>
</programlisting>
'';
};
frequency = mkOption {
type = types.nullOr types.str;
default = "daily";
description = ''
Run duplicity with the given frequency (see
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry> for the format).
If null, do not run automatically.
'';
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [];
example = [ "--full-if-older-than" "1M" ];
description = ''
Extra command-line flags passed to duplicity. See
<citerefentry><refentrytitle>duplicity</refentrytitle>
<manvolnum>1</manvolnum></citerefentry>.
'';
};
};
config = mkIf cfg.enable {
systemd = {
services.duplicity = {
description = "backup files with duplicity";
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)}
'';
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
StateDirectory = baseNameOf stateDirectory;
} // optionalAttrs (localTarget != null) {
ReadWritePaths = localTarget;
} // optionalAttrs (cfg.secretFile != null) {
EnvironmentFile = cfg.secretFile;
};
} // optionalAttrs (cfg.frequency != null) {
startAt = cfg.frequency;
};
tmpfiles.rules = optional (localTarget != null) "d ${localTarget} 0700 root root -";
};
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 != [];
message = ''
Duplicity will fail if you only specify included paths ("Because the
default is to include all files, the expression is redundant. Exiting
because this probably isn't what you meant.")
'';
};
};
}