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