summary refs log tree commit diff
path: root/nixos/modules/services/backup/duplicity.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/backup/duplicity.nix')
-rw-r--r--nixos/modules/services/backup/duplicity.nix141
1 files changed, 141 insertions, 0 deletions
diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix
new file mode 100644
index 00000000000..a8d56424862
--- /dev/null
+++ b/nixos/modules/services/backup/duplicity.nix
@@ -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.")
+      '';
+    };
+  };
+}