feat(hetzner): full migration of docker-mailserver to stalwart
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
{pkgs, ...}: let
|
||||
# Caddy's canonical certificate storage (XDG data dir). Renewals land here,
|
||||
# owned caddy:caddy 0600 — the mail server's non-root Postfix/Dovecot cannot
|
||||
# read it directly, so we copy it into a world-readable distribution dir.
|
||||
# owned caddy:caddy 0600 — stalwart cannot read it directly, so we copy it
|
||||
# into a distribution dir stalwart's service (user "stalwart") can reach.
|
||||
caddyCertDir = "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu";
|
||||
# World-readable distribution dir mounted (RO) into the mail server container.
|
||||
# Distribution dir read by stalwart (cert 0644, private key regranted to the
|
||||
# "stalwart" group by systemd.services.stalwart-cert-perm).
|
||||
distCertDir = "/var/lib/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.severijnse.eu";
|
||||
zoneFile = "/var/lib/coredns/zones/severijnse.eu.db";
|
||||
# Records the SPKI hash applied at the last restart of stalwart, so cert
|
||||
# renewals trigger exactly one restart and unchanged certs never do.
|
||||
stateFile = "/var/lib/tlsa-update/.last-spki";
|
||||
|
||||
syncScript = pkgs.writeShellScript "tlsa-update" ''
|
||||
set -euo pipefail
|
||||
@@ -22,11 +26,11 @@
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1) Propagate Caddy's renewed certificate into the distribution dir the
|
||||
# mail server mounts. Caddy stores certs 0600 caddy:caddy. The cert is
|
||||
# world-readable (Postfix/Dovecot read it as root before dropping
|
||||
# privileges); the private key is restricted to root (0640) so it is not
|
||||
# exposed to other local users.
|
||||
# 1) Propagate Caddy's renewed certificate into the distribution dir stalwart
|
||||
# reads. Caddy stores certs 0600 caddy:caddy. The cert is world-readable;
|
||||
# the private key is restricted to root (0640) and stalwart-cert-perm
|
||||
# regrants it to the "stalwart" group so it is not exposed to other local
|
||||
# users.
|
||||
install -D -m 0644 "$SRC_CERT" "$DST_CERT"
|
||||
install -D -m 0640 "$SRC_KEY" "$DST_KEY"
|
||||
|
||||
@@ -47,8 +51,24 @@
|
||||
|
||||
# 4) Reload services so the changes take effect immediately.
|
||||
systemctl reload coredns.service || true
|
||||
podman exec mailserver postfix reload || true
|
||||
podman exec mailserver dovecot reload || true
|
||||
# Stalwart reads its TLS certs ($certDir) via %{file:...}% placeholders at
|
||||
# startup only; there is no signal-based reload (management API reload needs
|
||||
# admin credentials we must not store). Restart it, but only when the cert
|
||||
# actually changed (SPKI hash differs from the last applied run), so the
|
||||
# daily timer and inotify events for unchanged certs do not drop connections.
|
||||
# NOTE: stalwart.service `requires` this unit, so the restart must be issued
|
||||
# with --no-block (async): a synchronous restart waits for stalwart to come
|
||||
# back up, which in turn waits for this unit to finish — a deadlock. The state
|
||||
# file is updated BEFORE the restart so the tlsa-update run that stalwart's
|
||||
# required-activation re-triggers sees a matching hash and exits immediately.
|
||||
if [ "$(cat "${stateFile}" 2>/dev/null || true)" != "$HEX" ]; then
|
||||
printf '%s\n' "$HEX" > "${stateFile}"
|
||||
# Restarting stalwart re-runs its Requires=tlsa-update dependency; guard
|
||||
# with is-active so a boot-time run never races stalwart's initial start.
|
||||
if systemctl is-active --quiet stalwart.service 2>/dev/null; then
|
||||
systemctl --no-block restart stalwart.service || true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "tlsa-update: TLSA set to $HEX"
|
||||
'';
|
||||
@@ -57,6 +77,7 @@ in {
|
||||
# Ensure the distribution dir exists (Caddy does not write here).
|
||||
tmpfiles.rules = [
|
||||
"d ${distCertDir} 0755 root root - -"
|
||||
"d /var/lib/tlsa-update 0755 root root - -"
|
||||
];
|
||||
|
||||
services.tlsa-update = {
|
||||
@@ -64,7 +85,7 @@ in {
|
||||
after = ["caddy.service" "coredns.service"];
|
||||
partOf = ["coredns.service"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
path = with pkgs; [openssl coreutils gnused podman systemd];
|
||||
path = with pkgs; [openssl coreutils gnused systemd];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${syncScript}";
|
||||
|
||||
Reference in New Issue
Block a user