#!/bin/bash
# ==============================================================================
# GUARDiA ITSM — SSL 인증서 자동 갱신 스크립트 (certbot)
# 경로: /opt/guardia/scripts/ssl/ssl_auto_renew.sh
#
# 기능:
#   1. certbot renew --dry-run 으로 사전 검증
#   2. certbot renew 실행 (갱신 대상 인증서만)
#   3. nginx/apache 서비스 재시작 (갱신 성공 시)
#   4. GUARDiA ITSM API 콜백 — 갱신 결과 전달 (SR 자동 생성)
#
# 환경변수 (실행 전 .env 또는 시스템에 설정):
#   GUARDIA_ITSM_URL     — ITSM 서버 URL (기본: http://localhost:8000)
#   GUARDIA_ITSM_TOKEN   — ITSM API Bearer 토큰
#   SSL_WEB_SERVER       — nginx 또는 apache2 (기본: nginx)
#   SSL_CERT_EMAIL       — certbot 알림 이메일
#   SSL_DOMAINS          — 갱신할 도메인 (쉼표 구분, 미설정 시 certbot 전체 갱신)
#   SSL_RENEW_DAYS       — 만료 N일 이내 갱신 시도 (기본: 30)
#   SSL_DRY_RUN_ONLY     — true 이면 dry-run 만 수행 (테스트용, 기본: false)
#   SSL_RELOAD_CMD       — 서비스 재시작 명령어 오버라이드
#
# 반환 코드:
#   0 — 성공 (갱신 없음 포함)
#   1 — 갱신 실패
#   2 — 사전 점검 실패 (dry-run 오류)
#   3 — 설치 환경 오류 (certbot 미설치 등)
# ==============================================================================

set -euo pipefail
IFS=$'\n\t'

# ── 설정 ──────────────────────────────────────────────────────────────────────
ITSM_URL="${GUARDIA_ITSM_URL:-http://localhost:8000}"
ITSM_TOKEN="${GUARDIA_ITSM_TOKEN:-}"
WEB_SERVER="${SSL_WEB_SERVER:-nginx}"
CERT_EMAIL="${SSL_CERT_EMAIL:-}"
DOMAINS="${SSL_DOMAINS:-}"
RENEW_DAYS="${SSL_RENEW_DAYS:-30}"
DRY_RUN_ONLY="${SSL_DRY_RUN_ONLY:-false}"
LOG_DIR="/var/log/guardia/ssl"
LOG_FILE="${LOG_DIR}/renew_$(date +%Y%m%d_%H%M%S).log"
SCRIPT_NAME="$(basename "$0")"

# certbot 재시작 후크 우선순위: 환경변수 > 자동 감지
if [ -n "${SSL_RELOAD_CMD:-}" ]; then
    RELOAD_CMD="${SSL_RELOAD_CMD}"
elif command -v systemctl &>/dev/null; then
    RELOAD_CMD="systemctl reload ${WEB_SERVER}"
else
    RELOAD_CMD="service ${WEB_SERVER} reload"
fi

# ── 로그 함수 ─────────────────────────────────────────────────────────────────
mkdir -p "${LOG_DIR}"
exec > >(tee -a "${LOG_FILE}") 2>&1

log()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]  $*"; }
warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]  $*"; }
err()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2; }

log "=== ${SCRIPT_NAME} 시작 ==="
log "ITSM URL     : ${ITSM_URL}"
log "WEB SERVER   : ${WEB_SERVER}"
log "RENEW DAYS   : ${RENEW_DAYS}"
log "DRY RUN ONLY : ${DRY_RUN_ONLY}"
log "LOG FILE     : ${LOG_FILE}"

# ── certbot 설치 확인 ─────────────────────────────────────────────────────────
if ! command -v certbot &>/dev/null; then
    err "certbot 명령어를 찾을 수 없습니다."
    err "설치: apt install certbot 또는 yum install certbot"
    _itsm_callback "ERROR" "certbot 미설치" "" 3
    exit 3
fi

CERTBOT_VERSION="$(certbot --version 2>&1 | head -1)"
log "certbot 버전: ${CERTBOT_VERSION}"

# ── ITSM API 콜백 함수 ────────────────────────────────────────────────────────
# 보안: ITSM 서버 주소만 포함 — IP·계정·비밀번호 전송 금지
_itsm_callback() {
    local status="$1"       # SUCCESS | FAILURE | DRY_RUN_OK | DRY_RUN_FAIL
    local message="$2"
    local renewed_domains="${3:-}"
    local exit_code="${4:-0}"

    if [ -z "${ITSM_TOKEN}" ]; then
        warn "GUARDIA_ITSM_TOKEN 미설정 — ITSM 콜백 건너뜀"
        return 0
    fi

    local payload
    payload="$(cat <<EOF
{
  "event":           "ssl_renew",
  "status":          "${status}",
  "message":         "${message}",
  "renewed_domains": "${renewed_domains}",
  "web_server":      "${WEB_SERVER}",
  "renew_days":      ${RENEW_DAYS},
  "exit_code":       ${exit_code},
  "log_file":        "${LOG_FILE}",
  "timestamp":       "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
}
EOF
)"

    # curl: 연결 실패 시 오류 무시 (ITSM가 다운돼도 갱신 스크립트는 계속 진행)
    if curl -sf \
        -X POST \
        -H "Authorization: Bearer ${ITSM_TOKEN}" \
        -H "Content-Type: application/json; charset=utf-8" \
        --data "${payload}" \
        --max-time 10 \
        "${ITSM_URL}/api/ssl/renew-callback" \
        > /dev/null 2>&1; then
        log "ITSM 콜백 전송 성공: status=${status}"
    else
        warn "ITSM 콜백 전송 실패 (서버 응답 없음 — 무시하고 계속 진행)"
    fi
}

# ── 사전 점검: dry-run ────────────────────────────────────────────────────────
log "--- 사전 점검 (dry-run) 시작 ---"

CERTBOT_ARGS=(
    renew
    --non-interactive
    --agree-tos
    "--deploy-hook" "${RELOAD_CMD}"
    "--days" "${RENEW_DAYS}"
)

if [ -n "${CERT_EMAIL}" ]; then
    CERTBOT_ARGS+=("--email" "${CERT_EMAIL}")
fi

if [ -n "${DOMAINS}" ]; then
    # 도메인 개별 지정 시 각 도메인을 --domain 으로 전달
    IFS=',' read -ra DOMAIN_LIST <<< "${DOMAINS}"
    for domain in "${DOMAIN_LIST[@]}"; do
        domain="$(echo "${domain}" | tr -d ' ')"
        [ -n "${domain}" ] && CERTBOT_ARGS+=("--domain" "${domain}")
    done
fi

if ! certbot "${CERTBOT_ARGS[@]}" --dry-run >> "${LOG_FILE}" 2>&1; then
    err "dry-run 실패 — 실제 갱신을 중단합니다."
    _itsm_callback "DRY_RUN_FAIL" "certbot dry-run 실패. 로그: ${LOG_FILE}" "" 2
    exit 2
fi

log "dry-run 성공."

if [ "${DRY_RUN_ONLY}" = "true" ]; then
    log "DRY_RUN_ONLY=true — 실제 갱신 건너뜀."
    _itsm_callback "DRY_RUN_OK" "dry-run 전용 실행 완료." "" 0
    exit 0
fi

# ── 실제 갱신 ─────────────────────────────────────────────────────────────────
log "--- 실제 갱신 시작 ---"

RENEW_OUTPUT="$(certbot "${CERTBOT_ARGS[@]}" 2>&1)" || {
    err "certbot renew 실패."
    err "${RENEW_OUTPUT}"
    _itsm_callback "FAILURE" "certbot renew 실패. 상세: ${LOG_FILE}" "" 1
    exit 1
}

log "${RENEW_OUTPUT}"

# 갱신된 인증서 도메인 추출
RENEWED=""
if echo "${RENEW_OUTPUT}" | grep -q "Successfully renewed"; then
    RENEWED="$(echo "${RENEW_OUTPUT}" \
        | grep -oP '(?<=Successfully renewed certificate for )[^\s]+' \
        | paste -sd ',' -)"
    log "갱신 완료 도메인: ${RENEWED:-없음}"
else
    log "갱신 대상 없음 (모든 인증서가 유효 기간 내)."
fi

# ── 웹서버 재시작 (갱신 발생 시) ──────────────────────────────────────────────
if [ -n "${RENEWED}" ]; then
    log "웹서버 재시작 중: ${RELOAD_CMD}"
    if eval "${RELOAD_CMD}" >> "${LOG_FILE}" 2>&1; then
        log "웹서버 재시작 성공."
    else
        warn "웹서버 재시작 실패 — 수동 확인 필요: ${RELOAD_CMD}"
    fi
fi

# ── ITSM 콜백 ─────────────────────────────────────────────────────────────────
if [ -n "${RENEWED}" ]; then
    _itsm_callback "SUCCESS" "인증서 갱신 완료." "${RENEWED}" 0
else
    _itsm_callback "SUCCESS" "갱신 대상 없음 (모두 유효)." "" 0
fi

log "=== ${SCRIPT_NAME} 완료 ==="
exit 0
