at 18.09-beta 11 kB view raw
1{ config, options, pkgs, lib, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.rspamd; 8 opts = options.services.rspamd; 9 10 bindSocketOpts = {options, config, ... }: { 11 options = { 12 socket = mkOption { 13 type = types.str; 14 example = "localhost:11333"; 15 description = '' 16 Socket for this worker to listen on in a format acceptable by rspamd. 17 ''; 18 }; 19 mode = mkOption { 20 type = types.str; 21 default = "0644"; 22 description = "Mode to set on unix socket"; 23 }; 24 owner = mkOption { 25 type = types.str; 26 default = "${cfg.user}"; 27 description = "Owner to set on unix socket"; 28 }; 29 group = mkOption { 30 type = types.str; 31 default = "${cfg.group}"; 32 description = "Group to set on unix socket"; 33 }; 34 rawEntry = mkOption { 35 type = types.str; 36 internal = true; 37 }; 38 }; 39 config.rawEntry = let 40 maybeOption = option: 41 optionalString options.${option}.isDefined " ${option}=${config.${option}}"; 42 in 43 if (!(hasPrefix "/" config.socket)) then "${config.socket}" 44 else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}"; 45 }; 46 47 workerOpts = { name, ... }: { 48 options = { 49 enable = mkOption { 50 type = types.nullOr types.bool; 51 default = null; 52 description = "Whether to run the rspamd worker."; 53 }; 54 name = mkOption { 55 type = types.nullOr types.str; 56 default = name; 57 description = "Name of the worker"; 58 }; 59 type = mkOption { 60 type = types.nullOr (types.enum [ 61 "normal" "controller" "fuzzy_storage" "proxy" "lua" 62 ]); 63 description = "The type of this worker"; 64 }; 65 bindSockets = mkOption { 66 type = types.listOf (types.either types.str (types.submodule bindSocketOpts)); 67 default = []; 68 description = '' 69 List of sockets to listen, in format acceptable by rspamd 70 ''; 71 example = [{ 72 socket = "/run/rspamd.sock"; 73 mode = "0666"; 74 owner = "rspamd"; 75 } "*:11333"]; 76 apply = value: map (each: if (isString each) 77 then if (isUnixSocket each) 78 then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";} 79 else {socket = each; rawEntry = "${each}";} 80 else each) value; 81 }; 82 count = mkOption { 83 type = types.nullOr types.int; 84 default = null; 85 description = '' 86 Number of worker instances to run 87 ''; 88 }; 89 includes = mkOption { 90 type = types.listOf types.str; 91 default = []; 92 description = '' 93 List of files to include in configuration 94 ''; 95 }; 96 extraConfig = mkOption { 97 type = types.lines; 98 default = ""; 99 description = "Additional entries to put verbatim into worker section of rspamd config file."; 100 }; 101 }; 102 config = mkIf (name == "normal" || name == "controller" || name == "fuzzy") { 103 type = mkDefault name; 104 includes = mkDefault [ "$CONFDIR/worker-${name}.inc" ]; 105 bindSockets = mkDefault (if name == "normal" 106 then [{ 107 socket = "/run/rspamd/rspamd.sock"; 108 mode = "0660"; 109 owner = cfg.user; 110 group = cfg.group; 111 }] 112 else if name == "controller" 113 then [ "localhost:11334" ] 114 else [] ); 115 }; 116 }; 117 118 indexOf = default: start: list: e: 119 if list == [] 120 then default 121 else if (head list) == e then start 122 else (indexOf default (start + (length (listenStreams (head list).socket))) (tail list) e); 123 124 systemdSocket = indexOf (abort "Socket not found") 0 allSockets; 125 126 isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket); 127 isPort = hasPrefix "*:"; 128 isIPv4Socket = hasPrefix "*v4:"; 129 isIPv6Socket = hasPrefix "*v6:"; 130 isLocalHost = hasPrefix "localhost:"; 131 listenStreams = socket: 132 if (isLocalHost socket) then 133 let port = (removePrefix "localhost:" socket); 134 in [ "127.0.0.1:${port}" ] ++ (if config.networking.enableIPv6 then ["[::1]:${port}"] else []) 135 else if (isIPv6Socket socket) then [removePrefix "*v6:" socket] 136 else if (isPort socket) then [removePrefix "*:" socket] 137 else if (isIPv4Socket socket) then 138 throw "error: IPv4 only socket not supported in rspamd with socket activation" 139 else if (length (splitString " " socket)) != 1 then 140 throw "error: string options not supported in rspamd with socket activation" 141 else [socket]; 142 143 mkBindSockets = enabled: socks: concatStringsSep "\n " (flatten (map (each: 144 if cfg.socketActivation && enabled != false then 145 let systemd = (systemdSocket each); 146 in (imap (idx: e: "bind_socket = \"systemd:${toString (systemd + idx - 1)}\";") (listenStreams each.socket)) 147 else "bind_socket = \"${each.rawEntry}\";") socks)); 148 149 rspamdConfFile = pkgs.writeText "rspamd.conf" 150 '' 151 .include "$CONFDIR/common.conf" 152 153 options { 154 pidfile = "$RUNDIR/rspamd.pid"; 155 .include "$CONFDIR/options.inc" 156 } 157 158 logging { 159 type = "syslog"; 160 .include "$CONFDIR/logging.inc" 161 } 162 163 ${concatStringsSep "\n" (mapAttrsToList (name: value: '' 164 worker ${optionalString (value.name != "normal" && value.name != "controller") "${value.name}"} { 165 type = "${value.type}"; 166 ${optionalString (value.enable != null) 167 "enabled = ${if value.enable != false then "yes" else "no"};"} 168 ${mkBindSockets value.enable value.bindSockets} 169 ${optionalString (value.count != null) "count = ${toString value.count};"} 170 ${concatStringsSep "\n " (map (each: ".include \"${each}\"") value.includes)} 171 ${value.extraConfig} 172 } 173 '') cfg.workers)} 174 175 ${cfg.extraConfig} 176 ''; 177 178 allMappedSockets = flatten (mapAttrsToList (name: value: 179 if value.enable != false 180 then imap (idx: each: { 181 name = "${name}"; 182 index = idx; 183 value = each; 184 }) value.bindSockets 185 else []) cfg.workers); 186 allSockets = map (e: e.value) allMappedSockets; 187 188 allSocketNames = map (each: "rspamd-${each.name}-${toString each.index}.socket") allMappedSockets; 189 190in 191 192{ 193 194 ###### interface 195 196 options = { 197 198 services.rspamd = { 199 200 enable = mkEnableOption "Whether to run the rspamd daemon."; 201 202 debug = mkOption { 203 type = types.bool; 204 default = false; 205 description = "Whether to run the rspamd daemon in debug mode."; 206 }; 207 208 socketActivation = mkOption { 209 type = types.bool; 210 description = '' 211 Enable systemd socket activation for rspamd. 212 ''; 213 }; 214 215 workers = mkOption { 216 type = with types; attrsOf (submodule workerOpts); 217 description = '' 218 Attribute set of workers to start. 219 ''; 220 default = { 221 normal = {}; 222 controller = {}; 223 }; 224 example = literalExample '' 225 { 226 normal = { 227 includes = [ "$CONFDIR/worker-normal.inc" ]; 228 bindSockets = [{ 229 socket = "/run/rspamd/rspamd.sock"; 230 mode = "0660"; 231 owner = "${cfg.user}"; 232 group = "${cfg.group}"; 233 }]; 234 }; 235 controller = { 236 includes = [ "$CONFDIR/worker-controller.inc" ]; 237 bindSockets = [ "[::1]:11334" ]; 238 }; 239 } 240 ''; 241 }; 242 243 extraConfig = mkOption { 244 type = types.lines; 245 default = ""; 246 description = '' 247 Extra configuration to add at the end of the rspamd configuration 248 file. 249 ''; 250 }; 251 252 user = mkOption { 253 type = types.string; 254 default = "rspamd"; 255 description = '' 256 User to use when no root privileges are required. 257 ''; 258 }; 259 260 group = mkOption { 261 type = types.string; 262 default = "rspamd"; 263 description = '' 264 Group to use when no root privileges are required. 265 ''; 266 }; 267 }; 268 }; 269 270 271 ###### implementation 272 273 config = mkIf cfg.enable { 274 275 services.rspamd.socketActivation = mkDefault (!opts.bindSocket.isDefined && !opts.bindUISocket.isDefined); 276 277 assertions = [ { 278 assertion = !cfg.socketActivation || !(opts.bindSocket.isDefined || opts.bindUISocket.isDefined); 279 message = "Can't use socketActivation for rspamd when using renamed bind socket options"; 280 } ]; 281 282 # Allow users to run 'rspamc' and 'rspamadm'. 283 environment.systemPackages = [ pkgs.rspamd ]; 284 285 users.users = singleton { 286 name = cfg.user; 287 description = "rspamd daemon"; 288 uid = config.ids.uids.rspamd; 289 group = cfg.group; 290 }; 291 292 users.groups = singleton { 293 name = cfg.group; 294 gid = config.ids.gids.rspamd; 295 }; 296 297 environment.etc."rspamd.conf".source = rspamdConfFile; 298 299 systemd.services.rspamd = { 300 description = "Rspamd Service"; 301 302 wantedBy = mkIf (!cfg.socketActivation) [ "multi-user.target" ]; 303 after = [ "network.target" ] ++ 304 (if cfg.socketActivation then allSocketNames else []); 305 requires = mkIf cfg.socketActivation allSocketNames; 306 307 serviceConfig = { 308 ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c ${rspamdConfFile} -f"; 309 Restart = "always"; 310 RuntimeDirectory = "rspamd"; 311 PrivateTmp = true; 312 Sockets = mkIf cfg.socketActivation (concatStringsSep " " allSocketNames); 313 }; 314 315 preStart = '' 316 ${pkgs.coreutils}/bin/mkdir -p /var/lib/rspamd 317 ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd 318 ''; 319 }; 320 systemd.sockets = mkIf cfg.socketActivation 321 (listToAttrs (map (each: { 322 name = "rspamd-${each.name}-${toString each.index}"; 323 value = { 324 description = "Rspamd socket ${toString each.index} for worker ${each.name}"; 325 wantedBy = [ "sockets.target" ]; 326 listenStreams = (listenStreams each.value.socket); 327 socketConfig = { 328 BindIPv6Only = mkIf (isIPv6Socket each.value.socket) "ipv6-only"; 329 Service = "rspamd.service"; 330 SocketUser = mkIf (isUnixSocket each.value.socket) each.value.owner; 331 SocketGroup = mkIf (isUnixSocket each.value.socket) each.value.group; 332 SocketMode = mkIf (isUnixSocket each.value.socket) each.value.mode; 333 }; 334 }; 335 }) allMappedSockets)); 336 }; 337 imports = [ 338 (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ]) 339 (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ]) 340 ]; 341}