nixos/duplicity: init
Add a simple module that wrap duplicity in a systemd service.
This commit is contained in:
parent
ea0ab2fd05
commit
7a878660a7
|
@ -186,6 +186,7 @@
|
||||||
./services/backup/duplicati.nix
|
./services/backup/duplicati.nix
|
||||||
./services/backup/crashplan.nix
|
./services/backup/crashplan.nix
|
||||||
./services/backup/crashplan-small-business.nix
|
./services/backup/crashplan-small-business.nix
|
||||||
|
./services/backup/duplicity.nix
|
||||||
./services/backup/mysql-backup.nix
|
./services/backup/mysql-backup.nix
|
||||||
./services/backup/postgresql-backup.nix
|
./services/backup/postgresql-backup.nix
|
||||||
./services/backup/restic.nix
|
./services/backup/restic.nix
|
||||||
|
|
141
nixos/modules/services/backup/duplicity.nix
Normal file
141
nixos/modules/services/backup/duplicity.nix
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
{ 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.")
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue