#!/usr/bin/env bash # Sync Let's Encrypt cert SANs with dashboard hosts from the API. # Cron: 0 */2 * * * /opt/meshkee/dashboards/deploy/ssl-sync.sh >> /var/log/meshkee-ssl-sync.log 2>&1 set -euo pipefail CONF=/etc/meshkee/ssl-sync.env # shellcheck disable=SC1090 source "$CONF" API_URL="${SSL_SYNC_API_URL:-https://api.meshkee.com/api/v1/internal/ssl/hosts}" CERT_NAME="${SSL_CERT_NAME:-meshkee-dashboards}" EMAIL="${SSL_EMAIL:-info@meshkee.com}" WEBROOT="${SSL_WEBROOT:-/var/www/certbot}" mkdir -p "$WEBROOT" if [[ -z "${SSL_SYNC_TOKEN:-}" ]]; then echo "$(date -Is) ERROR: SSL_SYNC_TOKEN not set in $CONF" exit 1 fi TMP=$(mktemp) trap 'rm -f "$TMP"' EXIT HTTP_CODE=$(curl -sS -o "$TMP" -w '%{http_code}' \ -H "X-SSL-Sync-Token: ${SSL_SYNC_TOKEN}" \ "$API_URL") if [[ "$HTTP_CODE" != "200" ]]; then echo "$(date -Is) ERROR: API returned HTTP $HTTP_CODE" cat "$TMP" || true exit 1 fi mapfile -t HOSTS < <(python3 -c ' import json, sys data = json.load(open(sys.argv[1])) for h in data.get("hosts") or []: h = str(h).strip().lower() if h: print(h) ' "$TMP") if [[ ${#HOSTS[@]} -eq 0 ]]; then echo "$(date -Is) ERROR: empty host list from API" exit 1 fi # Always ensure admin + bootstrap tenant exist even if DB empty EXTRA_HOSTS=(manage.meshkee.com business.sanihome.ir customer.sanihome.ir) declare -A SEEN=() FINAL=() for h in "${HOSTS[@]}" "${EXTRA_HOSTS[@]}"; do [[ -n "${SEEN[$h]:-}" ]] && continue SEEN[$h]=1 FINAL+=("$h") done LIVE_DIR="/etc/letsencrypt/live/${CERT_NAME}" NEED_ISSUE=0 if [[ ! -f "${LIVE_DIR}/fullchain.pem" ]]; then NEED_ISSUE=1 else CURRENT=$(openssl x509 -in "${LIVE_DIR}/fullchain.pem" -noout -text \ | awk '/DNS:/{gsub(/DNS:/,""); gsub(/,/, "\n"); print}' \ | tr -d ' ' | tr '[:upper:]' '[:lower:]' | sort -u) for h in "${FINAL[@]}"; do if ! grep -qxF "$h" <<<"$CURRENT"; then NEED_ISSUE=1 break fi done fi if [[ "$NEED_ISSUE" -eq 0 ]]; then echo "$(date -Is) OK: cert covers ${#FINAL[@]} hosts" exit 0 fi echo "$(date -Is) Expanding cert for: ${FINAL[*]}" ARGS=(-d) ARGS=() for h in "${FINAL[@]}"; do ARGS+=(-d "$h") done certbot certonly \ --webroot -w "$WEBROOT" \ --cert-name "$CERT_NAME" \ --email "$EMAIL" \ --agree-tos \ --non-interactive \ --expand \ "${ARGS[@]}" nginx -t && systemctl reload nginx echo "$(date -Is) Cert updated and nginx reloaded"