@ -6,6 +6,11 @@ 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 = {
@ -13,12 +18,28 @@ let
SERVER_EMAIL = cfg . siteOwner ;
ALLOWED_HOSTS = [ " l o c a l h o s t " " 1 2 7 . 0 . 0 . 1 " ] ++ cfg . webHosts ;
COMPRESS_OFFLINE = true ;
STATIC_ROOT = " / v a r / l i b / m a i l m a n - w e b / s t a t i c " ;
STATIC_ROOT = " / v a r / l i b / m a i l m a n - w e b - s t a t i c " ;
MEDIA_ROOT = " / v a r / l i b / m a i l m a n - w e b / m e d i a " ;
LOGGING = {
version = 1 ;
disable_existing_loggers = true ;
handlers . console . class = " l o g g i n g . S t r e a m H a n d l e r " ;
loggers . django = {
handlers = [ " c o n s o l e " ] ;
level = " I N F O " ;
} ;
} ;
} // cfg . webSettings ;
settingsJSON = pkgs . writeText " s e t t i n g s . j s o n " ( builtins . toJSON settings ) ;
# TODO: Should this be RFC42-ised so that users can set additional options without modifying the module?
mtaConfig = pkgs . writeText " m a i l m a n - p o s t f i x . c f g " ''
[ postfix ]
postmap_command : $ { pkgs . postfix } /bin/postmap
transport_file_type : hash
'' ;
mailmanCfg = ''
[ mailman ]
site_owner : $ { cfg . siteOwner }
@ -29,11 +50,14 @@ let
var_dir : /var/lib/mailman
queue_dir : $ var_dir/queue
template_dir : $ var_dir/templates
log_dir : $ var_dir/log
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 : $ { mtaConfig }
'' + o p t i o n a l S t r i n g c f g . h y p e r k i t t y . e n a b l e ''
[ archiver . hyperkitty ]
@ -84,7 +108,7 @@ in {
type = types . package ;
default = pkgs . mailman ;
defaultText = " p k g s . m a i l m a n " ;
example = "p k g s . m a i l m a n . o v e r r i d e { a r c h i v e r s = [ ] ; } " ;
example = literalExample "p k g s . m a i l m a n . o v e r r i d e { a r c h i v e r s = [ ] ; } " ;
description = " M a i l m a n p a c k a g e t o u s e " ;
} ;
@ -98,18 +122,6 @@ in {
'' ;
} ;
webRoot = mkOption {
type = types . path ;
default = " ${ pkgs . mailman-web } / ${ pkgs . python3 . sitePackages } " ;
defaultText = " \$ { p k g s . m a i l m a n - w e b } / \$ { p k g s . p y t h o n 3 . s i t e P a c k a g e s } " ;
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 +136,7 @@ in {
webUser = mkOption {
type = types . str ;
default = config . services . httpd . user ;
default = " m a i l m a n - w e b " ;
description = ''
User to run mailman-web as
'' ;
@ -138,6 +150,16 @@ in {
'' ;
} ;
serve = {
enable = mkEnableOption " A u t o m a t i c n g i n x a n d u w s g i s e t u p f o r m a i l m a n - w e b " ;
} ;
extraPythonPackages = mkOption {
description = " P a c k a g e s t o a d d t o t h e p y t h o n e n v i r o n m e n t u s e d b y m a i l m a n a n d m a i l m a n - w e b " ;
type = types . listOf types . package ;
default = [ ] ;
} ;
hyperkitty = {
enable = mkEnableOption " t h e H y p e r k i t t y a r c h i v e r f o r M a i l m a n " ;
@ -183,7 +205,17 @@ in {
( requirePostfixHash [ " c o n f i g " " l o c a l _ r e c i p i e n t _ m a p s " ] " p o s t f i x _ l m t p " )
] ;
users . users . mailman = { description = " G N U M a i l m a n " ; isSystemUser = true ; } ;
users . users . mailman = {
description = " G N U M a i l m a n " ;
isSystemUser = true ;
group = " m a i l m a n " ;
} ;
users . users . mailman-web = lib . mkIf ( cfg . webUser == " m a i l m a n - w e b " ) {
description = " G N U M a i l m a n w e b i n t e r f a c e " ;
isSystemUser = true ;
group = " m a i l m a n " ;
} ;
users . groups . mailman = { } ;
environment . etc . " m a i l m a n . c f g " . text = mailmanCfg ;
@ -205,190 +237,181 @@ in {
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 = " n o " ; # Mailman handles -owner addresses on its own
} ;
} ;
systemd . services . mailman = {
description = " G N U M a i l m a n M a s t e r P r o c e s s " ;
after = [ " n e t w o r k . t a r g e t " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n . c f g " . source ] ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
serviceConfig = {
ExecStart = " ${ cfg . package } / b i n / m a i l m a n s t a r t " ;
ExecStop = " ${ cfg . package } / b i n / m a i l m a n s t o p " ;
User = " m a i l m a n " ;
Type = " f o r k i n g " ;
RuntimeDirectory = " m a i l m a n " ;
PIDFile = " / r u n / m a i l m a n / m a s t e r . p i d " ;
} ;
} ;
systemd . services . mailman-settings = {
description = " G e n e r a t e s e t t i n g s f i l e s ( i n c l u d i n g s e c r e t s ) f o r M a i l m a n " ;
before = [ " m a i l m a n . s e r v i c e " " m a i l m a n - w e b . s e r v i c e " " h y p e r k i t t y . s e r v i c e " " h t t p d . s e r v i c e " " u w s g i . s e r v i c e " ] ;
requiredBy = [ " m a i l m a n . s e r v i c e " " m a i l m a n - w e b . s e r v i c e " " h y p e r k i t t y . s e r v i c e " " h t t p d . s e r v i c e " " u w s g i . s e r v i c e " ] ;
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 " $ h y p e r k i t t y A p i K e y " \
- - arg secret_key " $ s e c r e t K e y " \
> " $ m a i l m a n W e b C f g T m p "
chown $ { cfg . webUser } " $ m a i l m a n W e b C f g T m p "
mv - n " $ m a i l m a n W e b C f g T m p " $ mailmanWebCfg
fi
hyperkittyApiKey = " $ ( j q - r . M A I L M A N _ A R C H I V E R _ K E Y $ m a i l m a n W e b C f g ) "
mailmanCfgTmp = $ ( mktemp )
sed " s / @ A P I _ K E Y @ / $ h y p e r k i t t y A p i K e y / g " $ { mailmanHyperkittyCfg } > " $ m a i l m a n C f g T m p "
chown mailman " $ m a i l m a n C f g T m p "
mv " $ m a i l m a n C f g T m p " $ mailmanCfg
'' ;
serviceConfig = {
Type = " o n e s h o t " ;
# 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 = " y e s " ;
services . nginx = mkIf cfg . serve . enable {
enable = mkDefault true ;
virtualHosts . " ${ lib . head cfg . webHosts } " = {
serverAliases = cfg . webHosts ;
locations = {
" / " . extraConfig = " u w s g i _ p a s s u n i x : / r u n / m a i l m a n - w e b . s o c k e t ; " ;
" / s t a t i c / " . alias = settings . STATIC_ROOT + " / " ;
} ;
} ;
} ;
systemd . services . mailman-web = {
description = " I n i t P o s t o r i u s D B " ;
before = [ " h t t p d . s e r v i c e " " u w s g i . s e r v i c e " ] ;
requiredBy = [ " h t t p d . s e r v i c e " " u w s g i . s e r v i c e " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . 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 = " m a i l m a n - t o o l s " ;
# 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 = [ " / b i n " ] ;
paths = [ pythonEnv ] ;
postBuild = ''
find $ out/bin / - mindepth 1 - not - name " m a i l m a n * " - delete
'' ;
serviceConfig = {
User = cfg . webUser ;
Type = " o n e s h o t " ;
# Similar to mailman-settings.service, this makes restartTriggers work
# properly for this service.
RemainAfterExit = " y e s " ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
} ;
} ;
systemd . services . mailman-daily = {
description = " T r i g g e r d a i l y M a i l m a n e v e n t s " ;
startAt = " d a i l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n . c f g " . source ] ;
serviceConfig = {
ExecStart = " ${ cfg . package } / b i n / m a i l m a n d i g e s t s - - s e n d " ;
User = " m a i l m a n " ;
} ;
} ;
} ) ] ;
systemd . services . hyperkitty = {
inherit ( cfg . hyperkitty ) enable ;
description = " G N U H y p e r k i t t y Q C l u s t e r P r o c e s s " ;
after = [ " n e t w o r k . t a r g e t " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
wantedBy = [ " m a i l m a n . s e r v i c e " " m u l t i - u s e r . t a r g e t " ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b q c l u s t e r " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
services . postfix = {
recipientDelimiter = " + " ; # bake recipient addresses in mail envelopes via VERP
config = {
owner_request_special = " n o " ; # Mailman handles -owner addresses on its own
} ;
} ;
systemd . services . hyperkitty-minutely = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r m i n u t e l y H y p e r k i t t y e v e n t s " ;
startAt = " m i n u t e l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s m i n u t e l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
} ;
systemd . sockets . mailman-uwsgi = lib . mkIf cfg . serve . enable {
wantedBy = [ " s o c k e t s . t a r g e t " ] ;
before = [ " n g i n x . s e r v i c e " ] ;
socketConfig . ListenStream = " / r u n / m a i l m a n - w e b . s o c k e t " ;
} ;
systemd . services . hyperkitty-quarter-hourly = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r q u a r t e r - h o u r l y H y p e r k i t t y e v e n t s " ;
startAt = " * : 0 0 / 1 5 " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s q u a r t e r _ h o u r l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
systemd . services = {
mailman = {
description = " G N U M a i l m a n M a s t e r P r o c e s s " ;
after = [ " n e t w o r k . t a r g e t " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n . c f g " . source ] ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
serviceConfig = {
ExecStart = " ${ pythonEnv } / b i n / m a i l m a n s t a r t " ;
ExecStop = " ${ pythonEnv } / b i n / m a i l m a n s t o p " ;
User = " m a i l m a n " ;
Group = " m a i l m a n " ;
Type = " f o r k i n g " ;
RuntimeDirectory = " m a i l m a n " ;
LogsDirectory = " m a i l m a n " ;
PIDFile = " / r u n / m a i l m a n / m a s t e r . p i d " ;
} ;
} ;
} ;
systemd . services . hyperkitty-hourly = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r h o u r l y H y p e r k i t t y e v e n t s " ;
startAt = " h o u r l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s h o u r l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
mailman-settings = {
description = " G e n e r a t e s e t t i n g s f i l e s ( i n c l u d i n g s e c r e t s ) f o r M a i l m a n " ;
before = [ " m a i l m a n . s e r v i c e " " m a i l m a n - w e b - s e t u p . s e r v i c e " " m a i l m a n - u w s g i . s e r v i c e " " h y p e r k i t t y . s e r v i c e " ] ;
requiredBy = [ " m a i l m a n . s e r v i c e " " m a i l m a n - w e b - s e t u p . s e r v i c e " " m a i l m a n - u w s g i . s e r v i c e " " h y p e r k i t t y . s e r v i c e " ] ;
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 " $ h y p e r k i t t y A p i K e y " \
- - arg secret_key " $ s e c r e t K e y " \
> " $ m a i l m a n W e b C f g T m p "
chown root:mailman " $ m a i l m a n W e b C f g T m p "
chmod 440 " $ m a i l m a n W e b C f g T m p "
mv - n " $ m a i l m a n W e b C f g T m p " " $ m a i l m a n W e b C f g "
fi
hyperkittyApiKey = " $ ( j q - r . M A I L M A N _ A R C H I V E R _ K E Y " $ mailmanWebCfg " ) "
mailmanCfgTmp = $ ( mktemp )
sed " s / @ A P I _ K E Y @ / $ h y p e r k i t t y A p i K e y / g " $ { mailmanHyperkittyCfg } > " $ m a i l m a n C f g T m p "
chown mailman:mailman " $ m a i l m a n C f g T m p "
mv " $ m a i l m a n C f g T m p " " $ m a i l m a n C f g "
'' ;
} ;
} ;
systemd . services . hyperkitty-daily = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r d a i l y H y p e r k i t t y e v e n t s " ;
startAt = " d a i l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s d a i l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
mailman-web-setup = {
description = " P r e p a r e m a i l m a n - w e b f i l e s a n d d a t a b a s e " ;
before = [ " u w s g i . s e r v i c e " " m a i l m a n - u w s g i . s e r v i c e " ] ;
requiredBy = [ " m a i l m a n - u w s g i . s e r v i c e " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
script = ''
find " ${ settings . 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 = " m a i l m a n " ;
Type = " o n e s h o t " ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
} ;
} ;
} ;
systemd . services . hyperkitty-weekly = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r w e e k l y H y p e r k i t t y e v e n t s " ;
startAt = " w e e k l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s w e e k l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
mailman-uwsgi = mkIf cfg . serve . enable ( let
uwsgiConfig . uwsgi = {
type = " n o r m a l " ;
plugins = [ " p y t h o n 3 " ] ;
home = pythonEnv ;
module = " m a i l m a n _ w e b . w s g i " ;
} ;
uwsgiConfigFile = pkgs . writeText " u w s g i - m a i l m a n . j s o n " ( builtins . toJSON uwsgiConfig ) ;
in {
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
requires = [ " m a i l m a n - u w s g i . s o c k e t " " m a i l m a n - w e b - s e t u p . s e r v i c e " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . 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 } / b i n / e n v - C $ R U N T I M E _ D I R E C T O R Y ${ pkgs . uwsgi . override { plugins = [ " p y t h o n 3 " ] ; } } / b i n / u w s g i - - j s o n ${ uwsgiConfigFile } " ;
User = cfg . webUser ;
Group = " m a i l m a n " ;
RuntimeDirectory = " m a i l m a n - u w s g i " ;
} ;
} ) ;
mailman-daily = {
description = " T r i g g e r d a i l y M a i l m a n e v e n t s " ;
startAt = " d a i l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n . c f g " . source ] ;
serviceConfig = {
ExecStart = " ${ pythonEnv } / b i n / m a i l m a n d i g e s t s - - s e n d " ;
User = " m a i l m a n " ;
Group = " m a i l m a n " ;
} ;
} ;
} ;
systemd . services . hyperkitty-yearly = {
inherit ( cfg . hyperkitty ) enable ;
description = " T r i g g e r y e a r l y H y p e r k i t t y e v e n t s " ;
startAt = " y e a r l y " ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pkgs . mailman-web } / b i n / m a i l m a n - w e b r u n j o b s y e a r l y " ;
User = cfg . webUser ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
hyperkitty = lib . mkIf cfg . hyperkitty . enable {
description = " G N U H y p e r k i t t y Q C l u s t e r P r o c e s s " ;
after = [ " n e t w o r k . t a r g e t " ] ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
wantedBy = [ " m a i l m a n . s e r v i c e " " m u l t i - u s e r . t a r g e t " ] ;
serviceConfig = {
ExecStart = " ${ pythonEnv } / b i n / m a i l m a n - w e b q c l u s t e r " ;
User = cfg . webUser ;
Group = " m a i l m a n " ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
} ;
} ;
} ;
} // flip lib . mapAttrs' {
" m i n u t e l y " = " m i n u t e l y " ;
" q u a r t e r _ h o u r l y " = " * : 0 0 / 1 5 " ;
" h o u r l y " = " h o u r l y " ;
" d a i l y " = " d a i l y " ;
" w e e k l y " = " w e e k l y " ;
" y e a r l y " = " y e a r l y " ;
} ( name : startAt :
lib . nameValuePair " h y p e r k i t t y - ${ name } " ( lib . mkIf cfg . hyperkitty . enable {
description = " T r i g g e r ${ name } H y p e r k i t t y e v e n t s " ;
inherit startAt ;
restartTriggers = [ config . environment . etc . " m a i l m a n 3 / s e t t i n g s . p y " . source ] ;
serviceConfig = {
ExecStart = " ${ pythonEnv } / b i n / m a i l m a n - w e b r u n j o b s m i n u t e l y " ;
User = cfg . webUser ;
Group = " m a i l m a n " ;
WorkingDirectory = " / v a r / l i b / m a i l m a n - w e b " ;
} ;
} ) ) ;
} ;
}