From 153915794499552f3c7cd7dad9d42804a868fa0d Mon Sep 17 00:00:00 2001 From: mvl22 Date: Mon, 1 Jun 2026 10:39:58 +0100 Subject: [PATCH 1/2] Installation script for Mailman 2 (on Python 3) on Ubuntu 26.04 --- doc/install-ubuntu/04_exim4-config_mailman | 31 ++++ doc/install-ubuntu/101_exim4-config_mailman | 16 ++ doc/install-ubuntu/40_exim4-config_mailman | 12 ++ doc/install-ubuntu/README.md | 11 ++ doc/install-ubuntu/install-mailman.sh | 183 ++++++++++++++++++++ doc/install-ubuntu/lists.conf | 85 +++++++++ doc/install-ubuntu/mailman.service | 17 ++ doc/install-ubuntu/mm_cfg.py | 83 +++++++++ 8 files changed, 438 insertions(+) create mode 100644 doc/install-ubuntu/04_exim4-config_mailman create mode 100644 doc/install-ubuntu/101_exim4-config_mailman create mode 100644 doc/install-ubuntu/40_exim4-config_mailman create mode 100644 doc/install-ubuntu/README.md create mode 100755 doc/install-ubuntu/install-mailman.sh create mode 100644 doc/install-ubuntu/lists.conf create mode 100644 doc/install-ubuntu/mailman.service create mode 100644 doc/install-ubuntu/mm_cfg.py diff --git a/doc/install-ubuntu/04_exim4-config_mailman b/doc/install-ubuntu/04_exim4-config_mailman new file mode 100644 index 00000000..a21aafd7 --- /dev/null +++ b/doc/install-ubuntu/04_exim4-config_mailman @@ -0,0 +1,31 @@ +# start +# Home dir for your Mailman installation -- aka Mailman's prefix +# directory. +# By default this is set to "/usr/local/mailman" +# On a Red Hat/Fedora system using the RPM use "/var/mailman" +# On Debian using the deb package use "/var/lib/mailman" +# This is normally the same as ~mailman +MM_HOME=/var/lib/mailman +# +# User and group for Mailman, should match your --with-mail-gid +# switch to Mailman's configure script. Value is normally "mailman" +MM_UID=Debian-exim +MM_GID=Debian-exim +# +# Domains that your lists are in - colon separated list +# you may wish to add these into local_domains as well +domainlist mm_domains=lists.example.com +# +# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +# +# These values are derived from the ones above and should not need +# editing unless you have munged your mailman installation +# +# The path of the Mailman mail wrapper script +MM_WRAP=MM_HOME/mail/mailman +# +# The path of the list config file (used as a required file when +# verifying list addresses) +MM_LISTCHK=MM_HOME/lists/${lc::$local_part_data}/config.pck +# end + diff --git a/doc/install-ubuntu/101_exim4-config_mailman b/doc/install-ubuntu/101_exim4-config_mailman new file mode 100644 index 00000000..08ed517d --- /dev/null +++ b/doc/install-ubuntu/101_exim4-config_mailman @@ -0,0 +1,16 @@ +# Note: +# $local_part_data only gets set after $local_part has undergone some sort of lookup. +# The local_parts line uses $local_part to do a directory lookup in MM_HOME/lists, and returns the matching item. +# It then checks the local part against that value, which obviously it matches if the directory exists, and doesn't if it doesn't. +# If it does match, $local_part_data gets set. + +mailman_router: + driver = accept + local_parts = ${lookup {$local_part} dsearch,filter=dir {MM_HOME/lists}} + require_files = MM_HOME/lists/${local_part_data}/config.pck + local_part_suffix_optional + local_part_suffix = -bounces : -bounces+* : \ + -confirm+* : -join : -leave : \ + -owner : -request : -admin + domains = +mm_domains + transport = mailman_transport diff --git a/doc/install-ubuntu/40_exim4-config_mailman b/doc/install-ubuntu/40_exim4-config_mailman new file mode 100644 index 00000000..60b697e2 --- /dev/null +++ b/doc/install-ubuntu/40_exim4-config_mailman @@ -0,0 +1,12 @@ +mailman_transport: + driver = pipe + command = MM_WRAP \ + '${if def:local_part_suffix \ + {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \ + {post}}' \ + $local_part_data + current_directory = MM_HOME + home_directory = MM_HOME + user = MM_UID + group = MM_GID + diff --git a/doc/install-ubuntu/README.md b/doc/install-ubuntu/README.md new file mode 100644 index 00000000..844cffa1 --- /dev/null +++ b/doc/install-ubuntu/README.md @@ -0,0 +1,11 @@ +# Installation script for Mailman 2 (on Python 3) on Ubuntu 26.04 + +This directory contains an install script that will create a fully-working Mailman 2 (on Python 3) instance on a fresh Ubuntu 26.04 LTS machine. + +Usage (as root) - quote the list name and main e-mail address as arguments: + +``` +./install-mailman.sh lists.example.com webmaster@example.com +``` + +Script license: Public domain / CC0 (Creative Commons Zero). diff --git a/doc/install-ubuntu/install-mailman.sh b/doc/install-ubuntu/install-mailman.sh new file mode 100755 index 00000000..e17059c4 --- /dev/null +++ b/doc/install-ubuntu/install-mailman.sh @@ -0,0 +1,183 @@ +#!/bin/bash + + +# Script to install Mailman 2 running on Python 3 on Ubuntu 26.04 + +# Usage example: +# ./install-mailman.sh lists.example.com webmaster@example.com + +# Uses mailman2-python3 fork at: +# https://github.com/jaredmauch/mailman2-python3 + +# Installation documentation (for older Mailman2/Python2) at: +# https://www.gnu.org/software/mailman/mailman-install.pdf +# Useful notes (for older Mailman2/Python2 Ubuntu package) at: +# https://help.ubuntu.com/community/Mailman + +# NB You must ensure DNS hostname is set to the machine to ensure succesful SSL certificate creation + + +# Stop on error +set -e + +# Ensure this script is run as root +if [ "$(id -u)" != "0" ]; then + echo "This script must be run as root." 1>&2 + exit 1 +fi + +# Require hostname and e-mail arguments +display_usage() { + printf "Usage:\n./install-mailman.sh lists.example.com webmaster@example.com\n" +} +if [ $# -le 1 ] # If less than two arguments supplied, display usage +then + display_usage + exit 1 +fi +hostname=$1 +email=$2 + +# Update/patch machine +apt-get update +apt-get -y upgrade +apt-get -y dist-upgrade +apt-get -y autoremove + +# 1 Installation Requirements: Install Python3 +apt-get install -y build-essential +gcc --version +apt-get install python3 + +# 2.1 Add the group and user +groupadd -f mailman +id -u mailman &>/dev/null || useradd -c"GNU Mailman" -s /usr/sbin/nologin --no-create-home -g mailman mailman + +# Mailserver installation +apt-get -y install exim4 +usermod -a -G Debian-exim mailman + +# 2.2 Create the directory where the installation will be created +# NB If you use a directory such as /usr/local/mailman/ , you will need to logs and other directories to avoid AppArmor restrictions causing "OSError: [Errno 30] Read-only file system: '/usr/local/mailman/logs/error'" +# See: https://github.com/jaredmauch/mailman2-python3/issues/23 and https://linux-audit.com/systemd/settings/units/protectsystem/ +prefix=/var/lib/mailman +mkdir -p $prefix +chgrp -R mailman $prefix +chmod -R a+rx,g+ws $prefix + +# Obtain distribution +# See: https://github.com/jaredmauch/mailman2-python3/ +installDir=/tmp/mailman2-python3/ +if [ ! -d $installDir ]; then + apt-get install -y git + git clone https://github.com/jaredmauch/mailman2-python3.git $installDir +fi +chown -R mailman $installDir + +# 3 Build and install Mailman +# 3.1 Run configure +# Build, first adding build/runtime dependencies +apt-get install -y python3-dnspython python3-pip python3-legacy-cgi python3-html2text gettext python3-bsddb3 +# NB The following can be added, but it will be more self-explanatory to add these in $prefix/Mailman/mm_cfg.py: " --with-mailhost=$hostname --with-urlhost=$hostname" +cd $installDir +sudo -H -u mailman bash -c "./configure --prefix=$prefix --with-mail-gid=Debian-exim --with-cgi-gid=www-data" + +# 3.2 Make and install +sudo -H -u mailman bash -c 'make' +if [ ! -d "$prefix/Mailman/" ]; then + sudo -H -u mailman bash -c 'make install' +fi +cd - + +# 4 Check your installation +cd $prefix +sudo -H -u mailman bash -c "bin/check_perms -f" +cd - + +# 7 Review your site defaults +# Add in config +cp ./mm_cfg.py $prefix/Mailman/mm_cfg.py +chown mailman:mailman $prefix/Mailman/mm_cfg.py +sed -i "s/lists.example.com/${hostname}/g" $prefix/Mailman/mm_cfg.py + +# 5 Set up your web server +apt-get install -y apache2 +a2enmod rewrite cgid + +# Add HTTP VirtualHost +cp -pr ./lists.conf /etc/apache2/sites-available/ +sed -i "s/lists.example.com/${hostname}/g" /etc/apache2/sites-available/lists.conf +sed -i "s/webmaster@example.com/${email}/g" /etc/apache2/sites-available/lists.conf +mkdir -p /var/www/lists/ +a2ensite lists +service apache2 restart + +# Create SSL certificate, and enable the HTTPS (SSL) VirtualHost using this newly-created certificate +set +e # Allow this section to fail +apt-get install -y certbot +a2enmod ssl +certbot --agree-tos --no-eff-email certonly --keep-until-expiring --webroot -w /var/www/lists/ --email $email -d $hostname +if [ -f "/etc/letsencrypt/live/${hostname}/fullchain.pem" ]; then + sed -i "s/##//g" /etc/apache2/sites-available/lists.conf # Uncomment the ## lines from the template +fi +service apache2 restart +set -e # Revert to stop on fail + +# 6 Set up your mail server +# 6.2 Using the Exim mail server +# See: https://help.ubuntu.com/community/Mailman#Exim4_Configuration +# NB This uses split configuration +# Copy in Mailman files for Exim4 +cp -pr ./04_exim4-config_mailman /etc/exim4/conf.d/main/ +sed -i "s/lists.example.com/${hostname}/g" /etc/exim4/conf.d/main/04_exim4-config_mailman +cp -pr ./40_exim4-config_mailman /etc/exim4/conf.d/transport/ +cp -pr ./101_exim4-config_mailman /etc/exim4/conf.d/router/ +# Set dc_use_split_config to true, and ensure dc_other_hostnames has the new listserver hostname +sed -i -r "s/dc_use_split_config.+/dc_use_split_config='true'/" /etc/exim4/update-exim4.conf.conf +# Add hostname to dc_other_hostnames +if [ $(cat /etc/exim4/update-exim4.conf.conf | grep -c "${hostname}") -eq 0 ]; then + sed -i -E "s/dc_other_hostnames='([^']+)'/dc_other_hostnames='\1:${hostname}'/" /etc/exim4/update-exim4.conf.conf +fi +update-exim4.conf +service exim4 restart +exim -bP '+local_domains' # Verify config - should show the new listserver hostname + +# Set /etc/mailname (may not be necessary, and possibly not desirable if the machine has other mail functions) +#echo $domain > /etc/mailname + +# 8 Create a site-wide mailing list +apt-get install -y pwgen +if [ ! -d $prefix/lists/mailman ]; then + AUTOGENERATED_PASS=`pwgen -c -1 20` + echo "Site password is: $AUTOGENERATED_PASS" + $prefix/bin/newlist -q mailman $email $AUTOGENERATED_PASS +fi + +# 9 Set up cron; will create entry at /var/spool/cron/crontabs/mailman +crontab -u mailman $prefix/cron/crontab.in + +# Create service +cp ./mailman.service /etc/systemd/system/mailman.service +chown root:root /etc/systemd/system/mailman.service +systemctl daemon-reload +systemctl enable mailman.service + +# 10 Start the Mailman qrunner +#$prefix/bin/mailmanctl start # Manual start +systemctl start mailman.service + +# Confirm success +echo "Mailman installation complete!" +echo "Mailman is now running." +echo "The site password is shown above." +echo "You can monitor the Mailman error log at: $prefix/logs/error" + +# Migration notes +echo "" +echo "If you have existing list archives, they should be copied in to $prefix/archives/" +echo "Then set ownership of that directory throughout to mailman:mailman" +echo "" +echo "To migrate list configs from an old server, export and import using:" +echo "old-server$ config_list -o foo oldlistname" +echo "new-server$ config_list -i foo newlistname" + diff --git a/doc/install-ubuntu/lists.conf b/doc/install-ubuntu/lists.conf new file mode 100644 index 00000000..1c9d1f3e --- /dev/null +++ b/doc/install-ubuntu/lists.conf @@ -0,0 +1,85 @@ +# Config for Mailman lists site + +# HTTP host - redirect to SSL host + + ServerName lists.example.com + DocumentRoot /var/www/lists/ + CustomLog /var/log/apache2/lists.example.com_http-access.log combined + ErrorLog /var/log/apache2/lists.example.com_http-error.log +## RedirectMatch Permanent ^/(?!.well-known)(.*)$ https://lists.example.com/$1 + + +# SSL host +## +## ServerName lists.example.com +## ServerAdmin webmaster@example.com +## DocumentRoot /var/www/lists/ +## CustomLog /var/log/apache2/lists.example.com-access.log combined +## ErrorLog /var/log/apache2/lists.example.com-error.log +## LogLevel warn +## ServerSignature On +## +## # Enable SSL +## SSLEngine on +## SSLCertificateFile /etc/letsencrypt/live/lists.example.com/fullchain.pem +## SSLCertificateKeyFile /etc/letsencrypt/live/lists.example.com/privkey.pem +## +## +## Options FollowSymLinks +## AllowOverride None +## +## +## Options Indexes FollowSymLinks MultiViews +## AllowOverride None +## Require all granted +## +## +## AcceptPathInfo On +## +## Alias /pipermail/ /var/lib/mailman/archives/public/ +## Alias /icons/ /var/lib/mailman/icons/ +## ScriptAlias /admin /var/lib/mailman/cgi-bin/admin +## ScriptAlias /admindb /var/lib/mailman/cgi-bin/admindb +## ScriptAlias /confirm /var/lib/mailman/cgi-bin/confirm +## ScriptAlias /create /var/lib/mailman/cgi-bin/create +## ScriptAlias /edithtml /var/lib/mailman/cgi-bin/edithtml +## ScriptAlias /listinfo /var/lib/mailman/cgi-bin/listinfo +## ScriptAlias /options /var/lib/mailman/cgi-bin/options +## ScriptAlias /private /var/lib/mailman/cgi-bin/private +## ScriptAlias /rmlist /var/lib/mailman/cgi-bin/rmlist +## ScriptAlias /roster /var/lib/mailman/cgi-bin/roster +## ScriptAlias /subscribe /var/lib/mailman/cgi-bin/subscribe +## ScriptAlias /mailman/ /var/lib/mailman/cgi-bin/ +## +## +## AllowOverride None +## Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch +## Require all granted +## +## +## Options Indexes FollowSymlinks +## AllowOverride None +## Require all granted +## +## +## AllowOverride None +## Require all granted +## +## +## # Redirect to front page +## RedirectMatch Permanent ^/$ https://lists.example.com/mailman/admin +## +## # Use /mailman/private instead of /private if wished +## #Redirect /private/ /mailman/private/ +## +## # For a private installation, add additional HTTP auth if wished +## # +## # AuthType Basic +## # AuthName "You need to be authenticated" +## # AuthBasicProvider file +## # AuthUserFile /etc/apache2/.htpasswd +## # Require valid-user +## # +## +## + diff --git a/doc/install-ubuntu/mailman.service b/doc/install-ubuntu/mailman.service new file mode 100644 index 00000000..118f0954 --- /dev/null +++ b/doc/install-ubuntu/mailman.service @@ -0,0 +1,17 @@ +[Unit] +Description=Mailman Master Queue Runner +After=network.target + +[Service] +Type=forking +PIDFile=/var/lib/mailman/data/master-qrunner.pid +ExecStart=/var/lib/mailman/bin/mailmanctl -s start +ExecStop=/var/lib/mailman/bin/mailmanctl stop +ExecReload=/var/lib/mailman/bin/mailmanctl restart +Restart=always +RestartSec=3s +RestartPreventExitStatus=1 + +[Install] +WantedBy=multi-user.target +Alias=mailman-qrunner.service diff --git a/doc/install-ubuntu/mm_cfg.py b/doc/install-ubuntu/mm_cfg.py new file mode 100644 index 00000000..1d4386dc --- /dev/null +++ b/doc/install-ubuntu/mm_cfg.py @@ -0,0 +1,83 @@ +# -*- python -*- + +# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + + +"""This is the module which takes your site-specific settings. + +From a raw distribution it should be copied to mm_cfg.py. If you +already have an mm_cfg.py, be careful to add in only the new settings +you want. The complete set of distributed defaults, with annotation, +are in ./Defaults. In mm_cfg, override only those you want to +change, after the + + from Defaults import * + +line (see below). + +Note that these are just default settings - many can be overridden via the +admin and user interfaces on a per-list or per-user basis. + +Note also that some of the settings are resolved against the active list +setting by using the value as a format string against the +list-instance-object's dictionary - see the distributed value of +DEFAULT_MSG_FOOTER for an example.""" + + +####################################################### +# Here's where we get the distributed defaults. # +from Mailman.Defaults import * + +############################################################## +# Put YOUR site-specific configuration below, in mm_cfg.py . # +# See Defaults.py for explanations of the values. # + +#------------------------------------------------------------- +# If you change these, you have to configure your http server +# accordingly (Alias and ScriptAlias directives in most httpds) +DEFAULT_URL_PATTERN = 'https://%s/' + +#------------------------------------------------------------- +# Default domain for email addresses of newly created MLs +DEFAULT_EMAIL_HOST = 'lists.example.com' +#------------------------------------------------------------- +# Default host for web interface of newly created MLs +DEFAULT_URL_HOST = 'lists.example.com' +#------------------------------------------------------------- +# Required when setting any of its arguments. +add_virtualhost(DEFAULT_URL_HOST, DEFAULT_EMAIL_HOST) + +#------------------------------------------------------------- +# Unset send_reminders on newly created lists +DEFAULT_SEND_REMINDERS = 0 + +#------------------------------------------------------------- +# Uncomment this if you configured your MTA such that it +# automatically recognizes newly created lists. +# (see /usr/share/doc/mailman/README.Exim4.Debian or +# /usr/share/mailman/postfix-to-mailman.py) +MTA=None # Misnomer, suppresses alias output on newlist + +#------------------------------------------------------------- +# Uncomment if you want to filter mail with SpamAssassin. For +# more information please visit this website: +# http://www.jamesh.id.au/articles/mailman-spamassassin/ +# GLOBAL_PIPELINE.insert(1, 'SpamAssassin') + +# Note - if you're looking for something that is imported from mm_cfg, but you +# didn't find it above, it's probably in /usr/lib/mailman/Mailman/Defaults.py. From f0d5e9563ed903e2978c88e2c27b329590811437 Mon Sep 17 00:00:00 2001 From: mvl22 Date: Mon, 1 Jun 2026 10:44:05 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Update=20main=20README=20to=20reference=20?= =?UTF-8?q?=E2=80=8Edoc/install-ubuntu/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8a4d4517..012176fc 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,6 @@ this at https://github.com/cpanel/mailman2-python3 which you can/should also review. This is being used in production today. + +An installer for Ubuntu 26.04 (which should be translatable to other modern +Linux systems) is available in the ‎doc/install-ubuntu/ directory.