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