1 { config, options, pkgs, lib, ... }:
7 cfg = config.services.rspamd-upstream;
8 opts = options.services.rspamd-upstream;
9 postfixCfg = config.services.postfix;
11 bindSocketOpts = {options, config, ... }: {
15 example = "localhost:11333";
17 Socket for this worker to listen on in a format acceptable by rspamd.
23 description = "Mode to set on unix socket";
27 default = "${cfg.user}";
28 description = "Owner to set on unix socket";
32 default = "${cfg.group}";
33 description = "Group to set on unix socket";
42 optionalString options.${option}.isDefined " ${option}=${config.${option}}";
44 if (!(hasPrefix "/" config.socket)) then "${config.socket}"
45 else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
48 traceWarning = w: x: builtins.trace "
\e[1;31mwarning: ${w}
\e[0m" x;
50 workerOpts = { name, options, ... }: {
53 type = types.nullOr types.bool;
55 description = "Whether to run the rspamd worker.";
58 type = types.nullOr types.str;
60 description = "Name of the worker";
63 type = types.nullOr (types.enum [
64 "normal" "controller" "fuzzy_storage" "rspamd_proxy" "lua" "proxy"
67 The type of this worker. The type <literal>proxy</literal> is
68 deprecated and only kept for backwards compatibility and should be
69 replaced with <literal>rspamd_proxy</literal>.
72 from = "services.rspamd-upstream.workers.\”${name}\".type";
73 files = options.type.files;
74 warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
75 in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
77 bindSockets = mkOption {
78 type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
81 List of sockets to listen, in format acceptable by rspamd
84 socket = "/run/rspamd.sock";
88 apply = value: map (each: if (isString each)
89 then if (isUnixSocket each)
90 then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";}
91 else {socket = each; rawEntry = "${each}";}
95 type = types.nullOr types.int;
98 Number of worker instances to run
101 includes = mkOption {
102 type = types.listOf types.str;
105 List of files to include in configuration
108 extraConfig = mkOption {
111 description = "Additional entries to put verbatim into worker section of rspamd config file.";
114 config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
115 type = mkDefault name;
116 includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
121 socket = "/run/rspamd/${name}.sock";
125 in mkDefault (if name == "normal" then [(unixSocket "rspamd")]
126 else if name == "controller" then [ "localhost:11334" ]
127 else if name == "rspamd_proxy" then [ (unixSocket "proxy") ]
132 isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
134 mkBindSockets = enabled: socks: concatStringsSep "\n "
135 (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
137 rspamdConfFile = pkgs.writeText "rspamd.conf"
139 .include "$CONFDIR/common.conf"
142 pidfile = "$RUNDIR/rspamd.pid";
143 .include "$CONFDIR/options.inc"
144 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
145 .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
150 .include "$CONFDIR/logging.inc"
151 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
152 .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
155 ${concatStringsSep "\n" (mapAttrsToList (name: value: let
156 includeName = if name == "rspamd_proxy" then "proxy" else name;
157 tryOverride = if value.extraConfig == "" then "true" else "false";
159 worker "${value.type}" {
160 type = "${value.type}";
161 ${optionalString (value.enable != null)
162 "enabled = ${if value.enable != false then "yes" else "no"};"}
163 ${mkBindSockets value.enable value.bindSockets}
164 ${optionalString (value.count != null) "count = ${toString value.count};"}
165 ${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)}
166 .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
167 .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
171 ${optionalString (cfg.extraConfig != "") ''
172 .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
176 filterFiles = files: filterAttrs (n: v: v.enable) files;
177 rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
178 (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++
179 (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++
180 (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++
181 [ { name = "rspamd.conf"; path = rspamdConfFile; } ]
184 configFileModule = prefix: { name, config, ... }: {
190 Whether this file ${prefix} should be generated. This
191 option allows specific ${prefix} files to be disabled.
197 type = types.nullOr types.lines;
198 description = "Text of the file.";
203 description = "Path of the source file.";
207 source = mkIf (config.text != null) (
208 let name' = "rspamd-${prefix}-" + baseNameOf name;
209 in mkDefault (pkgs.writeText name' config.text));
214 (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
215 text = v.extraConfig;
217 (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
218 // (if cfg.extraConfig == "" then {} else {
219 "extra-config.inc".text = cfg.extraConfig;
229 services.rspamd-upstream = {
231 enable = mkEnableOption "rspamd, the Rapid spam filtering system";
236 description = "Whether to run the rspamd daemon in debug mode.";
240 type = with types; attrsOf (submodule (configFileModule "locals"));
243 Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
245 example = literalExample ''
246 { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
247 "arc.conf".text = "allow_envfrom_empty = true;";
252 overrides = mkOption {
253 type = with types; attrsOf (submodule (configFileModule "overrides"));
256 Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
258 example = literalExample ''
259 { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
260 "arc.conf".text = "allow_envfrom_empty = true;";
265 localLuaRules = mkOption {
267 type = types.nullOr types.path;
269 Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
275 type = with types; attrsOf (submodule workerOpts);
277 Attribute set of workers to start.
283 example = literalExample ''
286 includes = [ "$CONFDIR/worker-normal.inc" ];
288 socket = "/run/rspamd/rspamd.sock";
290 owner = "${cfg.user}";
291 group = "${cfg.group}";
295 includes = [ "$CONFDIR/worker-controller.inc" ];
296 bindSockets = [ "[::1]:11334" ];
302 extraConfig = mkOption {
306 Extra configuration to add at the end of the rspamd configuration
315 User to use when no root privileges are required.
323 Group to use when no root privileges are required.
331 description = "Add rspamd milter to postfix main.conf";
335 type = with types; attrsOf (either bool (either str (listOf str)));
337 Addon to postfix configuration
340 smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
341 non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
344 smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
345 non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
353 ###### implementation
355 config = mkIf cfg.enable {
356 services.rspamd-upstream.overrides = configOverrides;
357 services.rspamd-upstream.workers = mkIf cfg.postfix.enable {
362 socket = "/run/rspamd/rspamd-milter.sock";
364 group = postfixCfg.group;
368 default = yes; # Self-scan upstreams are always default
369 self_scan = yes; # Enable self-scan
374 services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
376 # Allow users to run 'rspamc' and 'rspamadm'.
377 environment.systemPackages = [ pkgs.rspamd ];
379 users.users = singleton {
381 description = "rspamd daemon";
382 uid = config.ids.uids.rspamd;
386 users.groups = singleton {
388 gid = config.ids.gids.rspamd;
391 environment.etc."rspamd".source = rspamdDir;
393 systemd.services.rspamd-upstream = {
394 description = "Rspamd Service";
396 wantedBy = [ "multi-user.target" ];
397 after = [ "network.target" ];
398 restartTriggers = [ rspamdDir ];
401 ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c /etc/rspamd/rspamd.conf -f";
403 RuntimeDirectory = "rspamd";
408 ${pkgs.coreutils}/bin/mkdir -p /var/lib/rspamd
409 ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
414 (mkRemovedOptionModule [ "services" "rspamd-upstream" "socketActivation" ]
415 "Socket activation never worked correctly and could at this time not be fixed and so was removed")
416 (mkRenamedOptionModule [ "services" "rspamd-upstream" "bindSocket" ]
417 [ "services" "rspamd-upstream" "workers" "normal" "bindSockets" ])
418 (mkRenamedOptionModule [ "services" "rspamd-upstream" "bindUISocket" ]
419 [ "services" "rspamd-upstream" "workers" "controller" "bindSockets" ])