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
The optional weekly restart performs separate stop and start operations. It
waits for both systemd and `mailman status` to confirm that Mailman has stopped
before starting it, then checks both signals again to confirm startup:
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
```
@ -41,3 +43,11 @@ 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.

View File

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

View File

@ -103,6 +103,15 @@
service: name=mailman state=restarted
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
template: src={{ item }}-logrotate.j2 dest=/etc/logrotate.d/{{ item }} owner=root mode=0444
with_items:

View File

@ -6,4 +6,4 @@ Wants=network-online.target
[Service]
Type=oneshot
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 MAILMAN='{{ mailman_bindir }}/mailman'
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 START_TIMEOUT={{ mailman_weekly_verified_restart_start_timeout | int }}
@ -13,16 +15,40 @@ log()
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()
{
! 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_is_started()
{
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()
@ -41,9 +67,8 @@ wait_for_state()
}
log 'stopping Mailman'
if ! systemctl stop "${SERVICE}"; then
log 'ERROR: systemctl could not stop Mailman'
exit 1
if ! timeout "$((SYSTEMD_STOP_TIMEOUT + 10))" systemctl stop "${SERVICE}"; then
log 'WARNING: systemctl stop did not complete successfully; verifying the actual state'
fi
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
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 name of an existing chain such as 'discard', 'reject', 'hold', or
# 'accept', otherwise 'hold' will be used.
jump_chain: "{{ mailman_antispam_chain_behaviour }}"
jump_chain: {{ mailman_antispam_chain_behaviour }}
[runner.nntp]
class: mailman.runners.nntp.NNTPRunner
@ -253,4 +253,4 @@ start: {{ mailman_start_nntp_runner }}
class: mailman_hyperkitty.Archiver
enable: yes
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
ExecReload={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg restart
ExecStop={{ mailman_bindir }}/mailman -C {{ mailman_conf_dir }}/mailman.cfg stop
ExecStopPost=/bin/rm -f {{ mailman_var_dir }}/master.pid
Type=forking
PIDFile={{ mailman_var_dir }}/master.pid
SyslogIdentifier=mailman
@ -17,6 +18,9 @@ User={{ mailman_user }}
Group={{ mailman_user }}
Restart=on-failure
RestartSec=5s
TimeoutStopSec={{ mailman_service_stop_timeout }}s
KillMode=control-group
SendSIGKILL=yes
[Install]
WantedBy=multi-user.target