Fix the configuration template and the restart script.

This commit is contained in:
Andrea Dell'Amico 2026-08-23 10:55:12 +02:00
parent ef104e06c4
commit e0bf89d83d
Signed by: adellam
GPG Key ID: 147ABE6CEB9E20FF
7 changed files with 60 additions and 11 deletions

View File

@ -28,12 +28,14 @@ collection is empty; lists with one or more existing rules are left untouched.
## Verified weekly restart ## Verified weekly restart
The optional weekly restart performs separate stop and start operations. It The optional weekly restart performs separate stop and start operations. It
waits for both systemd and `mailman status` to confirm that Mailman has stopped waits for systemd, `mailman status`, and the configured LMTP listener to confirm
before starting it, then checks both signals again to confirm startup: that Mailman has stopped before starting it. It then requires the master and
the LMTP port to become available before considering startup successful:
```yaml ```yaml
mailman_enable_weekly_verified_restart: true mailman_enable_weekly_verified_restart: true
mailman_weekly_verified_restart_on_calendar: 'Sun *-*-* 04:00:00' 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_stop_timeout: 120
mailman_weekly_verified_restart_start_timeout: 120 mailman_weekly_verified_restart_start_timeout: 120
``` ```
@ -41,3 +43,11 @@ mailman_weekly_verified_restart_start_timeout: 120
The timer is deliberately not persistent, so a missed run is not executed 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 immediately after a server boot. Failures are recorded by systemd in the
`mailman-verified-restart.service` journal. `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.

View File

@ -40,6 +40,7 @@ mailman_enable_daily_notifications: true
# default and, when enabled, uses the managed host's local timezone. # default and, when enabled, uses the managed host's local timezone.
mailman_enable_weekly_verified_restart: false mailman_enable_weekly_verified_restart: false
mailman_weekly_verified_restart_on_calendar: 'Sun *-*-* 04:00:00' 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_stop_timeout: 120
mailman_weekly_verified_restart_start_timeout: 120 mailman_weekly_verified_restart_start_timeout: 120

View File

@ -103,6 +103,15 @@
service: name=mailman state=restarted service: name=mailman state=restarted
when: mailman_hyperkitty_install is changed when: mailman_hyperkitty_install is changed
- name: Wait for the Mailman LMTP listener
ansible.builtin.wait_for:
host: '{{ mailman_lmtp_host }}'
port: '{{ mailman_lmtp_port }}'
state: started
sleep: 1
timeout: '{{ mailman_weekly_verified_restart_start_timeout }}'
when: not ansible_check_mode
- name: Install the mailman and mailmansuite logrotate configurations - name: Install the mailman and mailmansuite logrotate configurations
template: src={{ item }}-logrotate.j2 dest=/etc/logrotate.d/{{ item }} owner=root mode=0444 template: src={{ item }}-logrotate.j2 dest=/etc/logrotate.d/{{ item }} owner=root mode=0444
with_items: with_items:

View File

@ -6,4 +6,4 @@ Wants=network-online.target
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/local/sbin/mailman-verified-restart ExecStart=/usr/local/sbin/mailman-verified-restart
TimeoutStartSec={{ (mailman_weekly_verified_restart_stop_timeout | int) + (mailman_weekly_verified_restart_start_timeout | int) + 30 }} TimeoutStartSec={{ (mailman_service_stop_timeout | int) + (mailman_weekly_verified_restart_stop_timeout | int) + (mailman_weekly_verified_restart_start_timeout | int) + 30 }}

View File

@ -5,6 +5,8 @@ set -uo pipefail
readonly SERVICE='mailman.service' readonly SERVICE='mailman.service'
readonly MAILMAN='{{ mailman_bindir }}/mailman' readonly MAILMAN='{{ mailman_bindir }}/mailman'
readonly CONFIG='{{ mailman_conf_dir }}/mailman.cfg' readonly CONFIG='{{ mailman_conf_dir }}/mailman.cfg'
readonly LMTP_PORT={{ mailman_lmtp_port | int }}
readonly SYSTEMD_STOP_TIMEOUT={{ mailman_service_stop_timeout | int }}
readonly STOP_TIMEOUT={{ mailman_weekly_verified_restart_stop_timeout | int }} readonly STOP_TIMEOUT={{ mailman_weekly_verified_restart_stop_timeout | int }}
readonly START_TIMEOUT={{ mailman_weekly_verified_restart_start_timeout | int }} readonly START_TIMEOUT={{ mailman_weekly_verified_restart_start_timeout | int }}
@ -13,16 +15,40 @@ log()
printf 'mailman-verified-restart: %s\n' "$*" printf 'mailman-verified-restart: %s\n' "$*"
} }
lmtp_is_listening()
{
local sockets
local suffix=":${LMTP_PORT}"
if ! sockets="$(ss -H -ltn)"; then
return 2
fi
awk -v suffix="${suffix}" '
substr($4, length($4) - length(suffix) + 1) == suffix {
found = 1
}
END { exit !found }
' <<< "${sockets}"
}
mailman_is_stopped() mailman_is_stopped()
{ {
! systemctl is-active --quiet "${SERVICE}" && local listener_status
lmtp_is_listening
listener_status=$?
[[ ${listener_status} -eq 1 ]] &&
! systemctl is-active --quiet "${SERVICE}" &&
! "${MAILMAN}" -C "${CONFIG}" status >/dev/null 2>&1 ! "${MAILMAN}" -C "${CONFIG}" status >/dev/null 2>&1
} }
mailman_is_started() mailman_is_started()
{ {
systemctl is-active --quiet "${SERVICE}" && systemctl is-active --quiet "${SERVICE}" &&
"${MAILMAN}" -C "${CONFIG}" status >/dev/null 2>&1 "${MAILMAN}" -C "${CONFIG}" status >/dev/null 2>&1 &&
lmtp_is_listening
} }
wait_for_state() wait_for_state()
@ -41,9 +67,8 @@ wait_for_state()
} }
log 'stopping Mailman' log 'stopping Mailman'
if ! systemctl stop "${SERVICE}"; then if ! timeout "$((SYSTEMD_STOP_TIMEOUT + 10))" systemctl stop "${SERVICE}"; then
log 'ERROR: systemctl could not stop Mailman' log 'WARNING: systemctl stop did not complete successfully; verifying the actual state'
exit 1
fi fi
if ! wait_for_state mailman_is_stopped "${STOP_TIMEOUT}"; then if ! wait_for_state mailman_is_stopped "${STOP_TIMEOUT}"; then
@ -65,4 +90,4 @@ if ! wait_for_state mailman_is_started "${START_TIMEOUT}"; then
exit 1 exit 1
fi fi
log 'Mailman is active and its master process is running' log "Mailman is active, its master is running and LMTP port ${LMTP_PORT} is listening"

View File

@ -242,7 +242,7 @@ header_checks:
# The chain to jump to if any of the header patterns matches. This must be # The chain to jump to if any of the header patterns matches. This must be
# the name of an existing chain such as 'discard', 'reject', 'hold', or # the name of an existing chain such as 'discard', 'reject', 'hold', or
# 'accept', otherwise 'hold' will be used. # 'accept', otherwise 'hold' will be used.
jump_chain: "{{ mailman_antispam_chain_behaviour }}" jump_chain: {{ mailman_antispam_chain_behaviour }}
[runner.nntp] [runner.nntp]
class: mailman.runners.nntp.NNTPRunner class: mailman.runners.nntp.NNTPRunner
@ -253,4 +253,4 @@ start: {{ mailman_start_nntp_runner }}
class: mailman_hyperkitty.Archiver class: mailman_hyperkitty.Archiver
enable: yes enable: yes
configuration: {{ mailman_conf_dir }}/mailman-hyperkitty.cfg configuration: {{ mailman_conf_dir }}/mailman-hyperkitty.cfg
{% endif %} {% endif %}

View File

@ -10,6 +10,7 @@ Environment=PATH={{ mailman_bindir }}
ExecStart={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg start ExecStart={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg start
ExecReload={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg restart ExecReload={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg restart
ExecStop={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg stop ExecStop={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg stop
ExecStopPost=/bin/rm -f {{ mailman_var_dir }}/master.pid
Type=forking Type=forking
PIDFile={{ mailman_var_dir }}/master.pid PIDFile={{ mailman_var_dir }}/master.pid
SyslogIdentifier=mailman SyslogIdentifier=mailman
@ -17,6 +18,9 @@ User={{ mailman_user }}
Group={{ mailman_user }} Group={{ mailman_user }}
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
TimeoutStopSec={{ mailman_service_stop_timeout }}s
KillMode=control-group
SendSIGKILL=yes
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target