RockyLinux9.5で、Postfix+Mailman3をVirtuelEnvを使ってインストールする

メーリングリストでシェアの高いMailman。Sendmail時代にfmlと共に主流だったメーリングリストの仕組みは、今ではPostfix+Mailman2での構成をよく見かけます。
本構成は、Postfix+Mailman2の構成を行います。Mailman3は、Dockerコンテナなど様々な提供方法がありますが、本記事では、Python3.xとVirtualEnv機能を利用して、OSにインストールを行います。

## 0.あらかじめ準備をするもの
#### Postfix
- [Postfix+PostfixAdminでマルチドメイン対応のメールサーバーを作成する for RockyLinux 8.5](Postfix+PostfixAdminでマルチドメイン対応のメールサーバーを作成する for RockyLinux 8.5)

#### Python 3.x
- [Python3.xをインストール（ソースからビルド）for RockyLinux 9.x](https://qiita.com/shadowhat/items/025c49df5e45d6c1b665)

#### Apache 2.4
[Apache2.4をインストールする(ソースからコンパイル) for RockeyLinux 8.4 (systemd対応)](https://qiita.com/shadowhat/items/163ee5fdd56c51100e9e)

#### MariaDB(MySQL) or PostgreSQL ※本ページではMariaDBをベースに紹介
[MariaDB11.6.xのインストール（ソースからコンパイル）for RockyLinux 9.x and Ubuntu 22.04.2](https://qiita.com/shadowhat/items/3271fd58852bd11ac1db)

#### 必要なパッケージ
```
dnf install sassc
```


## 1.Pythonを入れる

```
cd /usr/local/src
wget https://www.python.org/ftp/python/3.12.8/Python-3.12.8.tgz
tar xvzf Python-3.12.8.tgz
cd Python-3.12.8

export CFLAGS="-I/usr/local/include -I/usr/local/ssl/include -L/usr/local/src/bzip2-1.0.8"
export LD_RUN_PATH=/usr/local/lib64:/usr/local/lib:/usr/local/ssl/lib:/usr/lib:/usr/lib64:/usr/local/mysql/lib
export PKG_CONFIG_PATH=/usr/local/ssl/pkgconfig:/usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib:/usr/lib64/pkgconfig
#export LTOFLAGS="-flto -fuse-linker-plugin -ffat-lto-objects -flto-partition = none"

./configure \
--with-ensurepip \
--enable-shared \
--enable-ipv6 \
--with-openssl=/usr/local/ssl \
--enable-optimizations \
--prefix=/usr/local/python-3.10.2 \
--with-system-ffi=/usr/local/lib64 \
--enable-loadable-sqlite-extensions
make -j 8
make install
```


## 2.ユーザーを追加する

```
useradd -m -d /usr/local/mailman -s /usr/bin/bash mailman
sudo chown mailman:mailman /usr/local/mailman
```

設定フォルダを作成する
```
mkdir -p /etc/mailman3
chown mailman:mailman /etc/mailman3
```


## 3.VirtualEnvを構成する

```
sudo su mailman
cd /usr/local/mailman

#suでmailmanに入ったら自動でvenvに入る＋環境変数をセット
vi ~/.bashrc
---
export LD_LIBRARY_PATH=/usr/local/mysql/lib:/usr/local/python-3.12.8/lib:/usr/local/ssl/lib:/usr/local/ssl:/usr/local/lib64:/usr/local/lib:/usr/lib:/usr/lib64
export PKG_CONFIG_PATH=/usr/local/mysql/lib/pkgconfig:/usr/local/ssl/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig
export PATH=/usr/local/mysql/bin:$PATH

export CFLAGS="-I/usr/local/python-3.12.8/include/python3.12 -I/usr/local/mysql/include/mysql"
export LDFLAGS="-L/usr/local/python-3.12.8/lib/python3.12/site-packages"

#export MYSQL_LIB=/usr/local/mysql/lib

source /usr/local/mailman/venv/bin/activate
---

保存する

#環境設定を行う
/usr/local/python-3.12.8/bin/python3 -m venv venv
#ventの中に入る
source venv/bin/activate
#Updateする
python -m pip install --upgrade pip
#pip3 install tox



#指定バージョンのバイナリをインストールしておく
#https://lists.mailman3.org/archives/list/mailman-users@mailman3.org/message/MZFWDTWVCFGH54WJDIQYUKEQ6WISME2P/
pip install mistune==2.0.0rc1
```

## 4.Mailman3をインストール (以下はrootで実行 )
併せて、PGSQLとMySQL対応にする

```
#export PATH=/usr/local/mysql/bin:$PATH
#export MYSQLCLIENT_CFLAGS=-I/usr/local/mysql/include/mysql
#export MYSQLCLIENT_LDFLAGS=-L/usr/local/mysql/lib
pip install wheel mailman psycopg2-binary mysqlclient
```

### 4.1 phpmysqlclientのインストールで、MariaDBのファイルとリンクしない
手動でインストールする（※必要に応じて利用する）

```
cd /usr/local/src
wget https://github.com/PyMySQL/mysqlclient/archive/refs/tags/v2.2.6.tar.gz
tar xvzf v2.2.6.tar.gz
cd mysqlclient-2.2.6
export #PKG_CONFIG_PATH=/usr/local/mysql/lib/pkgconfig:/usrt/local/ssl/lib/pkgconfig:/usr/local/python-3.12.8/lib/pkgconfig
#export CFLAGS="-I/usr/local/python-3.12.8/include/python3.12 -I/usr/local/mysql/include/mysql"
export LDFLAGS="-L/usr/local/python-3.12.8/lib/python3.12/site-packages -L/usr/local/mysql/lib"
```

makeファイルを書き換える
```:Makefile
.PHONY: build
build:
#python3で、インストールパスにに書き換える↓
         /usr/local/python-3.12.8/bin/python3 setup.py build_ext -if 

.PHONY: doc
doc:
        pip install .
        pip install sphinx
        cd doc && make html

.PHONY: clean
clean:
        find . -name '*.pyc' -delete
        find . -name '__pycache__' -delete
        rm -rf build

.PHONY: check
check:
        ruff *.py src ci
        black *.py src ci

```

Makeする
```
make
```

モジュールを確認する（正しくリンクしているか確認する）
```
cd /build/lib.linux-x86_64-cpython-312/MySQLdb
# ldd _mysql.cpython-312-x86_64-linux-gnu.so
        linux-vdso.so.1 (0x00007ffc25fef000)
        libmariadb.so.3 => /usr/local/mysql/lib/libmariadb.so.3 (0x00007f99be51f000)
        libz.so.1 => /usr/local/lib/libz.so.1 (0x00007f99be501000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f99be41a000)
        libssl.so.3 => /usr/local/ssl/lib/libssl.so.3 (0x00007f99be318000)
        libcrypto.so.3 => /usr/local/ssl/lib/libcrypto.so.3 (0x00007f99bdc00000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f99bd800000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f99be587000)
```



## 5.mailman.cfgを作成

```:/etc/mailman3/mailman.cfg
cat > /etc/mailman3/mailman.cfg << EOF
[paths.here]
var_dir: /usr/local/mailman/mm/var

[mailman]
layout: here
# This address is the "site owner" address.  Certain messages which must be
# delivered to a human, but which can't be delivered to a list owner (e.g. a
# bounce from a list owner), will be sent to this address.  It should point to
# a human.
site_owner: user@example.com

[database]
#class: mailman.database.postgresql.PostgreSQLDatabase
#url: postgres://mailman:mailman@127.0.0.1/mailman
class: mailman.database.mysql.MySQLDatabase
url: mysql://mailman:mailman@127.0.0.1/mailman

[archiver.prototype]
enable: yes

# For the HyperKitty archiver.
[archiver.hyperkitty]
class: mailman_hyperkitty.Archiver
enable: yes
configuration: /usr/local/mailman/mm/mailman-hyperkitty.cfg 
# And, create the /usr/local/mailman/mm/mailman-hyperkitty.cfg file containing
# these settings uncommented
#[general]
#↓ここを書き換える
#base_url: http://127.0.0.1/archives/
#api_key: Secret_Hyperkitty_API_Key

[shell]
history_file: $var_dir/history.py

[webservice]
hostname: localhost
port: 8001
use_https: no
admin_user: restadmin
admin_pass: restpass
api_version: 3.1

[mta]incoming: mailman.mta.postfix.LMTP
outgoing: mailman.mta.deliver.deliver
#↓必ず書き換える
lmtp_host: ml.example.com
lmtp_port: 8024
#↓必ず書き換える
smtp_host: ml.example.com
smtp_port: 25

verp_confirmations: yes
verp_personalized_deliveries: yes
verp_delivery_interval: 1

EOF
```

## 6.Postfixのmain.cfに追加

```:/etc/postfix/main.cf
recipient_delimiter = +
unknown_local_recipient_reject_code = 550
owner_request_special = no
transport_maps = hash:/usr/local/mailman/mm/var/data/postfix_lmtp
local_recipient_maps = hash:/usr/local/mailman/mm/var/data/postfix_lmtp
relay_domains = hash:/usr/local/mailman/mm/var/data/postfix_domains
```


## 7.WebUIをインストールする

```
pip install mailman-web mailman-hyperkitty
```

## 8.settings.pyを用意する

```:/etc/mailman3/settings.py
cat > /etc/mailman3/settings.py << EOF

# Mailman Web configuration file.
# /etc/mailman3/settings.py

from mailman_web.settings.base import *
from mailman_web.settings.mailman import *


#: Default list of admins who receive the emails from error logging.
ADMINS = (
    ('Mailman Suite Admin', 'root@localhost'),
)

# Database setup.
DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mailman',
        'USER': 'mailman',
        # TODO: Replace this with the password.
        'PASSWORD': 'mailman',
        #'HOST': 'localhost',
        'HOST': '127.0.0.1',
        #'PORT': '5432', #PGSQL
        'PORT': '3306', #MariaDB/MySQL
    }
}

# 'collectstatic' command will copy all the static files here.
# Alias this location from your webserver to "/static"
STATIC_ROOT = '/usr/local/mailman/web/static'


# Make sure that this directory is created or Django will fail on start.
LOGGING['handlers']['file']['filename'] = '/usr/local/mailman/web/logs/mailmanweb.log'

#: See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
#    "localhost",  # Archiving API from Mailman, keep it.
    "*",  # 接続テストのときはANYにしておく
    # "lists.your-domain.org",
    # Add here all production domains you have.
]

#: Current Django Site being served. This is used to customize the web host
#: being used to serve the current website. For more details about Django
#: site, see: https://docs.djangoproject.com/en/dev/ref/contrib/sites/
SITE_ID = 1

# Set this to a new secret value.
SECRET_KEY = 'MyVerrySecretKey'

# Set this to match the api_key setting in
# /opt/mailman/mm/mailman-hyperkitty.cfg (quoted here, not there).
MAILMAN_ARCHIVER_KEY = 'Secret_Hyperkitty_API_Key'

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
#EMAIL_HOST_USER = <username>
#EMAIL_HOST_PASSWORD = <password>

#文書検索
HAYSTACK_CONNECTIONS = {
      'default': {
      'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
      'PATH': os.path.join(BASE_DIR, "fulltext_index"),
      # You can also use the Xapian engine, it's faster and more accurate,
      # but requires another library.
      # http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian
      # Example configuration for Xapian:
      #'ENGINE': 'xapian_backend.XapianEngine'
      },
}

EOF
```

## 9.mailman-hyperkitty.cfgを用意する

```:/usr/local/mailman/mm/mailman-hyperkitty.cfg
mkdir /usr/local/mailman/mm

cat > /usr/local/mailman/mm/mailman-hyperkitty.cfg << EOF

# This is your HyperKitty installation, preferably on the localhost. This
# address will be used by Mailman to forward incoming emails to HyperKitty
# for archiving. It does not need to be publicly available, in fact it's
# better if it is not.
# However, if your Mailman installation is accessed via HTTPS, the URL needs
# to match your SSL certificate (e.g. https://lists.example.com/hyperkitty).
#base_url: http://127.0.0.1:8010/hyperkitty/
[general]
base_url: http://127.0.0.1/archives/

# Shared API key, must be the identical to the value in HyperKitty's
# settings.
api_key: Secret_Hyperkitty_API_Key

EOF
```

## 10.DBの準備

```
mkdir -p /usr/local/mailman/web/logs
chown mailman:mailman -R /usr/local/mailman/
#ln -s /usr/local/mysql/lib/libmariadb.so.3 /lib/libmariadb.so.3
```

この後にMySQLで、mailman用のユーザーとデーターベースを作成する


## 11.Webアプリケーションの初期設定

```
su mailman
cd ~
mailman-web migrate
mailman-web collectstatic
pip install libsass
mailman-web compress 
mailman-web compilemessages
```

## 12.Webアプリケーションの管理ユーザーを作成

```
su mailman
mailman-web createsuperuser
mailman aliases
```

## 13.DJango経由でのテストメールを送る

```
su mailman
mailman-web sendtestemail test@example.com
```

## 14.uWSGIをインストールする

```
su mailman
#unset LD_LIBRARY_PATH
pip install uwsgi
```

## 15.uwsgiの設定ファイルを準備する

```:/etc/mailman3/uwsgi.ini
cat > /etc/mailman3/uwsgi.ini << EOF

# /etc/mailman3/uwsgi.ini
#
[uwsgi]
# Port on which uwsgi will be listening.
#http-socket = 0.0.0.0:8000
socket = 0.0.0.0:8000
http = 0.0.0.0:8010
# If running uwsgi from the virtual environment ...
virtualenv = /usr/local/mailman/venv/

module=mailman_web.wsgi:application
# Add to python import path.
pythonpath = /etc/mailman3/
# The default settings module.
env = DJANGO_SETTINGS_MODULE=settings

# Setup default number of processes and threads per process.
master = true
processes = 2
threads = 2

# Setup the django_q related worker processes.
attach-daemon = /usr/local/mailman/venv/bin/mailman-web qcluster

# Setup the request log.
req-logger = file:/usr/local/mailman/web/logs/uwsgi.

# Log qcluster commands separately.
logger = qcluster file:/usr/local/mailman/web/logs/uwsgi-qcluster.log
log-route = qcluster uwsgi-daemons

# Last log and it logs the rest of the stuff.
logger = file:/usr/local/mailman/web/logs/uwsgi-error.log

EOF
```

## 16.mailmanwebの起動スクリプトを用意する

```:/usr/lib/systemd/system/mailmanweb.service
cat > /usr/lib/systemd/system/mailmanweb.service << EOF
[Unit]
Description=GNU Mailman Web UI
After=syslog.target NetworkManager.service mariadb.service mailman3.service

[Service]
Environment="PYTHONPATH=/etc/mailman3/"
Environment="LD_LIBRARY_PATH=/usr/local/ssl/lib:/usr/local/lib:/usr/lib:/usr/lib64:/usr/local/lib64:/usr/local/mysql/lib:/usr/local/python-3.12.8/lib"
U
User=mailman
Group=mailman
ExecStart=/usr/local/mailman/venv/bin/uwsgi --ini /etc/mailman3/uwsgi.ini



[Install]
WantedBy=multi-user.target

EOF
```


## 17.Mailmanの起動スクリプトを用意する

```:/usr/lib/systemd/system/mailman3.service
cat > /usr/lib/systemd/system/mailman3.service << EOF

[Unit]
Description=GNU Mailing List Manager
After=syslog.target NetworkManager.service mariadb.service

[Service]
Type=forking
PIDFile=/usr/local/mailman/mm/var/master.pid
User=mailman
Group=mailman
ExecStart=/usr/local/mailman/venv/bin/mailman start
ExecReload=/usr/local/mailman/venv/bin/mailman restart
ExecStop=/usr/local/mailman/venv/bin/mailman stop
Environment="LD_LIBRARY_PATH=/usr/local/lib:/usr/lib:/usr/lib64:/usr/local/lib64:/usr/local/mysql/lib:/usr/local/python-3.12.8/lib"

[Install]
WantedBy=multi-user.target

EOF
```

## 18.全文検索をインストール

```
su mailman
pip install whoosh
exit
```

## 19.サービスを起動・確認する

```
systemctl daemon-reload
systemctl start mailman3
systemctl enable mailman3
systemctl status mailman3
systemctl start mailmanweb
systemctl enable mailmanweb
```

mailman3を起動してもサービスが正常起動できない時がある。
この場合、サブプロセスがPythonのライブラリが見えてないために起動できていない可能性がある。
/usr/local/mailman/venv/bin/mailman start
で、起動時にどのような出力が出るかを確認して対処する。
```
ln -s /usr/local/python-3.12.8/lib/libpython3.12.so.1.0 /lib64/libpython3.12.so.1.0

```
で対応可能か確認する。

## 20.Cronの設定

```
su mailman
crontab -e
```

以下を追加

```
@daily /usr/local/mailman/venv/bin/mailman digests --periodic
@daily /usr/local/mailman/venv/bin/mailman notify
* * * * *          /usr/local/mailman/venv/bin/mailman-web runjobs minutely
0,15,30,45 * * * * /usr/local/mailman/venv/bin/mailman-web runjobs quarter_hourly
@hourly            /usr/local/mailman/venv/bin/mailman-web runjobs hourly
@daily             /usr/local/mailman/venv/bin/mailman-web runjobs daily
@weekly            /usr/local/mailman/venv/bin/mailman-web runjobs weekly
@monthly           /usr/local/mailman/venv/bin/mailman-web runjobs monthly
@yearly            /usr/local/mailman/venv/bin/mailman-web runjobs yearly
```

## 21.Apacheの設定変更を行う

```:/usr/local/apache2/conf/httpd.conf

#以下を追加

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so

<Location /mailman3/ >
  ProxyPass         uwsgi://127.0.0.1:8000/mailman3/
  ProxyPassReverse  uwsgi://127.0.0.1:8000/mailman3/
</Location>

<Location /accounts/ >
  ProxyPass         uwsgi://127.0.0.1:8000/accounts/
  ProxyPassReverse  uwsgi://127.0.0.1:8000/accounts/
</Location>

<Location /user-profile/ >
  ProxyPass         uwsgi://127.0.0.1:8000/user-profile/
  ProxyPassReverse  uwsgi://127.0.0.1:8000/user-profile/
</Location>

<Location /archives/ >
  ProxyPass         uwsgi://127.0.0.1:8000/archives/
  ProxyPassReverse  uwsgi://127.0.0.1:8000/archives/
</Location>

<Location /admin/ >
  ProxyPass         uwsgi://127.0.0.1:8000/admin/
  ProxyPassReverse  uwsgi://127.0.0.1:8000/admin/
</Location>

Alias   /static /usr/local/mailman/web/static
```

権限設定

```
chmod 755 /usr/local/mailman/
chmod -R 755 /usr/local/mailman/web/static
```

## 22.諸設定

### Web画面が「example.com」と表示される

MySQLの「django_site」テーブルの「name」項目を任意のものに書き換える。

## 参考:デフォルトの設定変更する

設定ファイルの場所
/usr/local/mailman/venv/lib/python3.12/site-packages/mailman/styles/base.py

パラメーター
mlist.preferred_language = 'ja' #日本語にする
mlist.subject_prefix = _('[$mlist.display_name %d] ') #件名に通し番号を入れる
mlist.reply_goes_to_list = ReplyToMunging.point_to_list #返信をMLにする