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.
This commit is contained in:
+77
@@ -0,0 +1,77 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user