aboutsummaryrefslogtreecommitdiff
path: root/nixpkgs/nixos/modules/services/mail
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/mail')
-rw-r--r--nixpkgs/nixos/modules/services/mail/dovecot.nix38
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailhog.nix68
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.nix455
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.xml59
-rw-r--r--nixpkgs/nixos/modules/services/mail/opendkim.nix30
-rw-r--r--nixpkgs/nixos/modules/services/mail/opensmtpd.nix23
-rw-r--r--nixpkgs/nixos/modules/services/mail/pfix-srsd.nix2
-rw-r--r--nixpkgs/nixos/modules/services/mail/postfix.nix90
-rw-r--r--nixpkgs/nixos/modules/services/mail/roundcube.nix18
9 files changed, 533 insertions, 250 deletions
diff --git a/nixpkgs/nixos/modules/services/mail/dovecot.nix b/nixpkgs/nixos/modules/services/mail/dovecot.nix
index 9fbf0c19752..c166ef68f29 100644
--- a/nixpkgs/nixos/modules/services/mail/dovecot.nix
+++ b/nixpkgs/nixos/modules/services/mail/dovecot.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ options, config, lib, pkgs, ... }:
with lib;
@@ -83,11 +83,11 @@ let
)
(
- optionalString (cfg.mailboxes != []) ''
+ optionalString (cfg.mailboxes != {}) ''
protocol imap {
namespace inbox {
inbox=yes
- ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)}
+ ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))}
}
}
''
@@ -125,15 +125,19 @@ let
mailboxConfig = mailbox: ''
mailbox "${mailbox.name}" {
auto = ${toString mailbox.auto}
+ '' + optionalString (mailbox.autoexpunge != null) ''
+ autoexpunge = ${mailbox.autoexpunge}
'' + optionalString (mailbox.specialUse != null) ''
special_use = \${toString mailbox.specialUse}
'' + "}";
- mailboxes = { ... }: {
+ mailboxes = { name, ... }: {
options = {
name = mkOption {
type = types.strMatching ''[^"]+'';
example = "Spam";
+ default = name;
+ readOnly = true;
description = "The name of the mailbox.";
};
auto = mkOption {
@@ -148,6 +152,15 @@ let
example = "Junk";
description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
};
+ autoexpunge = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "60d";
+ description = ''
+ To automatically remove all email from the mailbox which is older than the
+ specified time.
+ '';
+ };
};
};
in
@@ -323,9 +336,16 @@ in
};
mailboxes = mkOption {
- type = types.listOf (types.submodule mailboxes);
- default = [];
- example = [ { name = "Spam"; specialUse = "Junk"; auto = "create"; } ];
+ type = with types; coercedTo
+ (listOf unspecified)
+ (list: listToAttrs (map (entry: { name = entry.name; value = removeAttrs entry ["name"]; }) list))
+ (attrsOf (submodule mailboxes));
+ default = {};
+ example = literalExample ''
+ {
+ Spam = { specialUse = "Junk"; auto = "create"; };
+ }
+ '';
description = "Configure mailboxes and auto create or subscribe them.";
};
@@ -444,6 +464,10 @@ in
environment.systemPackages = [ dovecotPkg ];
+ warnings = mkIf (any isList options.services.dovecot2.mailboxes.definitions) [
+ "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03! See the release notes for more info for migration."
+ ];
+
assertions = [
{
assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
diff --git a/nixpkgs/nixos/modules/services/mail/mailhog.nix b/nixpkgs/nixos/modules/services/mail/mailhog.nix
index 0f998c6d0ea..b113f4ff3de 100644
--- a/nixpkgs/nixos/modules/services/mail/mailhog.nix
+++ b/nixpkgs/nixos/modules/services/mail/mailhog.nix
@@ -4,17 +4,59 @@ with lib;
let
cfg = config.services.mailhog;
-in {
+
+ args = lib.concatStringsSep " " (
+ [
+ "-api-bind-addr :${toString cfg.apiPort}"
+ "-smtp-bind-addr :${toString cfg.smtpPort}"
+ "-ui-bind-addr :${toString cfg.uiPort}"
+ "-storage ${cfg.storage}"
+ ] ++ lib.optional (cfg.storage == "maildir")
+ "-maildir-path $STATE_DIRECTORY"
+ ++ cfg.extraArgs
+ );
+
+in
+{
###### interface
+ imports = [
+ (mkRemovedOptionModule [ "services" "mailhog" "user" ] "")
+ ];
+
options = {
services.mailhog = {
enable = mkEnableOption "MailHog";
- user = mkOption {
- type = types.str;
- default = "mailhog";
- description = "User account under which mailhog runs.";
+
+ storage = mkOption {
+ type = types.enum [ "maildir" "memory" ];
+ default = "memory";
+ description = "Store mails on disk or in memory.";
+ };
+
+ apiPort = mkOption {
+ type = types.port;
+ default = 8025;
+ description = "Port on which the API endpoint will listen.";
+ };
+
+ smtpPort = mkOption {
+ type = types.port;
+ default = 1025;
+ description = "Port on which the SMTP endpoint will listen.";
+ };
+
+ uiPort = mkOption {
+ type = types.port;
+ default = 8025;
+ description = "Port on which the HTTP UI will listen.";
+ };
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of additional arguments to pass to the MailHog process.";
};
};
};
@@ -24,20 +66,16 @@ in {
config = mkIf cfg.enable {
- users.users.mailhog = {
- name = cfg.user;
- description = "MailHog service user";
- isSystemUser = true;
- };
-
systemd.services.mailhog = {
- description = "MailHog service";
+ description = "MailHog - Web and API based SMTP testing";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
- Type = "simple";
- ExecStart = "${pkgs.mailhog}/bin/MailHog";
- User = cfg.user;
+ Type = "exec";
+ ExecStart = "${pkgs.mailhog}/bin/MailHog ${args}";
+ DynamicUser = true;
+ Restart = "on-failure";
+ StateDirectory = "mailhog";
};
};
};
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.nix b/nixpkgs/nixos/modules/services/mail/mailman.nix
index f5e78b18293..5c61cfbebf6 100644
--- a/nixpkgs/nixos/modules/services/mail/mailman.nix
+++ b/nixpkgs/nixos/modules/services/mail/mailman.nix
@@ -6,42 +6,46 @@ let
cfg = config.services.mailman;
+ pythonEnv = pkgs.python3.withPackages (ps:
+ [ps.mailman ps.mailman-web]
+ ++ lib.optional cfg.hyperkitty.enable ps.mailman-hyperkitty
+ ++ cfg.extraPythonPackages);
+
# This deliberately doesn't use recursiveUpdate so users can
# override the defaults.
- settings = {
+ webSettings = {
DEFAULT_FROM_EMAIL = cfg.siteOwner;
SERVER_EMAIL = cfg.siteOwner;
ALLOWED_HOSTS = [ "localhost" "127.0.0.1" ] ++ cfg.webHosts;
COMPRESS_OFFLINE = true;
- STATIC_ROOT = "/var/lib/mailman-web/static";
+ STATIC_ROOT = "/var/lib/mailman-web-static";
MEDIA_ROOT = "/var/lib/mailman-web/media";
+ LOGGING = {
+ version = 1;
+ disable_existing_loggers = true;
+ handlers.console.class = "logging.StreamHandler";
+ loggers.django = {
+ handlers = [ "console" ];
+ level = "INFO";
+ };
+ };
+ HAYSTACK_CONNECTIONS.default = {
+ ENGINE = "haystack.backends.whoosh_backend.WhooshEngine";
+ PATH = "/var/lib/mailman-web/fulltext-index";
+ };
} // cfg.webSettings;
- settingsJSON = pkgs.writeText "settings.json" (builtins.toJSON settings);
-
- mailmanCfg = ''
- [mailman]
- site_owner: ${cfg.siteOwner}
- layout: fhs
-
- [paths.fhs]
- bin_dir: ${pkgs.python3Packages.mailman}/bin
- var_dir: /var/lib/mailman
- queue_dir: $var_dir/queue
- template_dir: $var_dir/templates
- log_dir: $var_dir/log
- lock_dir: $var_dir/lock
- etc_dir: /etc
- ext_dir: $etc_dir/mailman.d
- pid_file: /run/mailman/master.pid
- '' + optionalString cfg.hyperkitty.enable ''
-
- [archiver.hyperkitty]
- class: mailman_hyperkitty.Archiver
- enable: yes
- configuration: /var/lib/mailman/mailman-hyperkitty.cfg
+ webSettingsJSON = pkgs.writeText "settings.json" (builtins.toJSON webSettings);
+
+ # TODO: Should this be RFC42-ised so that users can set additional options without modifying the module?
+ mtaConfig = pkgs.writeText "mailman-postfix.cfg" ''
+ [postfix]
+ postmap_command: ${pkgs.postfix}/bin/postmap
+ transport_file_type: hash
'';
+ mailmanCfg = lib.generators.toINI {} cfg.settings;
+
mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" ''
[general]
# This is your HyperKitty installation, preferably on the localhost. This
@@ -84,7 +88,7 @@ in {
type = types.package;
default = pkgs.mailman;
defaultText = "pkgs.mailman";
- example = "pkgs.mailman.override { archivers = []; }";
+ example = literalExample "pkgs.mailman.override { archivers = []; }";
description = "Mailman package to use";
};
@@ -98,18 +102,6 @@ in {
'';
};
- webRoot = mkOption {
- type = types.path;
- default = "${pkgs.mailman-web}/${pkgs.python3.sitePackages}";
- defaultText = "\${pkgs.mailman-web}/\${pkgs.python3.sitePackages}";
- description = ''
- The web root for the Hyperkity + Postorius apps provided by Mailman.
- This variable can be set, of course, but it mainly exists so that site
- admins can refer to it in their own hand-written web server
- configuration files.
- '';
- };
-
webHosts = mkOption {
type = types.listOf types.str;
default = [];
@@ -124,7 +116,7 @@ in {
webUser = mkOption {
type = types.str;
- default = config.services.httpd.user;
+ default = "mailman-web";
description = ''
User to run mailman-web as
'';
@@ -138,6 +130,22 @@ in {
'';
};
+ serve = {
+ enable = mkEnableOption "Automatic nginx and uwsgi setup for mailman-web";
+ };
+
+ extraPythonPackages = mkOption {
+ description = "Packages to add to the python environment used by mailman and mailman-web";
+ type = types.listOf types.package;
+ default = [];
+ };
+
+ settings = mkOption {
+ description = "Settings for mailman.cfg";
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {};
+ };
+
hyperkitty = {
enable = mkEnableOption "the Hyperkitty archiver for Mailman";
@@ -158,6 +166,35 @@ in {
config = mkIf cfg.enable {
+ services.mailman.settings = {
+ mailman.site_owner = lib.mkDefault cfg.siteOwner;
+ mailman.layout = "fhs";
+
+ "paths.fhs" = {
+ bin_dir = "${pkgs.python3Packages.mailman}/bin";
+ var_dir = "/var/lib/mailman";
+ queue_dir = "$var_dir/queue";
+ template_dir = "$var_dir/templates";
+ log_dir = "/var/log/mailman";
+ lock_dir = "$var_dir/lock";
+ etc_dir = "/etc";
+ ext_dir = "$etc_dir/mailman.d";
+ pid_file = "/run/mailman/master.pid";
+ };
+
+ mta.configuration = lib.mkDefault "${mtaConfig}";
+
+ "archiver.hyperkitty" = lib.mkIf cfg.hyperkitty.enable {
+ class = "mailman_hyperkitty.Archiver";
+ enable = "yes";
+ configuration = "/var/lib/mailman/mailman-hyperkitty.cfg";
+ };
+ } // (let
+ loggerNames = ["root" "archiver" "bounce" "config" "database" "debug" "error" "fromusenet" "http" "locks" "mischief" "plugins" "runner" "smtp"];
+ loggerSectionNames = map (n: "logging.${n}") loggerNames;
+ in lib.genAttrs loggerSectionNames(name: { handler = "stderr"; })
+ );
+
assertions = let
inherit (config.services) postfix;
@@ -183,7 +220,17 @@ in {
(requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
];
- users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; };
+ users.users.mailman = {
+ description = "GNU Mailman";
+ isSystemUser = true;
+ group = "mailman";
+ };
+ users.users.mailman-web = lib.mkIf (cfg.webUser == "mailman-web") {
+ description = "GNU Mailman web interface";
+ isSystemUser = true;
+ group = "mailman";
+ };
+ users.groups.mailman = {};
environment.etc."mailman.cfg".text = mailmanCfg;
@@ -198,197 +245,193 @@ in {
import json
- with open('${settingsJSON}') as f:
+ with open('${webSettingsJSON}') as f:
globals().update(json.load(f))
with open('/var/lib/mailman-web/settings_local.json') as f:
globals().update(json.load(f))
'';
- environment.systemPackages = [ cfg.package ] ++ (with pkgs; [ mailman-web ]);
-
- services.postfix = {
- recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP
- config = {
- owner_request_special = "no"; # Mailman handles -owner addresses on its own
- };
- };
-
- systemd.services.mailman = {
- description = "GNU Mailman Master Process";
- after = [ "network.target" ];
- restartTriggers = [ config.environment.etc."mailman.cfg".source ];
- wantedBy = [ "multi-user.target" ];
- serviceConfig = {
- ExecStart = "${cfg.package}/bin/mailman start";
- ExecStop = "${cfg.package}/bin/mailman stop";
- User = "mailman";
- Type = "forking";
- RuntimeDirectory = "mailman";
- PIDFile = "/run/mailman/master.pid";
- };
- };
-
- systemd.services.mailman-settings = {
- description = "Generate settings files (including secrets) for Mailman";
- before = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
- requiredBy = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
- path = with pkgs; [ jq ];
- script = ''
- mailmanDir=/var/lib/mailman
- mailmanWebDir=/var/lib/mailman-web
-
- mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
- mailmanWebCfg=$mailmanWebDir/settings_local.json
-
- install -m 0700 -o mailman -g nogroup -d $mailmanDir
- install -m 0700 -o ${cfg.webUser} -g nogroup -d $mailmanWebDir
-
- if [ ! -e $mailmanWebCfg ]; then
- hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
- secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
-
- mailmanWebCfgTmp=$(mktemp)
- jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
- --arg archiver_key "$hyperkittyApiKey" \
- --arg secret_key "$secretKey" \
- >"$mailmanWebCfgTmp"
- chown ${cfg.webUser} "$mailmanWebCfgTmp"
- mv -n "$mailmanWebCfgTmp" $mailmanWebCfg
- fi
-
- hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY $mailmanWebCfg)"
- mailmanCfgTmp=$(mktemp)
- sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
- chown mailman "$mailmanCfgTmp"
- mv "$mailmanCfgTmp" $mailmanCfg
- '';
- serviceConfig = {
- Type = "oneshot";
- # RemainAfterExit makes restartIfChanged work for this service, so
- # downstream services will get updated automatically when things like
- # services.mailman.hyperkitty.baseUrl change. Otherwise users have to
- # restart things manually, which is confusing.
- RemainAfterExit = "yes";
+ services.nginx = mkIf cfg.serve.enable {
+ enable = mkDefault true;
+ virtualHosts."${lib.head cfg.webHosts}" = {
+ serverAliases = cfg.webHosts;
+ locations = {
+ "/".extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
+ "/static/".alias = webSettings.STATIC_ROOT + "/";
+ };
};
};
- systemd.services.mailman-web = {
- description = "Init Postorius DB";
- before = [ "httpd.service" "uwsgi.service" ];
- requiredBy = [ "httpd.service" "uwsgi.service" ];
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- script = ''
- ${pkgs.mailman-web}/bin/mailman-web migrate
- rm -rf static
- ${pkgs.mailman-web}/bin/mailman-web collectstatic
- ${pkgs.mailman-web}/bin/mailman-web compress
+ environment.systemPackages = [ (pkgs.buildEnv {
+ name = "mailman-tools";
+ # We don't want to pollute the system PATH with a python
+ # interpreter etc. so let's pick only the stuff we actually
+ # want from pythonEnv
+ pathsToLink = ["/bin"];
+ paths = [pythonEnv];
+ postBuild = ''
+ find $out/bin/ -mindepth 1 -not -name "mailman*" -delete
'';
- serviceConfig = {
- User = cfg.webUser;
- Type = "oneshot";
- # Similar to mailman-settings.service, this makes restartTriggers work
- # properly for this service.
- RemainAfterExit = "yes";
- WorkingDirectory = "/var/lib/mailman-web";
- };
- };
+ }) ];
- systemd.services.mailman-daily = {
- description = "Trigger daily Mailman events";
- startAt = "daily";
- restartTriggers = [ config.environment.etc."mailman.cfg".source ];
- serviceConfig = {
- ExecStart = "${cfg.package}/bin/mailman digests --send";
- User = "mailman";
- };
- };
-
- systemd.services.hyperkitty = {
- inherit (cfg.hyperkitty) enable;
- description = "GNU Hyperkitty QCluster Process";
- after = [ "network.target" ];
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- wantedBy = [ "mailman.service" "multi-user.target" ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web qcluster";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ services.postfix = {
+ recipientDelimiter = "+"; # bake recipient addresses in mail envelopes via VERP
+ config = {
+ owner_request_special = "no"; # Mailman handles -owner addresses on its own
};
};
- systemd.services.hyperkitty-minutely = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger minutely Hyperkitty events";
- startAt = "minutely";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs minutely";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
- };
+ systemd.sockets.mailman-uwsgi = lib.mkIf cfg.serve.enable {
+ wantedBy = ["sockets.target"];
+ before = ["nginx.service"];
+ socketConfig.ListenStream = "/run/mailman-web.socket";
};
-
- systemd.services.hyperkitty-quarter-hourly = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger quarter-hourly Hyperkitty events";
- startAt = "*:00/15";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs quarter_hourly";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ systemd.services = {
+ mailman = {
+ description = "GNU Mailman Master Process";
+ after = [ "network.target" ];
+ restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pythonEnv}/bin/mailman start";
+ ExecStop = "${pythonEnv}/bin/mailman stop";
+ User = "mailman";
+ Group = "mailman";
+ Type = "forking";
+ RuntimeDirectory = "mailman";
+ LogsDirectory = "mailman";
+ PIDFile = "/run/mailman/master.pid";
+ };
};
- };
- systemd.services.hyperkitty-hourly = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger hourly Hyperkitty events";
- startAt = "hourly";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs hourly";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ mailman-settings = {
+ description = "Generate settings files (including secrets) for Mailman";
+ before = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
+ requiredBy = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
+ path = with pkgs; [ jq ];
+ script = ''
+ mailmanDir=/var/lib/mailman
+ mailmanWebDir=/var/lib/mailman-web
+
+ mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
+ mailmanWebCfg=$mailmanWebDir/settings_local.json
+
+ install -m 0775 -o mailman -g mailman -d /var/lib/mailman-web-static
+ install -m 0770 -o mailman -g mailman -d $mailmanDir
+ install -m 0770 -o ${cfg.webUser} -g mailman -d $mailmanWebDir
+
+ if [ ! -e $mailmanWebCfg ]; then
+ hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+ secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+
+ mailmanWebCfgTmp=$(mktemp)
+ jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
+ --arg archiver_key "$hyperkittyApiKey" \
+ --arg secret_key "$secretKey" \
+ >"$mailmanWebCfgTmp"
+ chown root:mailman "$mailmanWebCfgTmp"
+ chmod 440 "$mailmanWebCfgTmp"
+ mv -n "$mailmanWebCfgTmp" "$mailmanWebCfg"
+ fi
+
+ hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY "$mailmanWebCfg")"
+ mailmanCfgTmp=$(mktemp)
+ sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
+ chown mailman:mailman "$mailmanCfgTmp"
+ mv "$mailmanCfgTmp" "$mailmanCfg"
+ '';
};
- };
- systemd.services.hyperkitty-daily = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger daily Hyperkitty events";
- startAt = "daily";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs daily";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ mailman-web-setup = {
+ description = "Prepare mailman-web files and database";
+ before = [ "uwsgi.service" "mailman-uwsgi.service" ];
+ requiredBy = [ "mailman-uwsgi.service" ];
+ restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+ script = ''
+ [[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete
+ ${pythonEnv}/bin/mailman-web migrate
+ ${pythonEnv}/bin/mailman-web collectstatic
+ ${pythonEnv}/bin/mailman-web compress
+ '';
+ serviceConfig = {
+ User = cfg.webUser;
+ Group = "mailman";
+ Type = "oneshot";
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
};
- };
- systemd.services.hyperkitty-weekly = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger weekly Hyperkitty events";
- startAt = "weekly";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs weekly";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ mailman-uwsgi = mkIf cfg.serve.enable (let
+ uwsgiConfig.uwsgi = {
+ type = "normal";
+ plugins = ["python3"];
+ home = pythonEnv;
+ module = "mailman_web.wsgi";
+ };
+ uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
+ in {
+ wantedBy = ["multi-user.target"];
+ requires = ["mailman-uwsgi.socket" "mailman-web-setup.service"];
+ restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+ serviceConfig = {
+ # Since the mailman-web settings.py obstinately creates a logs
+ # dir in the cwd, change to the (writable) runtime directory before
+ # starting uwsgi.
+ ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; }}/bin/uwsgi --json ${uwsgiConfigFile}";
+ User = cfg.webUser;
+ Group = "mailman";
+ RuntimeDirectory = "mailman-uwsgi";
+ };
+ });
+
+ mailman-daily = {
+ description = "Trigger daily Mailman events";
+ startAt = "daily";
+ restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+ serviceConfig = {
+ ExecStart = "${pythonEnv}/bin/mailman digests --send";
+ User = "mailman";
+ Group = "mailman";
+ };
};
- };
- systemd.services.hyperkitty-yearly = {
- inherit (cfg.hyperkitty) enable;
- description = "Trigger yearly Hyperkitty events";
- startAt = "yearly";
- restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
- serviceConfig = {
- ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs yearly";
- User = cfg.webUser;
- WorkingDirectory = "/var/lib/mailman-web";
+ hyperkitty = lib.mkIf cfg.hyperkitty.enable {
+ description = "GNU Hyperkitty QCluster Process";
+ after = [ "network.target" ];
+ restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+ wantedBy = [ "mailman.service" "multi-user.target" ];
+ serviceConfig = {
+ ExecStart = "${pythonEnv}/bin/mailman-web qcluster";
+ User = cfg.webUser;
+ Group = "mailman";
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
};
- };
+ } // flip lib.mapAttrs' {
+ "minutely" = "minutely";
+ "quarter_hourly" = "*:00/15";
+ "hourly" = "hourly";
+ "daily" = "daily";
+ "weekly" = "weekly";
+ "yearly" = "yearly";
+ } (name: startAt:
+ lib.nameValuePair "hyperkitty-${name}" (lib.mkIf cfg.hyperkitty.enable {
+ description = "Trigger ${name} Hyperkitty events";
+ inherit startAt;
+ restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+ serviceConfig = {
+ ExecStart = "${pythonEnv}/bin/mailman-web runjobs minutely";
+ User = cfg.webUser;
+ Group = "mailman";
+ WorkingDirectory = "/var/lib/mailman-web";
+ };
+ }));
+ };
+ meta = {
+ maintainers = with lib.maintainers; [ lheckemann ];
+ doc = ./mailman.xml;
};
}
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.xml b/nixpkgs/nixos/modules/services/mail/mailman.xml
new file mode 100644
index 00000000000..cbe50ed0b91
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailman.xml
@@ -0,0 +1,59 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="module-services-mailman">
+ <title>Mailman</title>
+ <para>
+ <link xlink:href="https://www.list.org">Mailman</link> is free
+ software for managing electronic mail discussion and e-newsletter
+ lists. Mailman and its web interface can be configured using the
+ corresponding NixOS module. Note that this service is best used with
+ an existing, securely configured Postfix setup, as it does not automatically configure this.
+ </para>
+
+ <section xml:id="module-services-mailman-basic-usage">
+ <title>Basic usage</title>
+ <para>
+ For a basic configuration, the following settings are suggested:
+ <programlisting>{ config, ... }: {
+ services.postfix = {
+ enable = true;
+ relayDomains = ["hash:/var/lib/mailman/data/postfix_domains"];
+ sslCert = config.security.acme.certs."lists.example.org".directory + "/full.pem";
+ sslKey = config.security.acme.certs."lists.example.org".directory + "/key.pem";
+ config = {
+ transport_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+ local_recipient_maps = ["hash:/var/lib/mailman/data/postfix_lmtp"];
+ };
+ };
+ services.mailman = {
+ <link linkend="opt-services.mailman.enable">enable</link> = true;
+ <link linkend="opt-services.mailman.serve.enable">serve.enable</link> = true;
+ <link linkend="opt-services.mailman.hyperkitty.enable">hyperkitty.enable</link> = true;
+ <link linkend="opt-services.mailman.hyperkitty.enable">webHosts</link> = ["lists.example.org"];
+ <link linkend="opt-services.mailman.hyperkitty.enable">siteOwner</link> = "mailman@example.org";
+ };
+ <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">services.nginx.virtualHosts."lists.example.org".enableACME</link> = true;
+ <link linkend="opt-services.mailman.hyperkitty.enable">networking.firewall.allowedTCPPorts</link> = [ 25 80 443 ];
+}</programlisting>
+ </para>
+ <para>
+ DNS records will also be required:
+ <itemizedlist>
+ <listitem><para><literal>AAAA</literal> and <literal>A</literal> records pointing to the host in question, in order for browsers to be able to discover the address of the web server;</para></listitem>
+ <listitem><para>An <literal>MX</literal> record pointing to a domain name at which the host is reachable, in order for other mail servers to be able to deliver emails to the mailing lists it hosts.</para></listitem>
+ </itemizedlist>
+ </para>
+ <para>
+ After this has been done and appropriate DNS records have been
+ set up, the Postorius mailing list manager and the Hyperkitty
+ archive browser will be available at
+ https://lists.example.org/. Note that this setup is not
+ sufficient to deliver emails to most email providers nor to
+ avoid spam -- a number of additional measures for authenticating
+ incoming and outgoing mails, such as SPF, DMARC and DKIM are
+ necessary, but outside the scope of the Mailman module.
+ </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/mail/opendkim.nix b/nixpkgs/nixos/modules/services/mail/opendkim.nix
index eb6a426684d..9bf6f338d93 100644
--- a/nixpkgs/nixos/modules/services/mail/opendkim.nix
+++ b/nixpkgs/nixos/modules/services/mail/opendkim.nix
@@ -129,6 +129,36 @@ in {
User = cfg.user;
Group = cfg.group;
RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
+ StateDirectory = "opendkim";
+ StateDirectoryMode = "0700";
+ ReadWritePaths = [ cfg.keyPath ];
+
+ AmbientCapabilities = [];
+ CapabilityBoundingSet = [];
+ DevicePolicy = "closed";
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateTmp = true;
+ PrivateUsers = true;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectSystem = "strict";
+ RemoveIPC = true;
+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6 AF_UNIX" ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ RestrictSUIDSGID = true;
+ SystemCallArchitectures = "native";
+ SystemCallFilter = [ "@system-service" "~@privileged @resources" ];
+ UMask = "0077";
};
};
diff --git a/nixpkgs/nixos/modules/services/mail/opensmtpd.nix b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
index 1fabe2da45c..c838d3b949d 100644
--- a/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
@@ -17,6 +17,10 @@ in {
###### interface
+ imports = [
+ (mkRenamedOptionModule [ "services" "opensmtpd" "addSendmailToSystemPath" ] [ "services" "opensmtpd" "setSendmail" ])
+ ];
+
options = {
services.opensmtpd = {
@@ -34,13 +38,10 @@ in {
description = "The OpenSMTPD package to use.";
};
- addSendmailToSystemPath = mkOption {
+ setSendmail = mkOption {
type = types.bool;
default = true;
- description = ''
- Whether to add OpenSMTPD's sendmail binary to the
- system path or not.
- '';
+ description = "Whether to set the system sendmail to OpenSMTPD's.";
};
extraServerArgs = mkOption {
@@ -82,7 +83,7 @@ in {
###### implementation
- config = mkIf cfg.enable {
+ config = mkIf cfg.enable rec {
users.groups = {
smtpd.gid = config.ids.gids.smtpd;
smtpq.gid = config.ids.gids.smtpq;
@@ -101,6 +102,14 @@ in {
};
};
+ security.wrappers.smtpctl = {
+ group = "smtpq";
+ setgid = true;
+ source = "${cfg.package}/bin/smtpctl";
+ };
+
+ services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl;
+
systemd.tmpfiles.rules = [
"d /var/spool/smtpd 711 root - - -"
"d /var/spool/smtpd/offline 770 root smtpq - -"
@@ -119,7 +128,5 @@ in {
serviceConfig.ExecStart = "${cfg.package}/sbin/smtpd -d -f ${conf} ${args}";
environment.OPENSMTPD_PROC_PATH = "${procEnv}/libexec/opensmtpd";
};
-
- environment.systemPackages = mkIf cfg.addSendmailToSystemPath [ sendmail ];
};
}
diff --git a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
index 38984f896d6..e3dbf2a014f 100644
--- a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
+++ b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
@@ -53,4 +53,4 @@ with lib;
};
};
};
-} \ No newline at end of file
+}
diff --git a/nixpkgs/nixos/modules/services/mail/postfix.nix b/nixpkgs/nixos/modules/services/mail/postfix.nix
index 608f64a68fb..fd4d16cdc37 100644
--- a/nixpkgs/nixos/modules/services/mail/postfix.nix
+++ b/nixpkgs/nixos/modules/services/mail/postfix.nix
@@ -25,6 +25,8 @@ let
clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
+ smtpTlsSecurityLevel = if cfg.useDane then "dane" else "may";
+
mainCf = let
escape = replaceStrings ["$"] ["$$"];
mkList = items: "\n " + concatStringsSep ",\n " items;
@@ -280,6 +282,17 @@ in
description = "Whether to enable smtp submission.";
};
+ enableSubmissions = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable smtp submission via smtps.
+
+ According to RFC 8314 this should be preferred
+ over STARTTLS for submission of messages by end user clients.
+ '';
+ };
+
submissionOptions = mkOption {
type = types.attrs;
default = {
@@ -298,6 +311,29 @@ in
description = "Options for the submission config in master.cf";
};
+ submissionsOptions = mkOption {
+ type = types.attrs;
+ default = {
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+ milter_macro_daemon_name = "ORIGINATING";
+ };
+ example = {
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_sasl_type = "dovecot";
+ smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+ milter_macro_daemon_name = "ORIGINATING";
+ };
+ description = ''
+ Options for the submission config via smtps in master.cf.
+
+ smtpd_tls_security_level will be set to encrypt, if it is missing
+ or has one of the values "may" or "none".
+
+ smtpd_tls_wrappermode with value "yes" will be added automatically.
+ '';
+ };
+
setSendmail = mkOption {
type = types.bool;
default = true;
@@ -454,7 +490,7 @@ in
'';
example = {
mail_owner = "postfix";
- smtp_use_tls = true;
+ smtp_tls_security_level = "may";
};
};
@@ -466,16 +502,26 @@ in
";
};
- sslCert = mkOption {
+ tlsTrustedAuthorities = mkOption {
type = types.str;
- default = "";
- description = "SSL certificate to use.";
+ default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ description = ''
+ File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This basically sets smtp_tls_CAfile and enables opportunistic tls. Defaults to NixOS trusted certification authorities.
+ '';
+ };
+
+ useDane = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Sets smtp_tls_security_level to "dane" rather than "may". See postconf(5) for details.
+ '';
};
- sslCACert = mkOption {
+ sslCert = mkOption {
type = types.str;
default = "";
- description = "SSL certificate of CA.";
+ description = "SSL certificate to use.";
};
sslKey = mkOption {
@@ -771,18 +817,20 @@ in
recipient_canonical_classes = [ "envelope_recipient" ];
}
// optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
+ // optionalAttrs (cfg.tlsTrustedAuthorities != "") {
+ smtp_tls_CAfile = cfg.tlsTrustedAuthorities;
+ smtp_tls_security_level = smtpTlsSecurityLevel;
+ }
// optionalAttrs (cfg.sslCert != "") {
- smtp_tls_CAfile = cfg.sslCACert;
smtp_tls_cert_file = cfg.sslCert;
smtp_tls_key_file = cfg.sslKey;
- smtp_use_tls = true;
+ smtp_tls_security_level = smtpTlsSecurityLevel;
- smtpd_tls_CAfile = cfg.sslCACert;
smtpd_tls_cert_file = cfg.sslCert;
smtpd_tls_key_file = cfg.sslKey;
- smtpd_use_tls = true;
+ smtpd_tls_security_level = "may";
};
services.postfix.masterConfig = {
@@ -878,6 +926,23 @@ in
command = "smtp";
args = [ "-o" "smtp_fallback_relay=" ];
};
+ } // optionalAttrs cfg.enableSubmissions {
+ submissions = {
+ type = "inet";
+ private = false;
+ command = "smtpd";
+ args = let
+ mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
+ adjustSmtpTlsSecurityLevel = !(cfg.submissionsOptions ? smtpd_tls_security_level) ||
+ cfg.submissionsOptions.smtpd_tls_security_level == "none" ||
+ cfg.submissionsOptions.smtpd_tls_security_level == "may";
+ submissionsOptions = cfg.submissionsOptions // {
+ smtpd_tls_wrappermode = "yes";
+ } // optionalAttrs adjustSmtpTlsSecurityLevel {
+ smtpd_tls_security_level = "encrypt";
+ };
+ in concatLists (mapAttrsToList mkKeyVal submissionsOptions);
+ };
};
}
@@ -900,4 +965,9 @@ in
services.postfix.mapFiles.client_access = checkClientAccessFile;
})
]);
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "postfix" "sslCACert" ]
+ "services.postfix.sslCACert was replaced by services.postfix.tlsTrustedAuthorities. In case you intend that your server should validate requested client certificates use services.postfix.extraConfig.")
+ ];
}
diff --git a/nixpkgs/nixos/modules/services/mail/roundcube.nix b/nixpkgs/nixos/modules/services/mail/roundcube.nix
index ed1439745ac..a0bbab64985 100644
--- a/nixpkgs/nixos/modules/services/mail/roundcube.nix
+++ b/nixpkgs/nixos/modules/services/mail/roundcube.nix
@@ -95,6 +95,18 @@ in
'';
};
+ maxAttachmentSize = mkOption {
+ type = types.int;
+ default = 18;
+ description = ''
+ The maximum attachment size in MB.
+
+ Note: Since roundcube only uses 70% of max upload values configured in php
+ 30% is added automatically to <xref linkend="opt-services.roundcube.maxAttachmentSize"/>.
+ '';
+ apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
+ };
+
extraConfig = mkOption {
type = types.lines;
default = "";
@@ -115,7 +127,7 @@ in
$config = array();
$config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
$config['log_driver'] = 'syslog';
- $config['max_message_size'] = '25M';
+ $config['max_message_size'] = '${cfg.maxAttachmentSize}';
$config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
$config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
$config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
@@ -172,8 +184,8 @@ in
phpOptions = ''
error_log = 'stderr'
log_errors = on
- post_max_size = 25M
- upload_max_filesize = 25M
+ post_max_size = ${cfg.maxAttachmentSize}
+ upload_max_filesize = ${cfg.maxAttachmentSize}
'';
settings = mapAttrs (name: mkDefault) {
"listen.owner" = "nginx";