Files
dezky/infrastructure/production/host/stalwart/cert-sync.sh
T
Ronni Baslund 3831c85285 feat(infra): production host bootstrap and bare-metal Stalwart scaffolding
Host provisioning for the single-server production target: SSH + firewall
hardening (nftables allowlist), k3s node registration, bare-metal Stalwart
install with systemd units and TLS cert-sync from the cluster secret, and
Restic encrypted backup/restore (primary + DR) with timer units. Host-specific
secrets live in config.env (gitignored); config.env.example is the template.
Also gitignores MemPalace per-project files.
2026-06-07 00:19:48 +02:00

78 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Sync the mail.dezky.eu TLS cert from the cluster (issued by cert-manager) to
# Stalwart on the host. The host IS the k3s node, so we read the secret via the
# local kubeconfig — no external machinery. Reloads Stalwart only when the cert
# actually changed (cert-manager renews ~30 days before expiry).
#
# Run by stalwart-cert-sync.timer (every 12h + on boot). Safe to run by hand.
#
# Forward dependency: needs the fleet layer to have created the TLS secret
# (default: namespace 'mail', secret 'mail-tls'). Until then this is a no-op and
# Stalwart keeps using the self-signed bootstrap cert from install.sh.
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; }
TLS_NAMESPACE="${TLS_NAMESPACE:-mail}"
TLS_SECRET="${TLS_SECRET:-mail-tls}"
TLS_DIR="/opt/stalwart/etc/tls"
KUBECONFIG_PATH="${KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}"
# kubectl: prefer standalone, fall back to the k3s-bundled one
if command -v kubectl >/dev/null 2>&1; then
KUBECTL=(kubectl)
elif command -v k3s >/dev/null 2>&1; then
KUBECTL=(k3s kubectl)
else
error "Neither kubectl nor k3s found — is the node provisioned yet?"
exit 1
fi
export KUBECONFIG="$KUBECONFIG_PATH"
# Pull the secret (no-op if it doesn't exist yet)
if ! "${KUBECTL[@]}" -n "$TLS_NAMESPACE" get secret "$TLS_SECRET" >/dev/null 2>&1; then
warn "Secret ${TLS_NAMESPACE}/${TLS_SECRET} not present yet — cert-manager hasn't issued it. Skipping."
exit 0
fi
TMP_CRT="$(mktemp)"; TMP_KEY="$(mktemp)"
trap 'rm -f "$TMP_CRT" "$TMP_KEY"' EXIT
"${KUBECTL[@]}" -n "$TLS_NAMESPACE" get secret "$TLS_SECRET" \
-o jsonpath='{.data.tls\.crt}' | base64 -d > "$TMP_CRT"
"${KUBECTL[@]}" -n "$TLS_NAMESPACE" get secret "$TLS_SECRET" \
-o jsonpath='{.data.tls\.key}' | base64 -d > "$TMP_KEY"
if [[ ! -s "$TMP_CRT" || ! -s "$TMP_KEY" ]]; then
error "Fetched cert or key is empty — leaving current cert in place."
exit 1
fi
# Only reload if something changed (compare hashes)
changed=0
mkdir -p "$TLS_DIR"
if ! cmp -s "$TMP_CRT" "$TLS_DIR/cert.pem" 2>/dev/null; then changed=1; fi
if ! cmp -s "$TMP_KEY" "$TLS_DIR/key.pem" 2>/dev/null; then changed=1; fi
if [[ $changed -eq 0 ]]; then
info "Cert unchanged — nothing to do."
exit 0
fi
install -o stalwart -g stalwart -m 0644 "$TMP_CRT" "$TLS_DIR/cert.pem"
install -o stalwart -g stalwart -m 0640 "$TMP_KEY" "$TLS_DIR/key.pem"
ok "Updated mail TLS cert from ${TLS_NAMESPACE}/${TLS_SECRET}."
# SIGHUP Stalwart to reload certs without dropping connections
if systemctl is-active --quiet stalwart-mail; then
systemctl reload stalwart-mail && ok "Reloaded stalwart-mail (SIGHUP)."
else
warn "stalwart-mail not active — cert staged, will be used on next start."
fi