142 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ 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.")
 | 
						|
      '';
 | 
						|
    };
 | 
						|
  };
 | 
						|
}
 |