at master 26 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 9 cfg = config.services.mailman; 10 11 inherit 12 (cfg.packageSet.buildEnvs { 13 withHyperkitty = cfg.hyperkitty.enable; 14 withLDAP = cfg.ldap.enable; 15 }) 16 mailmanEnv 17 webEnv 18 ; 19 20 withPostgresql = config.services.postgresql.enable; 21 22 # This deliberately doesn't use recursiveUpdate so users can 23 # override the defaults. 24 webSettings = { 25 DEFAULT_FROM_EMAIL = cfg.siteOwner; 26 SERVER_EMAIL = cfg.siteOwner; 27 ALLOWED_HOSTS = [ 28 "localhost" 29 "127.0.0.1" 30 ] 31 ++ cfg.webHosts; 32 COMPRESS_OFFLINE = true; 33 STATIC_ROOT = "/var/lib/mailman-web-static"; 34 MEDIA_ROOT = "/var/lib/mailman-web/media"; 35 LOGGING = { 36 version = 1; 37 disable_existing_loggers = true; 38 handlers.console.class = "logging.StreamHandler"; 39 loggers.django = { 40 handlers = [ "console" ]; 41 level = "INFO"; 42 }; 43 }; 44 HAYSTACK_CONNECTIONS.default = { 45 ENGINE = "haystack.backends.whoosh_backend.WhooshEngine"; 46 PATH = "/var/lib/mailman-web/fulltext-index"; 47 }; 48 } 49 // lib.optionalAttrs cfg.enablePostfix { 50 EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"; 51 EMAIL_HOST = "127.0.0.1"; 52 EMAIL_PORT = 25; 53 } 54 // cfg.webSettings; 55 56 webSettingsJSON = pkgs.writeText "settings.json" (builtins.toJSON webSettings); 57 58 # TODO: Should this be RFC42-ised so that users can set additional options without modifying the module? 59 postfixMtaConfig = pkgs.writeText "mailman-postfix.cfg" '' 60 [postfix] 61 postmap_command: ${lib.getExe' config.services.postfix.package "postmap"} 62 transport_file_type: hash 63 ''; 64 65 mailmanCfg = lib.generators.toINI { } ( 66 lib.recursiveUpdate cfg.settings { 67 webservice.admin_pass = "#NIXOS_MAILMAN_REST_API_PASS_SECRET#"; 68 } 69 ); 70 71 mailmanCfgFile = pkgs.writeText "mailman-raw.cfg" mailmanCfg; 72 73 mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" '' 74 [general] 75 # This is your HyperKitty installation, preferably on the localhost. This 76 # address will be used by Mailman to forward incoming emails to HyperKitty 77 # for archiving. It does not need to be publicly available, in fact it's 78 # better if it is not. 79 base_url: ${cfg.hyperkitty.baseUrl} 80 81 # Shared API key, must be the identical to the value in HyperKitty's 82 # settings. 83 api_key: @API_KEY@ 84 ''; 85 86in 87{ 88 89 ###### interface 90 91 imports = [ 92 (lib.mkRenamedOptionModule 93 [ "services" "mailman" "hyperkittyBaseUrl" ] 94 [ "services" "mailman" "hyperkitty" "baseUrl" ] 95 ) 96 97 (lib.mkRemovedOptionModule [ "services" "mailman" "hyperkittyApiKey" ] '' 98 The Hyperkitty API key is now generated on first run, and not 99 stored in the world-readable Nix store. To continue using 100 Hyperkitty, you must set services.mailman.hyperkitty.enable = true. 101 '') 102 (lib.mkRemovedOptionModule [ "services" "mailman" "package" ] '' 103 Didn't have an effect for several years. 104 '') 105 (lib.mkRemovedOptionModule [ "services" "mailman" "extraPythonPackages" ] '' 106 Didn't have an effect for several years. 107 '') 108 ]; 109 110 options = { 111 112 services.mailman = { 113 114 enable = lib.mkOption { 115 type = lib.types.bool; 116 default = false; 117 description = "Enable Mailman on this host. Requires an active MTA on the host (e.g. Postfix)."; 118 }; 119 120 packageSet = lib.mkPackageOption pkgs "mailmanPackages" { } // { 121 type = lib.types.attrs; 122 }; 123 124 ldap = { 125 enable = lib.mkEnableOption "LDAP auth"; 126 serverUri = lib.mkOption { 127 type = lib.types.str; 128 example = "ldaps://ldap.host"; 129 description = '' 130 LDAP host to connect against. 131 ''; 132 }; 133 bindDn = lib.mkOption { 134 type = lib.types.str; 135 example = "cn=root,dc=nixos,dc=org"; 136 description = '' 137 Service account to bind against. 138 ''; 139 }; 140 bindPasswordFile = lib.mkOption { 141 type = lib.types.str; 142 example = "/run/secrets/ldap-bind"; 143 description = '' 144 Path to the file containing the bind password of the service account 145 defined by [](#opt-services.mailman.ldap.bindDn). 146 ''; 147 }; 148 superUserGroup = lib.mkOption { 149 type = lib.types.nullOr lib.types.str; 150 default = null; 151 example = "cn=admin,ou=groups,dc=nixos,dc=org"; 152 description = '' 153 Group where a user must be a member of to gain superuser rights. 154 ''; 155 }; 156 userSearch = { 157 query = lib.mkOption { 158 type = lib.types.str; 159 example = "(&(objectClass=inetOrgPerson)(|(uid=%(user)s)(mail=%(user)s)))"; 160 description = '' 161 Query to find a user in the LDAP database. 162 ''; 163 }; 164 ou = lib.mkOption { 165 type = lib.types.str; 166 example = "ou=users,dc=nixos,dc=org"; 167 description = '' 168 Organizational unit to look up a user. 169 ''; 170 }; 171 }; 172 groupSearch = { 173 type = lib.mkOption { 174 type = lib.types.enum [ 175 "posixGroup" 176 "groupOfNames" 177 "memberDNGroup" 178 "nestedMemberDNGroup" 179 "nestedGroupOfNames" 180 "groupOfUniqueNames" 181 "nestedGroupOfUniqueNames" 182 "activeDirectoryGroup" 183 "nestedActiveDirectoryGroup" 184 "organizationalRoleGroup" 185 "nestedOrganizationalRoleGroup" 186 ]; 187 default = "posixGroup"; 188 apply = v: "${lib.toUpper (lib.substring 0 1 v)}${lib.substring 1 (lib.stringLength v) v}Type"; 189 description = '' 190 Type of group to perform a group search against. 191 ''; 192 }; 193 query = lib.mkOption { 194 type = lib.types.str; 195 example = "(objectClass=groupOfNames)"; 196 description = '' 197 Query to find a group associated to a user in the LDAP database. 198 ''; 199 }; 200 ou = lib.mkOption { 201 type = lib.types.str; 202 example = "ou=groups,dc=nixos,dc=org"; 203 description = '' 204 Organizational unit to look up a group. 205 ''; 206 }; 207 }; 208 attrMap = { 209 username = lib.mkOption { 210 default = "uid"; 211 type = lib.types.str; 212 description = '' 213 LDAP-attribute that corresponds to the `username`-attribute in mailman. 214 ''; 215 }; 216 firstName = lib.mkOption { 217 default = "givenName"; 218 type = lib.types.str; 219 description = '' 220 LDAP-attribute that corresponds to the `firstName`-attribute in mailman. 221 ''; 222 }; 223 lastName = lib.mkOption { 224 default = "sn"; 225 type = lib.types.str; 226 description = '' 227 LDAP-attribute that corresponds to the `lastName`-attribute in mailman. 228 ''; 229 }; 230 email = lib.mkOption { 231 default = "mail"; 232 type = lib.types.str; 233 description = '' 234 LDAP-attribute that corresponds to the `email`-attribute in mailman. 235 ''; 236 }; 237 }; 238 }; 239 240 enablePostfix = lib.mkOption { 241 type = lib.types.bool; 242 default = true; 243 example = false; 244 description = '' 245 Enable Postfix integration. Requires an active Postfix installation. 246 247 If you want to use another MTA, set this option to false and configure 248 settings in services.mailman.settings.mta. 249 250 Refer to the Mailman manual for more info. 251 ''; 252 }; 253 254 siteOwner = lib.mkOption { 255 type = lib.types.str; 256 example = "postmaster@example.org"; 257 description = '' 258 Certain messages that must be delivered to a human, but which can't 259 be delivered to a list owner (e.g. a bounce from a list owner), will 260 be sent to this address. It should point to a human. 261 ''; 262 }; 263 264 webHosts = lib.mkOption { 265 type = lib.types.listOf lib.types.str; 266 default = [ ]; 267 description = '' 268 The list of hostnames and/or IP addresses from which the Mailman Web 269 UI will accept requests. By default, "localhost" and "127.0.0.1" are 270 enabled. All additional names under which your web server accepts 271 requests for the UI must be listed here or incoming requests will be 272 rejected. 273 ''; 274 }; 275 276 webUser = lib.mkOption { 277 type = lib.types.str; 278 default = "mailman-web"; 279 description = '' 280 User to run mailman-web as 281 ''; 282 }; 283 284 webSettings = lib.mkOption { 285 type = lib.types.attrs; 286 default = { }; 287 description = '' 288 Overrides for the default mailman-web Django settings. 289 ''; 290 }; 291 292 restApiPassFile = lib.mkOption { 293 default = null; 294 type = lib.types.nullOr lib.types.str; 295 description = '' 296 Path to the file containing the value for `MAILMAN_REST_API_PASS`. 297 ''; 298 }; 299 300 serve = { 301 enable = lib.mkEnableOption "automatic nginx and uwsgi setup for mailman-web"; 302 303 uwsgiSettings = lib.mkOption { 304 default = { }; 305 example = { 306 uwsgi.buffer-size = 8192; 307 }; 308 inherit (pkgs.formats.json { }) type; 309 description = '' 310 Extra configuration to merge into uwsgi config. 311 ''; 312 }; 313 314 virtualRoot = lib.mkOption { 315 default = "/"; 316 example = lib.literalExpression "/lists"; 317 type = lib.types.str; 318 description = '' 319 Path to mount the mailman-web django application on. 320 ''; 321 }; 322 }; 323 324 settings = lib.mkOption { 325 description = "Settings for mailman.cfg"; 326 type = lib.types.attrsOf (lib.types.attrsOf lib.types.str); 327 default = { }; 328 }; 329 330 hyperkitty = { 331 enable = lib.mkEnableOption "the Hyperkitty archiver for Mailman"; 332 333 baseUrl = lib.mkOption { 334 type = lib.types.str; 335 default = "http://localhost:18507/archives/"; 336 description = '' 337 Where can Mailman connect to Hyperkitty's internal API, preferably on 338 localhost? 339 ''; 340 }; 341 }; 342 343 }; 344 }; 345 346 ###### implementation 347 348 config = lib.mkIf cfg.enable { 349 350 services.mailman.settings = { 351 mailman.site_owner = lib.mkDefault cfg.siteOwner; 352 mailman.layout = "fhs"; 353 354 "paths.fhs" = { 355 bin_dir = "${cfg.packageSet.mailman}/bin"; 356 var_dir = "/var/lib/mailman"; 357 queue_dir = "$var_dir/queue"; 358 template_dir = "$var_dir/templates"; 359 log_dir = "/var/log/mailman"; 360 lock_dir = "/run/mailman/lock"; 361 etc_dir = "/etc"; 362 pid_file = "/run/mailman/master.pid"; 363 }; 364 365 mta.configuration = lib.mkDefault ( 366 if cfg.enablePostfix then 367 "${postfixMtaConfig}" 368 else 369 throw "When Mailman Postfix integration is disabled, set `services.mailman.settings.mta.configuration` to the path of the config file required to integrate with your MTA." 370 ); 371 372 "archiver.hyperkitty" = lib.mkIf cfg.hyperkitty.enable { 373 class = "mailman_hyperkitty.Archiver"; 374 enable = "yes"; 375 configuration = "/var/lib/mailman/mailman-hyperkitty.cfg"; 376 }; 377 } 378 // ( 379 let 380 loggerNames = [ 381 "root" 382 "archiver" 383 "bounce" 384 "config" 385 "database" 386 "debug" 387 "error" 388 "fromusenet" 389 "http" 390 "locks" 391 "mischief" 392 "plugins" 393 "runner" 394 "smtp" 395 ]; 396 loggerSectionNames = map (n: "logging.${n}") loggerNames; 397 in 398 lib.genAttrs loggerSectionNames (name: { 399 handler = "stderr"; 400 }) 401 ); 402 403 assertions = 404 let 405 inherit (config.services) postfix; 406 407 requirePostfixHash = 408 optionPath: dataFile: 409 let 410 expected = "hash:/var/lib/mailman/data/${dataFile}"; 411 value = lib.attrByPath optionPath [ ] postfix; 412 in 413 { 414 assertion = postfix.enable -> lib.isList value && lib.elem expected value; 415 message = '' 416 services.postfix.${lib.concatStringsSep "." optionPath} must contain 417 "${expected}". 418 See <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>. 419 ''; 420 }; 421 in 422 [ 423 { 424 assertion = cfg.webHosts != [ ]; 425 message = '' 426 services.mailman.serve.enable requires there to be at least one entry 427 in services.mailman.webHosts. 428 ''; 429 } 430 ] 431 ++ (lib.optionals cfg.enablePostfix [ 432 { 433 assertion = postfix.enable; 434 message = '' 435 Mailman's default NixOS configuration requires Postfix to be enabled. 436 437 If you want to use another MTA, set services.mailman.enablePostfix 438 to false and configure settings in services.mailman.settings.mta. 439 440 Refer to <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html> 441 for more info. 442 ''; 443 } 444 (requirePostfixHash [ "settings" "main" "relay_domains" ] "postfix_domains") 445 (requirePostfixHash [ "settings" "main" "transport_maps" ] "postfix_lmtp") 446 (requirePostfixHash [ "settings" "main" "local_recipient_maps" ] "postfix_lmtp") 447 ]); 448 449 users.users.mailman = { 450 description = "GNU Mailman"; 451 isSystemUser = true; 452 group = "mailman"; 453 }; 454 users.users.mailman-web = lib.mkIf (cfg.webUser == "mailman-web") { 455 description = "GNU Mailman web interface"; 456 isSystemUser = true; 457 group = "mailman"; 458 }; 459 users.groups.mailman = { }; 460 461 environment.etc."mailman3/settings.py".text = '' 462 import os 463 from configparser import ConfigParser 464 465 # Required by mailman_web.settings, but will be overridden when 466 # settings_local.json is loaded. 467 os.environ["SECRET_KEY"] = "" 468 469 from mailman_web.settings.base import * 470 from mailman_web.settings.mailman import * 471 472 import json 473 474 with open('${webSettingsJSON}') as f: 475 globals().update(json.load(f)) 476 477 with open('/var/lib/mailman-web/settings_local.json') as f: 478 globals().update(json.load(f)) 479 480 with open('/etc/mailman.cfg') as f: 481 config = ConfigParser() 482 config.read_file(f) 483 MAILMAN_REST_API_PASS = config['webservice']['admin_pass'] 484 485 ${lib.optionalString (cfg.ldap.enable) '' 486 import ldap 487 from django_auth_ldap.config import LDAPSearch, ${cfg.ldap.groupSearch.type} 488 AUTH_LDAP_SERVER_URI = "${cfg.ldap.serverUri}" 489 AUTH_LDAP_BIND_DN = "${cfg.ldap.bindDn}" 490 with open("${cfg.ldap.bindPasswordFile}") as f: 491 AUTH_LDAP_BIND_PASSWORD = f.read().rstrip('\n') 492 AUTH_LDAP_USER_SEARCH = LDAPSearch("${cfg.ldap.userSearch.ou}", 493 ldap.SCOPE_SUBTREE, "${cfg.ldap.userSearch.query}") 494 AUTH_LDAP_GROUP_TYPE = ${cfg.ldap.groupSearch.type}() 495 AUTH_LDAP_GROUP_SEARCH = LDAPSearch("${cfg.ldap.groupSearch.ou}", 496 ldap.SCOPE_SUBTREE, "${cfg.ldap.groupSearch.query}") 497 AUTH_LDAP_USER_ATTR_MAP = { 498 ${lib.concatStrings ( 499 lib.flip lib.mapAttrsToList cfg.ldap.attrMap ( 500 key: value: '' 501 "${key}": "${value}", 502 '' 503 ) 504 )} 505 } 506 ${lib.optionalString (cfg.ldap.superUserGroup != null) '' 507 AUTH_LDAP_USER_FLAGS_BY_GROUP = { 508 "is_superuser": "${cfg.ldap.superUserGroup}" 509 } 510 ''} 511 AUTHENTICATION_BACKENDS = ( 512 "django_auth_ldap.backend.LDAPBackend", 513 "django.contrib.auth.backends.ModelBackend" 514 ) 515 ''} 516 ''; 517 518 services.nginx = lib.mkIf (cfg.serve.enable && cfg.webHosts != [ ]) { 519 enable = lib.mkDefault true; 520 virtualHosts = lib.genAttrs cfg.webHosts (webHost: { 521 locations = { 522 ${cfg.serve.virtualRoot}.uwsgiPass = "unix:/run/mailman-web.socket"; 523 "${lib.removeSuffix "/" cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/"; 524 }; 525 }); 526 proxyTimeout = lib.mkDefault "120s"; 527 }; 528 529 environment.systemPackages = [ 530 (pkgs.buildEnv { 531 name = "mailman-tools"; 532 # We don't want to pollute the system PATH with a python 533 # interpreter etc. so let's pick only the stuff we actually 534 # want from {web,mailman}Env 535 pathsToLink = [ "/bin" ]; 536 paths = [ 537 mailmanEnv 538 webEnv 539 ]; 540 # Only mailman-related stuff is installed, the rest is removed 541 # in `postBuild`. 542 ignoreCollisions = true; 543 postBuild = '' 544 find $out/bin/ -mindepth 1 -not -name "mailman*" -delete 545 '' 546 + lib.optionalString config.security.sudo.enable '' 547 mv $out/bin/mailman $out/bin/.mailman-wrapped 548 echo '#!${pkgs.runtimeShell} 549 sudo=exec 550 if [[ "$USER" != mailman ]]; then 551 sudo="exec /run/wrappers/bin/sudo -u mailman" 552 fi 553 $sudo ${placeholder "out"}/bin/.mailman-wrapped "$@" 554 ' > $out/bin/mailman 555 chmod +x $out/bin/mailman 556 ''; 557 }) 558 ]; 559 560 services.postfix = lib.mkIf cfg.enablePostfix { 561 settings.main = { 562 owner_request_special = "no"; # Mailman handles -owner addresses on its own 563 recipient_delimiter = "+"; # bake recipient addresses in mail envelopes via VERP 564 }; 565 }; 566 567 systemd.sockets.mailman-uwsgi = lib.mkIf cfg.serve.enable { 568 wantedBy = [ "sockets.target" ]; 569 before = [ "nginx.service" ]; 570 socketConfig.ListenStream = "/run/mailman-web.socket"; 571 }; 572 systemd.services = { 573 mailman = { 574 description = "GNU Mailman Master Process"; 575 before = lib.optional cfg.enablePostfix "postfix.service"; 576 after = [ 577 "network.target" 578 ] 579 ++ lib.optional cfg.enablePostfix "postfix-setup.service" 580 ++ lib.optional withPostgresql "postgresql.target"; 581 restartTriggers = [ mailmanCfgFile ]; 582 requires = lib.optional withPostgresql "postgresql.target"; 583 wantedBy = [ "multi-user.target" ]; 584 serviceConfig = { 585 ExecStart = "${mailmanEnv}/bin/mailman start"; 586 ExecStop = "${mailmanEnv}/bin/mailman stop"; 587 User = "mailman"; 588 Group = "mailman"; 589 Type = "forking"; 590 RuntimeDirectory = "mailman"; 591 LogsDirectory = "mailman"; 592 PIDFile = "/run/mailman/master.pid"; 593 Restart = "on-failure"; 594 TimeoutStartSec = 180; 595 TimeoutStopSec = 180; 596 }; 597 }; 598 599 mailman-settings = { 600 description = "Generate settings files (including secrets) for Mailman"; 601 before = [ 602 "mailman.service" 603 "mailman-web-setup.service" 604 "mailman-uwsgi.service" 605 "hyperkitty.service" 606 ]; 607 requiredBy = [ 608 "mailman.service" 609 "mailman-web-setup.service" 610 "mailman-uwsgi.service" 611 "hyperkitty.service" 612 ]; 613 path = with pkgs; [ jq ]; 614 after = lib.optional withPostgresql "postgresql.target"; 615 requires = lib.optional withPostgresql "postgresql.target"; 616 serviceConfig.RemainAfterExit = true; 617 serviceConfig.Type = "oneshot"; 618 script = '' 619 install -m0750 -o mailman -g mailman ${mailmanCfgFile} /etc/mailman.cfg 620 ${ 621 if cfg.restApiPassFile == null then 622 '' 623 sed -i "s/#NIXOS_MAILMAN_REST_API_PASS_SECRET#/$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)/g" \ 624 /etc/mailman.cfg 625 '' 626 else 627 '' 628 ${pkgs.replace-secret}/bin/replace-secret \ 629 '#NIXOS_MAILMAN_REST_API_PASS_SECRET#' \ 630 ${cfg.restApiPassFile} \ 631 /etc/mailman.cfg 632 '' 633 } 634 635 mailmanDir=/var/lib/mailman 636 mailmanWebDir=/var/lib/mailman-web 637 638 mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg 639 mailmanWebCfg=$mailmanWebDir/settings_local.json 640 641 install -m 0775 -o mailman -g mailman -d /var/lib/mailman-web-static 642 install -m 0770 -o mailman -g mailman -d $mailmanDir 643 install -m 0770 -o ${cfg.webUser} -g mailman -d $mailmanWebDir 644 645 if [ ! -e $mailmanWebCfg ]; then 646 hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64) 647 secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64) 648 649 install -m 0440 -o root -g mailman \ 650 <(jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \ 651 --arg archiver_key "$hyperkittyApiKey" \ 652 --arg secret_key "$secretKey") \ 653 "$mailmanWebCfg" 654 fi 655 656 hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")" 657 mailmanCfgTmp=$(mktemp) 658 sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp" 659 chown mailman:mailman "$mailmanCfgTmp" 660 mv "$mailmanCfgTmp" "$mailmanCfg" 661 ''; 662 }; 663 664 mailman-web-setup = { 665 description = "Prepare mailman-web files and database"; 666 before = [ 667 "hyperkitty.service" 668 "mailman-uwsgi.service" 669 ]; 670 requiredBy = [ 671 "hyperkitty.service" 672 "mailman-uwsgi.service" 673 ]; 674 restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; 675 script = '' 676 [[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete 677 ${webEnv}/bin/mailman-web migrate 678 ${webEnv}/bin/mailman-web collectstatic 679 ${webEnv}/bin/mailman-web compress 680 ''; 681 serviceConfig = { 682 User = cfg.webUser; 683 Group = "mailman"; 684 Type = "oneshot"; 685 WorkingDirectory = "/var/lib/mailman-web"; 686 }; 687 }; 688 689 mailman-uwsgi = lib.mkIf cfg.serve.enable ( 690 let 691 uwsgiConfig = lib.recursiveUpdate { 692 uwsgi = { 693 type = "normal"; 694 plugins = [ "python3" ]; 695 home = webEnv; 696 http = "127.0.0.1:18507"; 697 buffer-size = 8192; 698 } 699 // ( 700 if cfg.serve.virtualRoot == "/" then 701 { module = "mailman_web.wsgi:application"; } 702 else 703 { 704 mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application"; 705 manage-script-name = true; 706 } 707 ); 708 } cfg.serve.uwsgiSettings; 709 uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig); 710 in 711 { 712 wantedBy = [ "multi-user.target" ]; 713 after = lib.optional withPostgresql "postgresql.target"; 714 requires = [ 715 "mailman-uwsgi.socket" 716 "mailman-web-setup.service" 717 ] 718 ++ lib.optional withPostgresql "postgresql.target"; 719 restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; 720 serviceConfig = { 721 # Since the mailman-web settings.py obstinately creates a logs 722 # dir in the cwd, change to the (writable) runtime directory before 723 # starting uwsgi. 724 ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${ 725 pkgs.uwsgi.override { 726 plugins = [ "python3" ]; 727 python3 = webEnv.python; 728 } 729 }/bin/uwsgi --json ${uwsgiConfigFile}"; 730 User = cfg.webUser; 731 Group = "mailman"; 732 RuntimeDirectory = "mailman-uwsgi"; 733 Restart = "on-failure"; 734 }; 735 } 736 ); 737 738 mailman-daily = { 739 description = "Trigger daily Mailman events"; 740 startAt = "daily"; 741 restartTriggers = [ mailmanCfgFile ]; 742 serviceConfig = { 743 ExecStart = "${mailmanEnv}/bin/mailman digests --send"; 744 User = "mailman"; 745 Group = "mailman"; 746 }; 747 }; 748 749 hyperkitty = lib.mkIf cfg.hyperkitty.enable { 750 description = "GNU Hyperkitty QCluster Process"; 751 after = [ "network.target" ]; 752 restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; 753 wantedBy = [ 754 "mailman.service" 755 "multi-user.target" 756 ]; 757 serviceConfig = { 758 ExecStart = "${webEnv}/bin/mailman-web qcluster"; 759 User = cfg.webUser; 760 Group = "mailman"; 761 WorkingDirectory = "/var/lib/mailman-web"; 762 Restart = "on-failure"; 763 }; 764 }; 765 } 766 // 767 lib.flip lib.mapAttrs' 768 { 769 "minutely" = "minutely"; 770 "quarter_hourly" = "*:00/15"; 771 "hourly" = "hourly"; 772 "daily" = "daily"; 773 "weekly" = "weekly"; 774 "yearly" = "yearly"; 775 } 776 ( 777 name: startAt: 778 lib.nameValuePair "hyperkitty-${name}" ( 779 lib.mkIf cfg.hyperkitty.enable { 780 description = "Trigger ${name} Hyperkitty events"; 781 inherit startAt; 782 restartTriggers = [ config.environment.etc."mailman3/settings.py".source ]; 783 serviceConfig = { 784 ExecStart = "${webEnv}/bin/mailman-web runjobs ${name}"; 785 User = cfg.webUser; 786 Group = "mailman"; 787 WorkingDirectory = "/var/lib/mailman-web"; 788 }; 789 } 790 ) 791 ); 792 }; 793 794 meta = { 795 maintainers = with lib.maintainers; [ qyliss ]; 796 doc = ./mailman.md; 797 }; 798 799}