forked from ISTI-ansible-roles/ansible-role-mailman
167 lines
7.3 KiB
Markdown
167 lines
7.3 KiB
Markdown
# mailman
|
|
|
|
The site-wide antispam defaults are deliberately empty:
|
|
|
|
```yaml
|
|
mailman_antispam_header_checks: []
|
|
mailman_antispam_chain_behaviour: discard
|
|
```
|
|
|
|
Mailing-list owners can manage list-specific header matches and select their
|
|
action (`accept`, `discard`, `hold`, or `reject`) from Postorius. The global
|
|
chain is used only when a matching rule has no explicit action.
|
|
|
|
Existing lists without any header matches can be initialized through the
|
|
Mailman database model by enabling the optional seed operation:
|
|
|
|
```yaml
|
|
mailman_seed_empty_list_header_matches: true
|
|
mailman_empty_list_header_matches:
|
|
- header: 'X-Spam-Flag'
|
|
pattern: '^YES$'
|
|
action: 'discard'
|
|
```
|
|
|
|
The operation is idempotent. A list is modified only when its header-match
|
|
collection is empty; lists with one or more existing rules are left untouched.
|
|
|
|
## Verified weekly restart
|
|
|
|
The optional weekly restart performs separate stop and start operations. It
|
|
waits for systemd, `mailman status`, and the configured LMTP listener to confirm
|
|
that Mailman has stopped before starting it. It then requires the master and
|
|
the LMTP port to become available before considering startup successful:
|
|
|
|
```yaml
|
|
mailman_enable_weekly_verified_restart: true
|
|
mailman_weekly_verified_restart_on_calendar: 'Sun *-*-* 04:00:00'
|
|
mailman_service_stop_timeout: 30
|
|
mailman_weekly_verified_restart_stop_timeout: 120
|
|
mailman_weekly_verified_restart_start_timeout: 120
|
|
```
|
|
|
|
The timer is deliberately not persistent, so a missed run is not executed
|
|
immediately after a server boot. Failures are recorded by systemd in the
|
|
`mailman-verified-restart.service` journal.
|
|
|
|
The managed `mailman.service` uses `KillMode=control-group` and an explicit
|
|
`TimeoutStopSec`. If Mailman's own stop command hangs, systemd terminates the
|
|
whole service cgroup and removes a stale master PID file. The weekly job still
|
|
verifies the real stopped state and does not start a second instance until the
|
|
master and LMTP listener are gone.
|
|
The normal role execution also waits for the configured LMTP port and fails if
|
|
the listener does not become available after starting or restarting Mailman.
|
|
|
|
## Legacy HyperKitty compatibility fixes
|
|
|
|
The optional compatibility patcher addresses failures observed with the
|
|
legacy Python 3.6 web stack: malformed `From`/`Subject` headers, nested
|
|
`message/rfc822` parts without `Content-Transfer-Encoding`, and unreadable
|
|
gettext catalogs. It also normalizes non-ASCII sender addresses and backports
|
|
the byte-oriented delivery used by newer `mailman-hyperkitty` releases, so
|
|
surrogate-escaped message bytes do not pass through Requests as Unicode text.
|
|
Named MIME parts whose content type is missing or empty are extracted as binary
|
|
attachments instead of being appended to the plain-text message body. As an
|
|
additional safeguard, the NUL filtering introduced by django-mailman3 1.3.6 is
|
|
backported for scrubbed text fields only; binary attachments remain unchanged.
|
|
It also backports the HyperKitty 1.3.5 guards for all four asynchronous cache
|
|
tasks that may still reference a mailing list after its archive has been
|
|
deleted. Those obsolete tasks are logged and skipped instead of being retried.
|
|
Enable it only for the known package versions:
|
|
|
|
```yaml
|
|
mailman_enable_legacy_hyperkitty_compatibility_patches: true
|
|
mailman_legacy_hyperkitty_version: '1.3.3'
|
|
mailman_legacy_django_mailman3_version: '1.3.4'
|
|
mailman_legacy_mailman_hyperkitty_version: '1.1.0'
|
|
```
|
|
|
|
The Python source patches are idempotent, compiled before any file is replaced,
|
|
and installed atomically. The task fails without changing any source file if
|
|
the installed versions or expected source fragments do not match. Invalid
|
|
`django_extensions` `.mo` files are preserved beside the original name with a
|
|
`.disabled-by-ansible` suffix, allowing Django to fall back to another locale.
|
|
The standalone HyperKitty qcluster, uWSGI and Mailman services are restarted
|
|
only when a source file or catalog actually changes. Restarting qcluster is
|
|
required because its workers otherwise retain the old Python module in memory.
|
|
After restarting Mailman, the handler requires the configured LMTP listener to
|
|
become available.
|
|
|
|
Disable this option before upgrading HyperKitty, django-mailman3, or
|
|
mailman-hyperkitty, then review whether the compatibility patches are still
|
|
needed with the new versions.
|
|
|
|
## uWSGI availability safeguards
|
|
|
|
The Mailman web application runs with multiple uWSGI worker processes. A
|
|
request that exceeds the configured timeout causes only its worker to be
|
|
restarted, while the remaining workers continue to serve Postorius and
|
|
HyperKitty. Workers are also recycled after a bounded number of requests or
|
|
when their resident memory exceeds the configured threshold:
|
|
|
|
```yaml
|
|
mailman_postorius_uwsgi_processes: 4
|
|
mailman_postorius_uwsgi_threads: 2
|
|
mailman_postorius_uwsgi_harakiri: 120
|
|
mailman_postorius_uwsgi_max_requests: 1000
|
|
mailman_postorius_uwsgi_reload_on_rss: 512
|
|
```
|
|
|
|
The `harakiri` value should not exceed nginx's `uwsgi_read_timeout`; otherwise
|
|
nginx gives up while the blocked worker remains occupied. `reload-on-rss` is
|
|
expressed in MiB. Changing these values restarts `mailmansuite-uwsgi` through
|
|
the role handler.
|
|
|
|
## HyperKitty archive cleanup
|
|
|
|
Mailman Core stores the archive visibility policy and the activation of each
|
|
archiver as independent per-list settings. Consequently, setting
|
|
`archive_policy` to `never` is not sufficient by itself to stop delivery to
|
|
HyperKitty if that archiver remains enabled. The optional reconciliation timer
|
|
enforces the safe direction every 15 minutes: it disables HyperKitty for lists
|
|
whose policy is `never`, but never enables an archiver and never deletes data:
|
|
|
|
```yaml
|
|
mailman_enable_hyperkitty_archiver_reconciliation: true
|
|
mailman_hyperkitty_archiver_reconciliation_on_active: '5min'
|
|
mailman_hyperkitty_archiver_reconciliation_interval: '15min'
|
|
```
|
|
|
|
A read-only preview of the current inconsistencies is available with:
|
|
|
|
```bash
|
|
sudo -u mailman /usr/local/sbin/mailman-hyperkitty-archiver-reconcile --dry-run
|
|
```
|
|
|
|
Mailman Core also does not delete HyperKitty data when a list is removed or
|
|
when archiving is disabled. The separate cleanup timer removes the
|
|
corresponding HyperKitty `MailingList`, threads and messages in both cases:
|
|
|
|
```yaml
|
|
mailman_enable_hyperkitty_archive_cleanup: true
|
|
mailman_hyperkitty_archive_cleanup_on_calendar: 'Sun *-*-* 03:00:00'
|
|
mailman_hyperkitty_archive_cleanup_deleted_lists: true
|
|
mailman_hyperkitty_archive_cleanup_disabled_lists: true
|
|
mailman_hyperkitty_archive_cleanup_max_deletions: 50
|
|
```
|
|
|
|
Deletion is irreversible. Before changing the database, the cleanup script
|
|
retrieves a complete list from Mailman Core and rechecks each candidate. It
|
|
aborts if Core is unavailable, if Core returns no lists, or if the configured
|
|
maximum number of deletions would be exceeded. HyperKitty deletion signals
|
|
that would enqueue cache rebuilds for the objects being removed are disabled
|
|
only inside the cleanup process. This avoids the known failure in legacy
|
|
HyperKitty releases where those asynchronous jobs try to load a list after it
|
|
has been deleted.
|
|
|
|
The timer is not persistent, so a missed run is not started after boot. A
|
|
read-only preview can be run before the first scheduled execution:
|
|
|
|
```bash
|
|
sudo -u mailman /usr/local/sbin/hyperkitty-archive-cleanup --dry-run
|
|
```
|
|
|
|
The monthly HyperKitty job already configured by this role removes stale
|
|
entries from the full-text search index. Therefore the weekly cleanup does not
|
|
run an additional, expensive full index scan.
|