From 05853144684bb77bb645ff373a844c59cafc8904 Mon Sep 17 00:00:00 2001 From: j4n Date: Mon, 16 Feb 2026 20:12:45 +0100 Subject: [PATCH] docker: extract cert monitor from background process to systemd timer The cert monitoring was an orphaned background process (`monitor_certificates &`) Replace with a proper systemd timer/service (every 60s). Also made journald ForwardToConsole=yes idempotent. --- docker-compose.yaml | 3 -- docker/chatmail_relay.dockerfile | 6 ++++ docker/example.env | 4 +-- docker/files/chatmail-certmon.service | 8 +++++ docker/files/chatmail-certmon.sh | 28 +++++++++++++++++ docker/files/chatmail-certmon.timer | 9 ++++++ docker/files/entrypoint.sh | 2 +- docker/files/setup_chatmail_docker.sh | 45 ++------------------------- 8 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 docker/files/chatmail-certmon.service create mode 100644 docker/files/chatmail-certmon.sh create mode 100644 docker/files/chatmail-certmon.timer diff --git a/docker-compose.yaml b/docker-compose.yaml index d2a43c7d..0c9ad92e 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -29,10 +29,7 @@ services: environment: MAIL_DOMAIN: $MAIL_DOMAIN CMDEPLOY_STAGES: ${CMDEPLOY_STAGES:-} - # Certificate monitoring (only needed with USE_FOREIGN_CERT_MANAGER) USE_FOREIGN_CERT_MANAGER: ${USE_FOREIGN_CERT_MANAGER:-} - ENABLE_CERTS_MONITORING: ${ENABLE_CERTS_MONITORING:-} - CERTS_MONITORING_TIMEOUT: ${CERTS_MONITORING_TIMEOUT:-} network_mode: "host" volumes: ## system diff --git a/docker/chatmail_relay.dockerfile b/docker/chatmail_relay.dockerfile index 3b08ad2c..67640d33 100644 --- a/docker/chatmail_relay.dockerfile +++ b/docker/chatmail_relay.dockerfile @@ -77,6 +77,12 @@ RUN rm -f /etc/nginx/sites-enabled/default COPY --chmod=555 ./docker/files/setup_chatmail_docker.sh /setup_chatmail_docker.sh COPY --chmod=555 ./docker/files/entrypoint.sh /entrypoint.sh +# Certificate monitoring as a proper systemd timer (not a background process) +COPY --chmod=555 ./docker/files/chatmail-certmon.sh /chatmail-certmon.sh +COPY ./docker/files/chatmail-certmon.service /lib/systemd/system/chatmail-certmon.service +COPY ./docker/files/chatmail-certmon.timer /lib/systemd/system/chatmail-certmon.timer +RUN ln -sf /lib/systemd/system/chatmail-certmon.timer /etc/systemd/system/timers.target.wants/chatmail-certmon.timer + HEALTHCHECK --interval=60s --timeout=10s --retries=3 \ CMD systemctl is-active dovecot postfix nginx unbound opendkim filtermail doveauth chatmail-metadata || exit 1 diff --git a/docker/example.env b/docker/example.env index 2d022279..da73f8bd 100644 --- a/docker/example.env +++ b/docker/example.env @@ -3,7 +3,5 @@ MAIL_DOMAIN="chat.example.com" # CMDEPLOY_STAGES - default: "configure,activate". Set to "install,configure,activate" to force full reinstall. # CMDEPLOY_STAGES="configure,activate" -# Certificate monitoring (only needed with USE_FOREIGN_CERT_MANAGER) +# Skip acmetool when using an external certificate manager (e.g. Traefik, Caddy). # USE_FOREIGN_CERT_MANAGER="True" -# ENABLE_CERTS_MONITORING="true" -# CERTS_MONITORING_TIMEOUT=60 diff --git a/docker/files/chatmail-certmon.service b/docker/files/chatmail-certmon.service new file mode 100644 index 00000000..f89b950f --- /dev/null +++ b/docker/files/chatmail-certmon.service @@ -0,0 +1,8 @@ +[Unit] +Description=Check TLS certificate changes and reload services +After=setup_chatmail.service + +[Service] +Type=oneshot +ExecStart=/bin/bash /chatmail-certmon.sh +PassEnvironment=MAIL_DOMAIN PATH_TO_SSL diff --git a/docker/files/chatmail-certmon.sh b/docker/files/chatmail-certmon.sh new file mode 100644 index 00000000..107c169d --- /dev/null +++ b/docker/files/chatmail-certmon.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Check if TLS certificates have changed and reload services if so. +# Called by chatmail-certmon.timer (systemd timer, default every 60s). +set -eo pipefail + +PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}" +HASH_FILE="/run/chatmail-certmon.hash" + +if [ ! -d "$PATH_TO_SSL" ]; then + exit 0 +fi + +current_hash=$(find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}') +previous_hash="" +if [ -f "$HASH_FILE" ]; then + previous_hash=$(cat "$HASH_FILE") +fi + +if [ -n "$current_hash" ] && [ "$current_hash" != "$previous_hash" ]; then + echo "[INFO] Certificate hash changed, reloading nginx, dovecot and postfix." + echo "$current_hash" > "$HASH_FILE" + # On first run (no previous hash), don't reload — services may not be up yet + if [ -n "$previous_hash" ]; then + systemctl reload nginx.service + systemctl reload dovecot.service + systemctl reload postfix.service + fi +fi diff --git a/docker/files/chatmail-certmon.timer b/docker/files/chatmail-certmon.timer new file mode 100644 index 00000000..8dc5aa8d --- /dev/null +++ b/docker/files/chatmail-certmon.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Periodically check TLS certificate changes + +[Timer] +OnBootSec=120 +OnUnitActiveSec=60 + +[Install] +WantedBy=timers.target diff --git a/docker/files/entrypoint.sh b/docker/files/entrypoint.sh index 669acd0b..3f72bfdb 100755 --- a/docker/files/entrypoint.sh +++ b/docker/files/entrypoint.sh @@ -6,7 +6,7 @@ SETUP_CHATMAIL_SERVICE_PATH="${SETUP_CHATMAIL_SERVICE_PATH:-/lib/systemd/system/ # Whitelist only the env vars needed by setup_chatmail_docker.sh. # Forwarding all env vars (via printenv) would leak Docker internals, # orchestrator secrets, and other unrelated variables into systemd. -env_vars="MAIL_DOMAIN CMDEPLOY_STAGES CHATMAIL_INI CHATMAIL_NOSYSCTL CHATMAIL_NOPORTCHECK ENABLE_CERTS_MONITORING CERTS_MONITORING_TIMEOUT PATH_TO_SSL PATH USE_FOREIGN_CERT_MANAGER" +env_vars="MAIL_DOMAIN CMDEPLOY_STAGES CHATMAIL_INI CHATMAIL_NOSYSCTL CHATMAIL_NOPORTCHECK PATH_TO_SSL PATH USE_FOREIGN_CERT_MANAGER" sed -i "s||$env_vars|g" "$SETUP_CHATMAIL_SERVICE_PATH" exec /lib/systemd/systemd "$@" diff --git a/docker/files/setup_chatmail_docker.sh b/docker/files/setup_chatmail_docker.sh index 312811a1..b8329803 100755 --- a/docker/files/setup_chatmail_docker.sh +++ b/docker/files/setup_chatmail_docker.sh @@ -2,9 +2,6 @@ set -eo pipefail export CHATMAIL_INI="${CHATMAIL_INI:-/etc/chatmail/chatmail.ini}" -export ENABLE_CERTS_MONITORING="${ENABLE_CERTS_MONITORING:-true}" -export CERTS_MONITORING_TIMEOUT="${CERTS_MONITORING_TIMEOUT:-60}" -export PATH_TO_SSL="${PATH_TO_SSL:-/var/lib/acme/live/${MAIL_DOMAIN}}" CMDEPLOY=/opt/cmdeploy/bin/cmdeploy @@ -13,42 +10,6 @@ if [ -z "$MAIL_DOMAIN" ]; then exit 1 fi -calculate_hash() { - if [ ! -d "$PATH_TO_SSL" ]; then - echo "" - return 0 - fi - find "$PATH_TO_SSL" -type f -exec sha1sum {} \; | sort | sha1sum | awk '{print $1}' -} - -monitor_certificates() { - if [ "$ENABLE_CERTS_MONITORING" != "true" ]; then - echo "Certs monitoring disabled." - return 0 - fi - - # Wait for certificate directory to exist before monitoring - echo "[INFO] Waiting for certificate directory: $PATH_TO_SSL" - while [ ! -d "$PATH_TO_SSL" ]; do - sleep "$CERTS_MONITORING_TIMEOUT" - done - echo "[INFO] Certificate directory found, starting monitoring." - - previous_hash=$(calculate_hash) - - while true; do - sleep "$CERTS_MONITORING_TIMEOUT" - current_hash=$(calculate_hash) - if [ -n "$current_hash" ] && [ "$current_hash" != "$previous_hash" ]; then - echo "[INFO] Certificate's folder hash was changed, reloading nginx, dovecot and postfix services." - systemctl reload nginx.service - systemctl reload dovecot.service - systemctl reload postfix.service - previous_hash=$current_hash - fi - done -} - ### MAIN if [ ! -f /etc/dkimkeys/opendkim.private ]; then @@ -66,7 +27,7 @@ fi export CMDEPLOY_STAGES="${CMDEPLOY_STAGES:-configure,activate}" $CMDEPLOY run --config "$CHATMAIL_INI" --ssh-host @local -echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf +# Journald: forward to console for docker logs (idempotent) +grep -q '^ForwardToConsole=yes' /etc/systemd/journald.conf \ + || echo "ForwardToConsole=yes" >> /etc/systemd/journald.conf systemctl restart systemd-journald - -monitor_certificates &