From ffd688958bae07b4ec8ca25eaf07440428d50ac5 Mon Sep 17 00:00:00 2001 From: DeveloperSarim <168187569+DeveloperSarim@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:37:40 +0500 Subject: [PATCH] Read the mail domain from .env instead of hardcoding it deploy/setup-tls.sh and deploy/verify.sh had mail.sarimtools.com baked in, so a fresh clone would request a Let's Encrypt certificate for someone else's domain and health-check the wrong host. Both now read MAIL_HOSTNAME from .env and fail with a clear message when it is unset. The Apache vhosts become templates with a @@DOMAIN@@ placeholder that setup-tls substitutes, .env.example ships example.com rather than a real instance, and the Python helpers resolve .env relative to the checkout instead of assuming /root/raymail. --- .env.example | 8 ++++---- deploy/_stalwart.py | 6 +++++- deploy/dns-records.py | 4 ++-- deploy/raymail-http.conf | 2 +- deploy/raymail.conf | 10 +++++----- deploy/setup-tls.sh | 17 ++++++++++++----- deploy/verify.sh | 6 ++++-- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 0e5d05e..25124bd 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,11 @@ # ---------- identity ---------- -MAIL_DOMAIN=mail.sarimtools.com -MAIL_HOSTNAME=mail.sarimtools.com -APP_URL=https://mail.sarimtools.com +MAIL_DOMAIN=mail.example.com +MAIL_HOSTNAME=mail.example.com +APP_URL=https://mail.example.com # ---------- stalwart admin ---------- # Set by the bootstrap; Stalwart generates the permanent admin on first setup. -STALWART_ADMIN_USER=admin@mail.sarimtools.com +STALWART_ADMIN_USER=admin@mail.example.com STALWART_ADMIN_PASSWORD= # ---------- outbound smarthost relay ---------- diff --git a/deploy/_stalwart.py b/deploy/_stalwart.py index 93ed5f7..3e83794 100644 --- a/deploy/_stalwart.py +++ b/deploy/_stalwart.py @@ -4,7 +4,11 @@ ENDPOINT = os.environ.get("STALWART_ADMIN_URL", "http://127.0.0.1:3881/jmap/") -def load_env(path="/root/raymail/.env"): +def load_env(path=None): + # Resolve relative to this file so the scripts work from any checkout, + # not only from /root/raymail. + if path is None: + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".env") env = {} with open(path) as fh: for line in fh: diff --git a/deploy/dns-records.py b/deploy/dns-records.py index 10fb9ac..db9bc36 100755 --- a/deploy/dns-records.py +++ b/deploy/dns-records.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Print the exact DNS records this installation needs, using its live DKIM keys.""" -import subprocess +import subprocess, sys from _stalwart import load_env, client env = load_env() -domain = env.get("MAIL_DOMAIN", "mail.sarimtools.com") +domain = env.get("MAIL_DOMAIN") or sys.exit("Set MAIL_DOMAIN in .env first.") host = env.get("MAIL_HOSTNAME", domain) sub = domain.split(".")[0] diff --git a/deploy/raymail-http.conf b/deploy/raymail-http.conf index 93892b1..6dddcc2 100644 --- a/deploy/raymail-http.conf +++ b/deploy/raymail-http.conf @@ -1,7 +1,7 @@ # RayMail — stage 1 (pre-TLS). Serves the ACME challenge and proxies plain HTTP. # Replaced by raymail.conf once the certificate is issued. - ServerName mail.sarimtools.com + ServerName @@DOMAIN@@ Alias /.well-known/acme-challenge/ /var/www/letsencrypt/.well-known/acme-challenge/ diff --git a/deploy/raymail.conf b/deploy/raymail.conf index c532450..13aa90e 100644 --- a/deploy/raymail.conf +++ b/deploy/raymail.conf @@ -1,9 +1,9 @@ -# RayMail — mail.sarimtools.com +# RayMail — @@DOMAIN@@ # Proxies to the RayMail web container on 127.0.0.1:3880. # Stalwart's admin/JMAP port (3881) is deliberately NOT exposed here; the web # app reaches it over the internal docker network instead. - ServerName mail.sarimtools.com + ServerName @@DOMAIN@@ Alias /.well-known/acme-challenge/ /var/www/letsencrypt/.well-known/acme-challenge/ @@ -17,7 +17,7 @@ - ServerName mail.sarimtools.com + ServerName @@DOMAIN@@ ProxyPreserveHost On @@ -37,8 +37,8 @@ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" SSLEngine on - SSLCertificateFile /etc/letsencrypt/live/mail.sarimtools.com/fullchain.pem - SSLCertificateKeyFile /etc/letsencrypt/live/mail.sarimtools.com/privkey.pem + SSLCertificateFile /etc/letsencrypt/live/@@DOMAIN@@/fullchain.pem + SSLCertificateKeyFile /etc/letsencrypt/live/@@DOMAIN@@/privkey.pem ErrorLog ${APACHE_LOG_DIR}/raymail-error.log CustomLog ${APACHE_LOG_DIR}/raymail-access.log combined diff --git a/deploy/setup-tls.sh b/deploy/setup-tls.sh index 6b11855..7adf90c 100755 --- a/deploy/setup-tls.sh +++ b/deploy/setup-tls.sh @@ -1,12 +1,19 @@ #!/usr/bin/env bash -# Issues the Let's Encrypt cert for mail.sarimtools.com and installs the TLS vhost. +# Issues the Let's Encrypt certificate for the configured domain and installs +# the TLS vhost. # Safety contract: never restarts Apache, never edits an existing vhost, and # aborts before touching anything if configtest fails. set -euo pipefail -DOMAIN=mail.sarimtools.com HERE="$(cd "$(dirname "$0")" && pwd)" +# The domain comes from .env. It used to be hardcoded, which meant a fresh +# clone would try to issue a certificate for someone else's domain. +ENV_FILE="${ENV_FILE:-$HERE/../.env}" +[ -f "$ENV_FILE" ] || { echo "FAIL: $ENV_FILE not found - copy .env.example first"; exit 1; } +DOMAIN="$(grep -E '^MAIL_HOSTNAME=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'[:space:]')" +[ -n "$DOMAIN" ] || { echo "FAIL: MAIL_HOSTNAME is not set in $ENV_FILE"; exit 1; } + say(){ printf '\n\033[1m==> %s\033[0m\n' "$*"; } say "Preflight: DNS" @@ -33,7 +40,7 @@ fi say "Installing stage-1 HTTP vhost (ACME challenge)" mkdir -p /var/www/letsencrypt/.well-known/acme-challenge -install -m 644 "$HERE/raymail-http.conf" /etc/apache2/sites-available/raymail.conf +sed "s|@@DOMAIN@@|$DOMAIN|g" "$HERE/raymail-http.conf" > /etc/apache2/sites-available/raymail.conf a2ensite raymail >/dev/null apache2ctl configtest systemctl reload apache2 # graceful: existing connections are not dropped @@ -44,10 +51,10 @@ certbot certonly --webroot -w /var/www/letsencrypt -d "$DOMAIN" \ --non-interactive --agree-tos --register-unsafely-without-email --keep-until-expiring say "Installing stage-2 TLS vhost" -install -m 644 "$HERE/raymail.conf" /etc/apache2/sites-available/raymail.conf +sed "s|@@DOMAIN@@|$DOMAIN|g" "$HERE/raymail.conf" > /etc/apache2/sites-available/raymail.conf if ! apache2ctl configtest; then echo "FAIL: configtest rejected the TLS vhost — rolling back to stage 1" - install -m 644 "$HERE/raymail-http.conf" /etc/apache2/sites-available/raymail.conf + sed "s|@@DOMAIN@@|$DOMAIN|g" "$HERE/raymail-http.conf" > /etc/apache2/sites-available/raymail.conf apache2ctl configtest && systemctl reload apache2 exit 1 fi diff --git a/deploy/verify.sh b/deploy/verify.sh index 9f9dc72..bd8f74a 100755 --- a/deploy/verify.sh +++ b/deploy/verify.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash # Read-only verification of the RayMail deployment. Changes nothing. -DOMAIN=mail.sarimtools.com +HERE="$(cd "$(dirname "$0")" && pwd)" +DOMAIN="$(grep -E '^MAIL_HOSTNAME=' "$HERE/../.env" 2>/dev/null | cut -d= -f2- | tr -d '\"[:space:]')" +DOMAIN="${DOMAIN:-localhost}" b(){ printf '\n\033[1m== %s\033[0m\n' "$*"; } b "containers" -docker compose -f /root/raymail/docker-compose.yml ps 2>/dev/null +docker compose -f "$HERE/../docker-compose.yml" ps 2>/dev/null b "listening sockets owned by RayMail" ss -tlpnH | grep -E ':(25|465|587|993|3880|3881)\b' || echo "none bound yet"