{ config, lib, pkgs, options, ... }: let inherit (lib) mkIf mkEnableOption mkOption concatStringsSep toList listToAttrs ; inherit (lib.types) listOf nonEmptyStr package str port bool submoduleWith ; inherit (lib.my) slugify; cfg = config.modules.services.rsync-backup; in { # Used to back up multiple source destinations to a target destination regularly, # with possibility to enable incremental backups for versioning. # TODO: systemd unit # - [x] script taking configurable args # - [x] sync from/to paths # - [ ] support # - [ ] nfs # - [ ] sftp # - [ ] ssh # - [x] local # - [x] rsyncd # - [ ] retention limit for incremental backups options.modules.services.rsync-backup = { enable = mkEnableOption "rsync backups"; modules = mkOption { default = [ ]; type = listOf (submoduleWith { shorthandOnlyDefinesConfig = true; modules = toList (import ./module.nix); specialArgs = { rootCfg = cfg; }; }); }; exclude = mkOption { type = listOf str; default = [ ".git" ]; }; followGitignore = mkOption { type = bool; default = true; }; package = mkOption { type = package; default = pkgs.rsync; description = '' The rsync package to use. ''; }; interval = mkOption { type = nonEmptyStr; default = "daily"; }; unitName = mkOption { type = nonEmptyStr; default = "rsync-backup"; }; user = mkOption { type = str; default = "rsync-backup"; description = "User account under which Rsync runs."; }; group = mkOption { type = str; default = "rsync-backup"; description = "Group under which Rsync runs."; }; port = mkOption { type = port; default = options.modules.services.rsync-daemon.port.default; }; }; config = mkIf cfg.enable { systemd.timers = listToAttrs ( map ( mod: let name = "${cfg.unitName}-${slugify mod.target.location}"; in { inherit name; value = { enable = true; wantedBy = [ "timers.target" ]; unitConfig = { Description = "Triggers rsync-backups regularly."; }; timerConfig = { Unit = "${name}.service"; OnCalendar = mod.interval; Persistent = true; }; }; } ) cfg.modules ); systemd.services = listToAttrs ( map (mod: { name = "${cfg.unitName}-${slugify mod.target.location}"; value = { script = let destination = if mod.incremental.enable then "${mod.target.finalLocation}/backup" else mod.target.finalLocation; sources = concatStringsSep " " (map (s: "\"${s}\"") mod.sources); in '' ${cfg.package}/bin/rsync \ ${mod.flagsFinal} \ ${sources} \ "${destination}" ''; requires = mkIf (mod.target.type == "rsyncd") [ "network.target" ]; unitConfig = { Description = "Backs up files from a source location to a specified destination."; }; serviceConfig = { Type = "simple"; Restart = "on-failure"; RestartSec = 300; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; ReadOnlyPaths = concatStringsSep " " mod.sources; IPAddressAllow = "any"; }; }; }) cfg.modules ); users.users = mkIf (cfg.user == "rsync-backup") { rsync-backup = { useDefaultShell = true; group = cfg.group; isSystemUser = true; }; }; users.groups = mkIf (cfg.group == "rsync-backup") { rsync-backup = { }; }; }; }