f66a343472
ci / changes (push) Successful in 3s
ci / tc_operator (push) Has been skipped
ci / build_portal (push) Has been skipped
ci / build_operator (push) Has been skipped
ci / build_platform_api (push) Has been skipped
ci / tc_portal (push) Has been skipped
ci / tc_booking (push) Has been skipped
ci / tc_website (push) Has been skipped
ci / tc_platform_api (push) Has been skipped
ci / test_platform_api (push) Has been skipped
ci / build_booking (push) Has been skipped
ci / deploy (push) Successful in 42s
The v0.16 config migration silently dropped the fallback admin — the live server had ZERO accounts, so every platform-api JMAP call 401'd and tenant mail provisioning was dead. Bootstrapped via recovery mode on node1 (STALWART_RECOVERY_ADMIN): created the dezky.eu domain + an admin account with the Admin role and the existing STALWART_ADMIN_PASSWORD. v0.16 logins use the full address, so STALWART_ADMIN_USER becomes admin@dezky.eu; config-rev annotation bump rolls platform-api so it picks up the new env. install.sh follow-ups now document the recovery-mode bootstrap for rebuilds instead of the defunct fallback-admin promise.
162 lines
7.8 KiB
Bash
Executable File
162 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Install Stalwart mail server as a hardened host systemd service on the AX41.
|
|
# Run AFTER bootstrap.sh (and ideally after k3s registration, so cert-sync can
|
|
# immediately pull the real cert). Idempotent — safe to re-run to upgrade.
|
|
#
|
|
# sudo ./install.sh
|
|
#
|
|
# What it does: creates the stalwart user + /opt/stalwart layout, downloads a
|
|
# pinned Stalwart binary, installs config.toml + the secrets EnvironmentFile,
|
|
# drops a self-signed bootstrap cert (replaced later by cert-sync), and installs
|
|
# the systemd units (mail service + cert-sync service/timer).
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
HOST_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
CONFIG_FILE="${CONFIG_FILE:-$HOST_DIR/config.env}"
|
|
|
|
PREFIX="/opt/stalwart"
|
|
STALWART_REPO="${STALWART_REPO:-stalwartlabs/stalwart}"
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
error "Run as root."
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
error "Missing $CONFIG_FILE — fill in the STALWART_* values first."
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG_FILE"
|
|
|
|
: "${STALWART_ADMIN_PASSWORD:?STALWART_ADMIN_PASSWORD required (openssl rand -hex 24)}"
|
|
: "${STALWART_WEBHOOK_SECRET:?STALWART_WEBHOOK_SECRET required (openssl rand -hex 32)}"
|
|
: "${STALWART_VERSION:=latest}"
|
|
|
|
# ── Step 1: user + directory layout ────────────────────────────────────────
|
|
info "Step 1: stalwart user + ${PREFIX} layout..."
|
|
if ! id -u stalwart >/dev/null 2>&1; then
|
|
useradd --system --home-dir "$PREFIX" --shell /usr/sbin/nologin stalwart
|
|
fi
|
|
install -d -o stalwart -g stalwart -m 0750 "$PREFIX" "$PREFIX/bin" "$PREFIX/data" "$PREFIX/logs"
|
|
install -d -o stalwart -g stalwart -m 0750 "$PREFIX/etc" "$PREFIX/etc/tls"
|
|
ok "Layout ready."
|
|
|
|
# ── Step 2: download the Stalwart binary ───────────────────────────────────
|
|
info "Step 2: fetching Stalwart binary (${STALWART_REPO}@${STALWART_VERSION})..."
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
x86_64) target="x86_64-unknown-linux-gnu" ;;
|
|
aarch64) target="aarch64-unknown-linux-gnu" ;;
|
|
*) error "Unsupported arch: $arch"; exit 1 ;;
|
|
esac
|
|
|
|
if [[ "$STALWART_VERSION" == "latest" ]]; then
|
|
api="https://api.github.com/repos/${STALWART_REPO}/releases/latest"
|
|
warn "Using 'latest' — pin STALWART_VERSION to a tag in config.env after this install."
|
|
else
|
|
api="https://api.github.com/repos/${STALWART_REPO}/releases/tags/${STALWART_VERSION}"
|
|
fi
|
|
|
|
asset_url="$(curl -fsSL "$api" \
|
|
| grep -oE "https://[^\"]+/stalwart-${target}\.tar\.gz" \
|
|
| grep -v foundationdb \
|
|
| head -n1)"
|
|
if [[ -z "$asset_url" ]]; then
|
|
error "Could not find a ${target} .tar.gz asset in ${STALWART_REPO}@${STALWART_VERSION}."
|
|
error "Check the release assets or set STALWART_REPO/STALWART_VERSION."
|
|
exit 1
|
|
fi
|
|
|
|
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
|
info "Downloading $asset_url"
|
|
curl -fsSL "$asset_url" -o "$tmp/stalwart.tar.gz"
|
|
tar -xzf "$tmp/stalwart.tar.gz" -C "$tmp"
|
|
bin="$(find "$tmp" -type f \( -name stalwart -o -name stalwart-mail \) | head -n1)"
|
|
if [[ -z "$bin" ]]; then
|
|
error "No 'stalwart'/'stalwart-mail' binary found in the archive."
|
|
exit 1
|
|
fi
|
|
systemctl stop stalwart-mail 2>/dev/null || true
|
|
install -o stalwart -g stalwart -m 0755 "$bin" "$PREFIX/bin/stalwart"
|
|
ok "Installed $("$PREFIX/bin/stalwart" --version 2>/dev/null || echo 'stalwart binary')."
|
|
|
|
# ── Step 3: config.json (v0.16 datastore) + secrets EnvironmentFile ────────
|
|
# v0.16 dropped TOML: config.json describes ONLY the datastore; every other
|
|
# setting (listeners, auth, TLS, domains, DKIM, spam, webhooks) lives in the DB
|
|
# and is managed via the web UI / stalwart-cli / platform-api (JMAP).
|
|
info "Step 3: config.json (v0.16 datastore-only) + secrets env..."
|
|
install -o stalwart -g stalwart -m 0640 "$SCRIPT_DIR/config.json" "$PREFIX/etc/config.json"
|
|
umask 077
|
|
cat > "$PREFIX/etc/stalwart.env" <<EOF
|
|
# Generated by install.sh from config.env — DO NOT commit. Bootstrap secrets
|
|
# platform-api uses to authenticate to Stalwart's management API (set the
|
|
# fallback admin to this on first DB setup).
|
|
STALWART_ADMIN_PASSWORD=${STALWART_ADMIN_PASSWORD}
|
|
STALWART_WEBHOOK_SECRET=${STALWART_WEBHOOK_SECRET}
|
|
EOF
|
|
chown root:stalwart "$PREFIX/etc/stalwart.env"
|
|
chmod 0640 "$PREFIX/etc/stalwart.env"
|
|
ok "Config + secrets installed."
|
|
|
|
# ── Step 4: self-signed bootstrap cert (only if none yet) ──────────────────
|
|
if [[ ! -s "$PREFIX/etc/tls/cert.pem" ]]; then
|
|
info "Step 4: generating self-signed bootstrap cert (cert-sync replaces it)..."
|
|
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
|
-keyout "$PREFIX/etc/tls/key.pem" \
|
|
-out "$PREFIX/etc/tls/cert.pem" \
|
|
-subj "/CN=mail.dezky.eu" >/dev/null 2>&1
|
|
chown stalwart:stalwart "$PREFIX/etc/tls/"*.pem
|
|
chmod 0644 "$PREFIX/etc/tls/cert.pem"; chmod 0640 "$PREFIX/etc/tls/key.pem"
|
|
ok "Bootstrap cert in place."
|
|
else
|
|
ok "Step 4: TLS cert already present — keeping it."
|
|
fi
|
|
|
|
# ── Step 5: cert-sync + systemd units ──────────────────────────────────────
|
|
info "Step 5: installing cert-sync + systemd units..."
|
|
install -o root -g root -m 0755 "$SCRIPT_DIR/cert-sync.sh" "$PREFIX/cert-sync.sh"
|
|
install -m 0644 "$SCRIPT_DIR/stalwart-mail.service" /etc/systemd/system/stalwart-mail.service
|
|
install -m 0644 "$SCRIPT_DIR/stalwart-cert-sync.service" /etc/systemd/system/stalwart-cert-sync.service
|
|
install -m 0644 "$SCRIPT_DIR/stalwart-cert-sync.timer" /etc/systemd/system/stalwart-cert-sync.timer
|
|
systemctl daemon-reload
|
|
systemctl enable --now stalwart-mail.service
|
|
systemctl enable --now stalwart-cert-sync.timer
|
|
ok "Services enabled."
|
|
|
|
# Try an immediate cert sync (no-op until cert-manager has issued the secret)
|
|
"$PREFIX/cert-sync.sh" || true
|
|
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ Stalwart installed & running ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
systemctl --no-pager --lines=0 status stalwart-mail || true
|
|
echo ""
|
|
warn "Follow-ups:"
|
|
warn " • v0.16 has NO fallback admin — STALWART_ADMIN_PASSWORD alone does"
|
|
warn " nothing. Bootstrap the management account once:"
|
|
warn " 1. add to $PREFIX/etc/stalwart.env:"
|
|
warn " STALWART_RECOVERY_MODE=1"
|
|
warn " STALWART_RECOVERY_ADMIN=admin:<STALWART_ADMIN_PASSWORD>"
|
|
warn " and restart stalwart-mail (recovery listener on :8080)"
|
|
warn " 2. via JMAP (basic auth, those creds): x:Domain/set create the"
|
|
warn " primary domain, then x:Account/set create name=admin in it with"
|
|
warn " roles {\"@type\":\"Admin\"} and a Password credential"
|
|
warn " 3. remove the two STALWART_RECOVERY_* lines, restart again"
|
|
warn " • PTR/rDNS for the server IP MUST be 'mail.dezky.eu' (Hetzner Robot)."
|
|
warn " • Publish DNS at simply.com: MX → mail.dezky.eu, SPF, DMARC; per-domain"
|
|
warn " DKIM records come from Stalwart's dnsZoneFile via platform-api."
|
|
warn " • platform-api (k3s) env: STALWART_API_URL=http://<node-ip>:8080"
|
|
warn " STALWART_ADMIN_USER=admin@<primary-domain> (full address — v0.16 logins)"
|
|
warn " STALWART_ADMIN_PASSWORD=<same as here>"
|
|
warn " STALWART_WEBHOOK_SECRET=<same as here> STALWART_PROVISIONING_ENABLED=true"
|