#!/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/mail-server}" 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://[^\"]+${target}[^\"]+\.tar\.gz" \ | 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 + secrets EnvironmentFile ─────────────────────────────── info "Step 3: config.toml + secrets env..." install -o stalwart -g stalwart -m 0640 "$SCRIPT_DIR/config.toml" "$PREFIX/etc/config.toml" umask 077 cat > "$PREFIX/etc/stalwart.env" </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 " • 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://:8080" warn " STALWART_ADMIN_USER=admin STALWART_ADMIN_PASSWORD=" warn " STALWART_WEBHOOK_SECRET= STALWART_PROVISIONING_ENABLED=true"