Things I did

mailman3 and postorius on Archlinux

Categories: HowTo
Tags: server archlinux mailman postfix

I needed to host some mailing lists, so I started setting up mailman3 on an Archlinux system. It was quite bumpy ride as the documentation in the Arch Wiki lacked some steps and other documentation was not made for Arch or completely outdated.

mailman3 is a rewrite of the mailman2 suite. The biggest differences is that the webui is now a second project, postorius, and needs to be setup independently. There is also an archiver, hyperkitty, which I don’t need and will not be part of this document.

First we need to install the necessary software:

    pacman -S postfix mailman3 postorius nginx gunicorn certbot-nginx

We need nginx for the postorius and gunicorn makes it possible to run postorius, which uses Django, as a systemd process. certbot-nginx is needed for the letsencrypt certificates.

Now we will enable and start the processes:

	systemctl enable mailman3.service 
	systemctl enable mailman3-digests.timer
	systemctl enable mailman3-gatenews.timer
	systemctl enable mailman3-notify.timer
	systemctl enable nginx

We need to create some files and set them up with the proper rights:

	touch /var/log/gunicorn-error.log
	chown postorius:postorius /var/log/gunicorn-error.log
	touch /var/log/gunicorn-access.log
	chown postorius:postorius /var/log/gunicorn-access.log

	mkdir /run/gunicorn
	chown -R postorius:postorius /run/gunicorn
	touch /srv/http/DOMAINNAME
	chown -R http:http /srv/http/

After first installation of postorius make sure to generate a database as the user postorius (user has shell /usr/bin/nologin in /etc/passwd):

	[postorius]$ django-admin migrate --pythonpath /usr/share/webapps/postorius/ --settings settings

Afterwards, the static data for the application needs to be collected:

	[postorius]$ django-admin collectstatic --pythonpath /usr/share/webapps/postorius/ --settings settings

Create a superuser account (listadmin) for the Django application. Give the admin user a working mailadress as it will be verified:

	[postorius]$ django-admin createsuperuser --pythonpath /usr/share/webapps/postorius/ --settings settings

Nginx config for the proxy to postorius:

	user http;
	worker_processes auto;
	events {
        worker_connections 768;
	}
	http {
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
        gzip on;
	server {
			server_name DOMAINNAME www.DOMAINNAME mail.DOMAINNAME;
    	    server_name_in_redirect off;
    	    root /srv/http/DOMAINNAME/www/;
			access_log  /var/log/nginx/DOMAINNAME-access.log;
			error_log /var/log/nginx/DOMAINNAME-error.log;
			index index.html index.php index.htm;
    	    location ~* ^.+\.(ico|js|gif|jpg|jpeg|png|bmp)$ {
    	    	expires 30d;
    	    }
    	    location ~ /\.ht {
				deny all;
    	    }
		}
	server {
    	server_name lists.DOMAINNAME;
    		access_log /var/log/nginx/lists.DOMAINNAME-access-postorius.log;
    		error_log /var/log/nginx/lists.DOMAINNAME-error-postorius.log;
    		location / {
        	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        	    proxy_set_header X-Forwarded-Host $host;
        	    proxy_set_header X-Forwarded-Server $host;
        	    proxy_set_header X-Forwarded-Proto $scheme;
        	    proxy_redirect off;
        	    proxy_set_header Host $http_host; 
        	    proxy_pass http://127.0.0.1:8000;
    		}
    		location /static/ {
        	    alias /var/lib/postorius/static/;
			}
		}
	}

/etc/mailman.cfg

	[mailman]
	layout: fhs
	site_owner: postmaster@DOMAINNAME
	default_language: de

	[webservice] # REST Service von Mailman. Wird von Web Frontend verwendet
	port: 8001
	admin_user: rest_admin_user
	admin_pass: rest_admin_password

	[database]
	url: sqlite:////var/lib/mailman/data/mailman.db

	[mta]
	incoming: mailman.mta.postfix.LMTP
	outgoing: mailman.mta.deliver.deliver
	lmtp_host: mail.DOMAINNAME
	lmtp_port: 8024
	smtp_host: mail.DOMAINNAME
	smtp_port: 25
	#smtp_secure_mode: starttls
	smtp_secure_mode: smtp

