#!/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. # # v0.16 NOTE: Stalwart no longer reads TLS files directly from config.toml. # A one-time x:Certificate object (management JMAP) points at these paths # with the File variant: # {"certificate":{"@type":"File","filePath":"/opt/stalwart/etc/tls/cert.pem"}, # "privateKey":{"@type":"File","filePath":"/opt/stalwart/etc/tls/key.pem"}} # Created 2026-06-10. With that in place this script's file update + reload # keeps working for renewals exactly as designed. # # 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