{ config, lib, pkgs, ... }: with lib; let cfg = config.services.syncoid; # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html escapeUnitName = name: lib.concatMapStrings (s: if lib.isList s then "-" else s) (builtins.split "[^a-zA-Z0-9_.\\-]+" name); in { # Interface options.services.syncoid = { enable = mkEnableOption "Syncoid ZFS synchronization service"; interval = mkOption { type = types.str; default = "hourly"; example = "*-*-* *:15:00"; description = '' Run syncoid at this interval. The default is to run hourly. The format is described in systemd.time 7. ''; }; user = mkOption { type = types.str; default = "root"; example = "backup"; description = '' The user for the service. Sudo or ZFS privilege delegation must be configured to use a user other than root. ''; }; sshKey = mkOption { type = types.nullOr types.path; # Prevent key from being copied to store apply = mapNullable toString; default = null; description = '' SSH private key file to use to login to the remote system. Can be overridden in individual commands. ''; }; commonArgs = mkOption { type = types.listOf types.str; default = []; example = [ "--no-sync-snap" ]; description = '' Arguments to add to every syncoid command, unless disabled for that command. See for available options. ''; }; service = mkOption { type = types.attrs; default = {}; description = '' Systemd configuration common to all syncoid services. ''; }; commands = mkOption { type = types.attrsOf (types.submodule ({ name, ... }: { options = { source = mkOption { type = types.str; example = "pool/dataset"; description = '' Source ZFS dataset. Can be either local or remote. Defaults to the attribute name. ''; }; target = mkOption { type = types.str; example = "user@server:pool/dataset"; description = '' Target ZFS dataset. Can be either local (pool/dataset) or remote (user@server:pool/dataset). ''; }; recursive = mkEnableOption ''the transfer of child datasets''; sshKey = mkOption { type = types.nullOr types.path; # Prevent key from being copied to store apply = mapNullable toString; description = '' SSH private key file to use to login to the remote system. Defaults to option. ''; }; sendOptions = mkOption { type = types.separatedString " "; default = ""; example = "Lc e"; description = '' Advanced options to pass to zfs send. Options are specified without their leading dashes and separated by spaces. ''; }; recvOptions = mkOption { type = types.separatedString " "; default = ""; example = "ux recordsize o compression=lz4"; description = '' Advanced options to pass to zfs recv. Options are specified without their leading dashes and separated by spaces. ''; }; useCommonArgs = mkOption { type = types.bool; default = true; description = '' Whether to add the configured common arguments to this command. ''; }; service = mkOption { type = types.attrs; default = {}; description = '' Systemd configuration specific to this syncoid service. ''; }; extraArgs = mkOption { type = types.listOf types.str; default = []; example = [ "--sshport 2222" ]; description = "Extra syncoid arguments for this command."; }; }; config = { source = mkDefault name; sshKey = mkDefault cfg.sshKey; }; })); default = {}; example."pool/test".target = "root@target:pool/test"; description = "Syncoid commands to run."; }; }; # Implementation config = mkIf cfg.enable { systemd.services = mapAttrs' (name: c: nameValuePair "syncoid-${escapeUnitName name}" (recursiveUpdate (recursiveUpdate { description = "Syncoid ZFS synchronization from ${c.source} to ${c.target}"; script = lib.escapeShellArgs ([ "${pkgs.sanoid}/bin/syncoid" ] ++ (optionals c.useCommonArgs cfg.commonArgs) ++ (optional c.recursive "-r") ++ (optionals (c.sshKey != null) [ "--sshkey" c.sshKey ]) ++ c.extraArgs ++ [ "--sendoptions" c.sendOptions "--recvoptions" c.recvOptions c.source c.target ]); after = [ "zfs.target" ]; serviceConfig.User = cfg.user; startAt = cfg.interval; } cfg.serviceConfig) c.serviceConfig) cfg.commands; }; meta.maintainers = with maintainers; [ lopsided98 ]; }