postfix main.conf:

	myhostname = MAILSERVER-DOMAIN
	mydestination = $myhostname, localhost, 127.0.0.1, DOMAINNAME
	mynetworks = localhost, PUBLICIP, 127.0.0.0/8
	inet_protocols = ipv4
	inet_interfaces = all

	alias_maps = hash:/etc/aliases
	
	smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service inet:127.0.0.1:10030

	compatibility_level=3.6

	recipient_delimiter = +
	unknown_local_recipient_reject_code = 550
	owner_request_special = no

	transport_maps = hash:/var/lib/mailman/data/postfix_lmtp
	local_recipient_maps = hash:/var/lib/mailman/data/postfix_lmtp
	relay_domains = hash:/var/lib/mailman/data/postfix_domains

	debug_peer_level = 15

	smtpd_sasl_auth_enable = yes
	smtpd_sasl_authenticated_header = yes
	smtpd_tls_auth_only = no
	smtp_use_tls = yes
	smtpd_use_tls = yes
	smtp_tls_note_starttls_offer = yes
	smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
	smtpd_tls_key_file = /etc/letsencrypt/live/CERTNAME/privkey.pem
	smtpd_tls_cert_file = /etc/letsencrypt/live/CERTNAME/fullchain.pem

	virtual_maps = hash:/etc/postfix/virtual

Enable and start nginx before creating the letsencrypt certificates

	systemctl enable nginx
	systemctl start nginx

Create letsencrypt certificate

	certbot --nginx -d kollhackerektiv.de -d www.DOMAINNAME -d lists.DOMAINNAME
	certbot --nginx -d mail.DOMAINNAME

Start postfix and mailman

	systemctl enable postfix
	systemctl start postfix
	systemctl start mailman3.service 
	systemctl start mailman3-digests.timer
	systemctl start mailman3-gatenews.timer
	systemctl start mailman3-notify.timer

/etc/systemd/system/gunicorn.service

	[Unit]
	Description=GNU Mailman web interfaces
	After=syslog.target network.target
	[Service]
	PIDFile=/run/gunicorn.pid
	WorkingDirectory=/usr/share/webapps/postorius
	ExecStart=/usr/bin/gunicorn -c /usr/share/webapps/postorius/gunicorn.conf wsgi:application
	ExecReload=/bin/kill -s HUP $MAINPID
	ExecStop=/bin/kill -s QUIT $MAINPID
	PrivateTmp=true
	# Change to a different user (and group) here
	User=postorius
	Group=postorius
	[Install]
	WantedBy=multi-user.target

/usr/share/webapps/postorius/gunicorn.conf

	#bind = ['127.0.0.1:8000']
	proc_name = "postorius"
	#chdir = "/opt/mailman/mm"
	chdir = "/usr/share/webapps/postorius/"
	pidfile = "/run/gunicorn/gunicorn.pid"
	accesslog = "/var/log/gunicorn-access.log"
	errorlog = "/var/log/gunicorn-error.log"
	#daemon = True
	workers = 4

/etc/tmpfiles.d/gunicorn.conf

D /run/gunicorn 0750 postorius postorius

settings_local.py for postorius. File is in /etc/webapps/postorius/

	DEFAULT_AUTO_FIELD='django.db.models.AutoField' 
	SECRET_KEY = 'secret'
	DEBUG = True
	MAILMAN_REST_API_URL = 'http://localhost:8001'
	MAILMAN_REST_API_USER = 'username'
	MAILMAN_REST_API_PASS = 'password'
	EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
	EMAIL_HOST = 'localhost'
	EMAIL_PORT = 25
	EMAIL_HOST_USER = ''
	EMAIL_HOST_PASSWORD = ''
	ALLOWED_HOSTS = [
	    'localhost',
	    '127.0.0.1',
	    'PUBLIC_IP',
	    'DOMAINNAME'
	]
	EMAIL_CONFIRMATION_FROM = 'postmaster@DOMAINNAME'
	POSTORIUS_TEMPLATE_BASE_URL = 'https://lists.DOMAINNAME'
	CSRF_TRUSTED_ORIGINS = [
	    'localhost',
	    '127.0.0.1',
	    'PUBLIC_IP',
	    'DOMAINNAME'
	]
	LANGUAGE_CODE = 'en-us'
	TIME_ZONE = 'Europe/Berlin'
	USE_TZ = True
	DEFAULT_FROM_EMAIL = 'postmaster@DOMAINNAME'

Start gunicorn:

	systemctl enable gunicorn.service
	systemctl start gunicorn.service

Hints 🔗

  • don’t use masquerade_domains in main.conf as it will masquerade the subdomain used for the list server.

  • Mailman commands should always be made as the mailman user, otherwise the permissions are fucked up

  • Switch every mailing list from digest to non digest mode when creating with postorius

Addons 🔗

  • Use DMARC, DKIM and SPF
  • Integrate dovecot for user accounts with IMAP and SMTP

Further reading 🔗

Categories