diff --git a/containers/stalwart/docker-compose.yml b/containers/stalwart/docker-compose.yml index 014d077..42894b0 100644 --- a/containers/stalwart/docker-compose.yml +++ b/containers/stalwart/docker-compose.yml @@ -9,6 +9,18 @@ services: # snapshot, one thing to restore. stalwart-service: #LIBREPORTAL|SERVICE_TAG_1|stalwart-service container_name: stalwart-service + # The image runs as its own uid 2000, which cannot write either of the + # bind mounts below: LibrePortal hands container directories to the + # docker install user in rootless mode and to the manager in rooted mode, + # and 2000 is neither. Stalwart then fails to open its database at all — + # "Permission denied" on /var/lib/stalwart — so no mail can be stored and + # even the setup wizard cannot complete. + # Running as container-root maps to whichever host user owns those + # directories, which is the one uid that is correct in both modes. Under + # rootless this is not host root; it is the unprivileged docker install + # user. It also removes any question about binding port 25 inside the + # container. + user: "0:0" # Pinned to a minor tag, NOT :latest. Stalwart is pre-1.0 and has said the # storage schema is still being finalised, so an unattended jump to a new # minor could carry a data migration. CFG_STALWART_UPDATE_TYPE is likewise diff --git a/containers/stalwart/scripts/stalwart_install_hooks.sh b/containers/stalwart/scripts/stalwart_install_hooks.sh index 8ebbfb7..e985b7d 100644 --- a/containers/stalwart/scripts/stalwart_install_hooks.sh +++ b/containers/stalwart/scripts/stalwart_install_hooks.sh @@ -100,8 +100,16 @@ stalwart_install_provision() plan=$(printf '{"@type":"update","object":"Bootstrap","value":{"serverHostname":"%s","defaultDomain":"%s","generateDkimKeys":true,"requestTlsCertificate":true}}' \ "$mail_host" "$mail_domain") - if ! printf '%s\n' "$plan" | stalwart_cli apply --stdin >/dev/null 2>&1; then + # Keep the server's own error rather than swallowing it. The two that + # actually happen say exactly what is wrong — a hostname under a TLD that + # does not exist, or a data directory the server cannot write — and both are + # unfixable guesswork without the message. + local apply_out + if ! apply_out=$(printf '%s\n' "$plan" | stalwart_cli apply --stdin 2>&1); then isError "Automatic setup failed — open the admin console to finish it by hand." + local reason + reason=$(printf '%s\n' "$apply_out" | grep -oE '(Invalid|Failed|Permission)[^|]*' | head -1) + [[ -n "$reason" ]] && isNotice " Stalwart said: ${reason% }" return 1 fi