{ config, lib, pkgs, ... }: with lib; let cfg = config.services.unison; pairOf = t: let list = types.addCheck (types.listOf t) (l: length l == 2); in list // { description = list.description + " of length 2"; }; pairOptions = { options = { stateDirectory = mkOption { type = types.path; default = "${config.xdg.dataHome}/unison"; defaultText = "$XDG_DATA_HOME/unison"; description = lib.mdDoc '' Unison state directory to use. ''; }; commandOptions = mkOption rec { type = with types; attrsOf (either str (listOf str)); apply = mergeAttrs default; default = { repeat = "watch"; sshcmd = "${pkgs.openssh}/bin/ssh"; ui = "text"; auto = "true"; batch = "true"; log = "false"; # don't log to file, handled by systemd }; description = lib.mdDoc '' Additional command line options as a dictionary to pass to the `unison` program. Use a list of strings to declare the same option multiple times. See {manpage}`unison(1)` for a list of available options. ''; }; roots = mkOption { type = pairOf types.str; example = literalExpression '' [ "/home/user/documents" "ssh://remote/documents" ] ''; description = lib.mdDoc '' Pair of roots to synchronise. ''; }; }; }; serialiseArg = key: val: concatStringsSep " " (forEach (toList val) (x: escapeShellArg "-${key}=${escape [ "=" ] x}")); serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args); makeDefs = gen: mapAttrs' (name: pairCfg: nameValuePair "unison-pair-${name}" (gen name pairCfg)) cfg.pairs; in { meta.maintainers = with maintainers; [ pacien ]; options.services.unison = { enable = mkEnableOption (lib.mdDoc "Unison synchronisation"); pairs = mkOption { type = with types; attrsOf (submodule pairOptions); default = { }; example = literalExpression '' { "my-documents" = { roots = [ "/home/user/documents" "ssh://remote/documents" ]; }; } ''; description = lib.mdDoc '' Unison root pairs to keep synchronised. ''; }; }; config = mkIf cfg.enable { assertions = [ (lib.hm.assertions.assertPlatform "services.unison" pkgs lib.platforms.linux) ]; systemd.user.services = makeDefs (name: pairCfg: { Unit = { Description = "Unison pair sync (${name})"; # Retry forever, useful in case of network disruption. StartLimitIntervalSec = 0; }; Service = { Restart = "always"; RestartSec = 60; CPUSchedulingPolicy = "idle"; IOSchedulingClass = "idle"; Environment = [ "UNISON='${toString pairCfg.stateDirectory}'" ]; ExecStart = '' ${pkgs.unison}/bin/unison \ ${serialiseArgs pairCfg.commandOptions} \ ${strings.concatMapStringsSep " " escapeShellArg pairCfg.roots} ''; }; Install = { WantedBy = [ "default.target" ]; }; }); }; }