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.")
 | |
|       '';
 | |
|     };
 | |
|   };
 | |
| }
 | 
