Stalwart v0.16 does not ship the WebUI in its Docker image — the admin console is fetched from GitHub on first start. With no outbound HTTPS at that moment the fetch fails silently: /healthz/ready still answers 200 because the mail server genuinely is serving, so both the installer and the upgrade verifier reported success while /admin and /account 404'd with nothing to explain why. Install hook now probes /admin after the port-25 and PTR checks and, on 404, names the GitHub download as the cause rather than emitting a generic failure. Upgrade verifier treats stable readiness as necessary but not sufficient and confirms /admin before returning 0; the console is polled under the same deadline because the bundle download runs behind the server coming up, and failing on the first 404 would abort an upgrade that was seconds from finishing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
6.1 KiB
Bash
118 lines
6.1 KiB
Bash
#!/bin/bash
|
|
|
|
# Stalwart install hooks.
|
|
#
|
|
# Installing a mail server is not like installing any other app: the container
|
|
# starting successfully means almost nothing. Mail only works once DNS, reverse
|
|
# DNS and outbound port 25 are right, and every one of those lives OUTSIDE the
|
|
# box — at the registrar and the VPS provider. So the job of these hooks is to
|
|
# say plainly what still has to be done, with the actual values to enter, rather
|
|
# than reporting "installed" and leaving the admin to discover weeks later that
|
|
# their mail is landing in spam.
|
|
|
|
# Echo the admin credentials for the standard final-message block (word-split
|
|
# by the caller into positional args: user pass).
|
|
stalwart_install_message_data()
|
|
{
|
|
printf '%s %s' "${CFG_STALWART_ADMIN_USER:-admin}" "${CFG_STALWART_ADMIN_PASSWORD:-}"
|
|
}
|
|
|
|
stalwart_install_post_start()
|
|
{
|
|
local app_name="$1"
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Mail server checks + the DNS records you still need"
|
|
echo ""
|
|
|
|
# Resolved admin port comes from the compose tag (format `external:internal`),
|
|
# the same source adguard's hook reads — the legacy $usedport1 isn't populated
|
|
# by the current install pipeline.
|
|
local compose_file="$containers_dir$app_name/docker-compose.yml"
|
|
local admin_pair admin_port
|
|
admin_pair=$(tagsManagerGetTagContent "$compose_file" "PORTS_TAG_1" 2>/dev/null)
|
|
admin_port="${admin_pair%%:*}"
|
|
|
|
# ---- 1. Can this host even send mail? --------------------------------
|
|
# Most cheap VPS providers block outbound 25 by default (and several only
|
|
# unblock on request). A blocked port 25 means no mail EVER leaves the box,
|
|
# and nothing in the WebUI would otherwise reveal it.
|
|
isNotice "Checking outbound port 25 (required to deliver mail to other servers)…"
|
|
if command -v timeout >/dev/null 2>&1 \
|
|
&& timeout 8 bash -c 'exec 3<>/dev/tcp/gmail-smtp-in.l.google.com/25' 2>/dev/null; then
|
|
isSuccessful "Outbound port 25 is open."
|
|
else
|
|
isError "Outbound port 25 appears BLOCKED or filtered on this host."
|
|
isNotice " Most VPS providers block it by default. Ask your provider to unblock"
|
|
isNotice " outbound 25, or mail will queue and never deliver."
|
|
fi
|
|
|
|
# ---- 2. Reverse DNS --------------------------------------------------
|
|
# Receiving servers check that the sending IP resolves back to a name. A
|
|
# generic provider PTR (e.g. static.1.2.3.4.provider.net) is a common reason
|
|
# for mail being junked, and it can only be fixed in the provider's panel.
|
|
if [[ -n "$public_ip_v4" ]] && command -v dig >/dev/null 2>&1; then
|
|
local ptr; ptr=$(dig +short -x "$public_ip_v4" 2>/dev/null | head -1)
|
|
if [[ -n "$ptr" ]]; then
|
|
isNotice "Reverse DNS (PTR) for $public_ip_v4 is: ${ptr%.}"
|
|
isNotice " It should match your mail hostname. Set it in your VPS provider's panel."
|
|
else
|
|
isError "No reverse DNS (PTR) record for $public_ip_v4 — set one at your VPS provider."
|
|
fi
|
|
fi
|
|
|
|
# ---- 3. Is the admin console actually there? -------------------------
|
|
# Stalwart v0.16 does not ship the WebUI inside the Docker image: the admin
|
|
# console is a single-page app the server fetches from GitHub on first
|
|
# start. If this host had no outbound HTTPS at that moment the download
|
|
# silently fails, the server still comes up healthy, and /admin and
|
|
# /account answer 404 forever with nothing to explain why. Checking it here
|
|
# turns "the panel is broken" into a one-line, fixable cause.
|
|
isNotice "Checking the admin console (WebUI) responds…"
|
|
local admin_code
|
|
admin_code=$(runFileOp docker exec stalwart-service curl -fsS -o /dev/null -w '%{http_code}' \
|
|
--max-time 5 http://localhost:8080/admin 2>/dev/null | tr -d '\r')
|
|
if [[ "$admin_code" == "404" ]]; then
|
|
isError "The admin console is missing (/admin returns 404)."
|
|
isNotice " Stalwart does not bundle the WebUI — it downloads it from"
|
|
isNotice " https://github.com/stalwartlabs/webui/releases/latest on first start."
|
|
isNotice " That download failed, so /admin and /account will 404 until it succeeds."
|
|
isNotice " Allow outbound HTTPS to github.com from this host, then restart the"
|
|
isNotice " container: docker restart stalwart-service"
|
|
isNotice " The mail server itself is unaffected — only the web interface is."
|
|
elif [[ -z "$admin_code" ]]; then
|
|
isError "Could not probe the admin console (no response from the container)."
|
|
else
|
|
isSuccessful "Admin console is being served (HTTP $admin_code)."
|
|
fi
|
|
|
|
# ---- 4. The records the admin must add themselves --------------------
|
|
# Printed with real values so they can be pasted at the registrar. DKIM is
|
|
# deliberately NOT guessed here: Stalwart generates the keypair on first
|
|
# run, and the public key must be copied from its admin UI.
|
|
local mail_host="${host_setup:-your-mail-hostname}"
|
|
local mail_domain="${mail_host#*.}"
|
|
echo ""
|
|
isNotice "DNS records to add at your domain registrar:"
|
|
echo " MX @ 10 ${mail_host}"
|
|
echo " A ${mail_host} ${public_ip_v4:-<the IP of this server>}"
|
|
echo " TXT @ \"v=spf1 mx -all\""
|
|
echo " TXT _dmarc \"v=DMARC1; p=quarantine; rua=mailto:postmaster@${mail_domain}\""
|
|
echo " TXT <selector>._domainkey (DKIM — copy from Stalwart's admin UI once it has"
|
|
echo " generated the key; it is not known until then)"
|
|
echo ""
|
|
|
|
# ---- 5. Where to go next ---------------------------------------------
|
|
if [[ -n "$admin_port" ]]; then
|
|
isNotice "Finish setup in the admin interface:"
|
|
[[ -n "$public_ip_v4" ]] && echo " http://$public_ip_v4:$admin_port/"
|
|
echo ""
|
|
isNotice "Sign in as '${CFG_STALWART_ADMIN_USER:-admin}' with the password shown below,"
|
|
isNotice "then add your domain and create mailboxes."
|
|
fi
|
|
|
|
isNotice "Until MX, PTR, SPF, DKIM and DMARC are all in place, expect delivery"
|
|
isNotice "problems — that is normal for a new mail server, not a fault in the app."
|
|
}
|