#!/usr/bin/env bash
#   2026-06-04 jshan  PRISM Cosmos OneClick SSL 구성 — install.sh --ssl 진입점.
#                     고객이 nginx/compose 를 전혀 안 건드리게: 산출물은 전부 고객존에 "생성"(편집 X).
#                       · $INSTALL_ROOT/certs/server.crt|key            (cert 배치 — 표준 위치)
#                       · $INSTALL_ROOT/ops/nginx/custom/ssl.conf       (https top-level server{}, _locations.inc 재사용)
#                       · $INSTALL_ROOT/ops/docker/docker-compose.override.yml (gateway 443 포트 + cert 마운트)
#                     base 파일(default.conf / docker-compose.<env>.yml)은 무수정. compose -f base -f override 자동 병합.
#                     5계명 정합: heredoc 템플릿 생성(sed 정규식 치환 X), 폴백 X, cert 누락/형식오류 = 명확 raise.
#
#   호출 환경변수 (install.sh / install_core.sh 가 export):
#     INSTALL_ROOT  설치 루트 (예: /opt/prism-cosmos)
#     SSL_PORT      https 노출 포트 (기본 443)
#     DOMAIN        server_name (선택, 비면 _)
#     CERT_PATH     인증서 디렉토리 (선택, 비면 self-signed 생성)
#                   인식 이름 — cert: server.crt/fullchain.pem/cert.pem · key: server.key/privkey.pem/key.pem
#     PRISM_ENV     prod|staging|dev (compose 본체 선택)
#     PROJECT       compose project name
#     GATEWAY_IMAGE nginx -t 검증용 이미지 (기본 trinity-prism-gateway:${VERSION:-1.0.0})
set -euo pipefail

RED='\033[31m'; GREEN='\033[32m'; CYAN='\033[36m'; YELLOW='\033[33m'; NC='\033[0m'
step() { printf "${CYAN}▶ %s${NC}\n" "$*"; }
ok()   { printf "${GREEN}  ✓${NC} %s\n" "$*"; }
warn() { printf "${YELLOW}  ⚠${NC} %s\n" "$*"; }
die()  { printf "${RED}  ✗${NC} %s\n" "$*" >&2; exit 1; }

# ── 입력 정합 (정확한 오류 raise, 폴백 X) ─────────────────────────────────
INSTALL_ROOT="${INSTALL_ROOT:?INSTALL_ROOT 미지정. install.sh 가 export 해야 함}"
SSL_PORT="${SSL_PORT:-443}"
DOMAIN="${DOMAIN:-}"
CERT_PATH="${CERT_PATH:-}"
PRISM_ENV="${PRISM_ENV:-prod}"
PROJECT="${PROJECT:-prism-cosmos-prod}"
VERSION="${VERSION:-1.0.0}"
GATEWAY_IMAGE="${GATEWAY_IMAGE:-trinity-prism-gateway:${VERSION}}"

case "$SSL_PORT" in
  ''|*[!0-9]*) die "잘못된 --ssl-port=$SSL_PORT (정수 아님)" ;;
esac
[ "$SSL_PORT" -ge 1 ] && [ "$SSL_PORT" -le 65535 ] || die "--ssl-port=$SSL_PORT 범위 밖 (1~65535)"
if [ "$SSL_PORT" -lt 1024 ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then
  die "--ssl-port=$SSL_PORT < 1024 = root 권한 필요. 비root 면 --ssl-port=1024+ 지정."
fi

CERTS_DIR="$INSTALL_ROOT/certs"
NGINX_CUSTOM_DIR="$INSTALL_ROOT/ops/nginx/custom"
COMPOSE_DIR="$INSTALL_ROOT/ops/docker"
OVERRIDE_FILE="$COMPOSE_DIR/docker-compose.override.yml"
#   prod = docker-compose.yml (통일 base) / dev = docker-compose.dev.yml
#   2026-07-06 yjmoon  prod 파일명 하드코딩 → 실존 감지. 6-28 compose 통일 이전 최초설치 서버는
#     docker-compose.prod.yml 이 남아있을 수 있음(compose 패치 미수신분) → 폴백으로 커버.
#     신규 설치·통일 이후 서버는 docker-compose.yml 이라 폴백 미발동(회귀 0).
if [ "$PRISM_ENV" = "prod" ]; then
  BASE_COMPOSE="$COMPOSE_DIR/docker-compose.yml"
  [ -f "$BASE_COMPOSE" ] || BASE_COMPOSE="$COMPOSE_DIR/docker-compose.prod.yml"
else
  BASE_COMPOSE="$COMPOSE_DIR/docker-compose.${PRISM_ENV}.yml"
fi

[ -d "$INSTALL_ROOT/ops/nginx" ] || die "nginx 디렉토리 없음: $INSTALL_ROOT/ops/nginx (install_core 의 cp -r ops 선행 필요)"
[ -f "$INSTALL_ROOT/ops/nginx/_locations.inc" ] || die "_locations.inc 없음. Phase0 snippet 누락 (패키지 빌드 점검)"
[ -f "$BASE_COMPOSE" ] || die "base compose 없음: $COMPOSE_DIR/docker-compose.yml (통일 이전 설치는 docker-compose.prod.yml)"

mkdir -p "$CERTS_DIR" "$NGINX_CUSTOM_DIR"
chmod 700 "$CERTS_DIR"

# ── 1. cert 배치 ──────────────────────────────────────────────────────────
step "SSL 1/4  인증서 배치 ($CERTS_DIR)"
SELF_SIGNED=0
if [ -n "$CERT_PATH" ]; then
  [ -d "$CERT_PATH" ] || die "--cert-path 디렉토리 없음: $CERT_PATH"
  #   2026-07-08 yjmoon  CA 발급 번들 파일명 수용 — server.crt 고정 요구라 cert.pem/key.pem 번들이
  #     전부 die 하던 것 보완(기술지원 #265 LS오토모티브). 우선순위 고정 탐색 + 선택 파일명 로그
  #     = 어느 파일을 썼는지 항상 가시화 (silent 폴백 아님, 입력 형식 확대).
  SRC_CRT=""
  for c in server.crt fullchain.pem cert.pem; do
    if [ -f "$CERT_PATH/$c" ]; then SRC_CRT="$CERT_PATH/$c"; break; fi
  done
  SRC_KEY=""
  for k in server.key privkey.pem key.pem; do
    if [ -f "$CERT_PATH/$k" ]; then SRC_KEY="$CERT_PATH/$k"; break; fi
  done
  if [ -z "$SRC_CRT" ] || [ -z "$SRC_KEY" ]; then
    echo "  --cert-path=$CERT_PATH 안의 파일:" >&2
    ls -1 "$CERT_PATH" >&2 || true
    [ -z "$SRC_CRT" ] && warn "인증서를 못 찾음. 인식하는 이름: server.crt / fullchain.pem / cert.pem"
    [ -z "$SRC_KEY" ] && warn "개인키를 못 찾음. 인식하는 이름: server.key / privkey.pem / key.pem"
    die "발급 번들이 웹서버별 하위폴더(Nginx/Apache 등)로 나뉜 경우 nginx 용 폴더를 --cert-path 로 지정하세요."
  fi
  #   2026-07-08 yjmoon  암호 걸린 개인키 조기 감지 — 기존엔 4단계 nginx -t 에서 원인 불명 die(#265).
  #     ENCRYPTED = 전통 PEM(Proc-Type: 4,ENCRYPTED) + PKCS#8(BEGIN ENCRYPTED PRIVATE KEY) 둘 다 매치.
  if grep -q "ENCRYPTED" "$SRC_KEY"; then
    warn "해제 방법: openssl pkey -in '$SRC_KEY' -out <새폴더>/server.key   (발급 시 받은 개인키 암호 입력. RSA/EC 키 모두 동작)"
    die "개인키에 암호(passphrase)가 걸려 있음: $SRC_KEY. 웹서버가 읽을 수 없어 중단. 위 방법으로 암호 해제 후 재시도."
  fi
  #   2026-07-08 yjmoon  cert-key 짝 일치 검증 — 독립 탐색이 세대 다른 짝(옛 crt + 새 key)을 집으면
  #     4단계 nginx -t 의 범용 메시지(이미지 부재 시엔 gateway 기동 실패)로 밀리던 것을 여기서 명확히 die.
  #     openssl 없으면 skip — 4단계 nginx -t 가 그대로 백스톱 (검사 생략이지 폴백 아님).
  if command -v openssl >/dev/null 2>&1; then
    CRT_PUB=$(openssl x509 -in "$SRC_CRT" -noout -pubkey 2>/dev/null) || die "인증서 형식 오류(x509 PEM 아님): $SRC_CRT"
    KEY_PUB=$(openssl pkey -in "$SRC_KEY" -pubout 2>/dev/null) || die "개인키 형식 오류: $SRC_KEY"
    [ "$CRT_PUB" = "$KEY_PUB" ] || die "인증서와 개인키가 서로 짝이 아님: $(basename "$SRC_CRT") ↔ $(basename "$SRC_KEY"). 같은 발급분 파일인지 확인하세요."
    ok "인증서-개인키 짝 일치 확인"
  fi
  cp "$SRC_CRT" "$CERTS_DIR/server.crt"
  cp "$SRC_KEY" "$CERTS_DIR/server.key"
  ok "인증서 복사: $(basename "$SRC_CRT") + $(basename "$SRC_KEY") → $CERTS_DIR/server.{crt,key} (운영자 제공 cert)"
else
  # D1: --ssl 만 주고 cert 미제공 = self-signed 생성 (openssl heredoc 정공). 브라우저 경고 고지.
  command -v openssl >/dev/null 2>&1 || die "openssl 미설치. --cert-path 로 인증서 직접 제공 필요"
  CN="${DOMAIN:-prism.local}"
  openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
    -keyout "$CERTS_DIR/server.key" \
    -out    "$CERTS_DIR/server.crt" \
    -subj   "/C=KR/ST=Seoul/O=BI MATRIX/CN=${CN}" \
    >/dev/null 2>&1 || die "self-signed 인증서 생성 실패 (openssl)"
  SELF_SIGNED=1
  ok "self-signed 인증서 생성: CN=${CN} (3650일)"
  warn "자체서명 인증서. 브라우저가 보안 경고를 표시합니다. 운영은 --cert-path 로 정식 cert 권장."
fi
chmod 600 "$CERTS_DIR/server.key"
chmod 644 "$CERTS_DIR/server.crt"
ok "key 권한 600 / crt 644"

# ── 2. nginx custom/ssl.conf 생성 (heredoc, _locations.inc 재사용 = 중복0) ─
step "SSL 2/4  https server 블록 생성 ($NGINX_CUSTOM_DIR/ssl.conf)"
SERVER_NAME="${DOMAIN:-_}"
cat > "$NGINX_CUSTOM_DIR/ssl.conf" <<EOF
#   2026-06-04 jshan  고객존 SSL — install.sh --ssl 자동 생성 (수동 편집 X).
#     base default.conf 무수정. 이 파일은 http 컨텍스트 include 훅으로 로드되는 top-level https server{}.
#     location 은 _locations.inc 재사용 — listen 80 server 와 동일 집합 (중복0).
#     재생성: install.sh --ssl ... 또는 setup_ssl.sh 재실행. 직접 편집 시 다음 --ssl 이 덮음.
server {
    listen ${SSL_PORT} ssl;
    server_name ${SERVER_NAME};
    client_max_body_size 100M;

    root /usr/share/nginx/html;
    index index.html;

    ssl_certificate     /etc/nginx/certs/server.crt;
    ssl_certificate_key /etc/nginx/certs/server.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;
    #   SSE TLS 레코드 4k — listen 80 server 와 동일 정책 (토큰 즉시 송출).
    ssl_buffer_size     4k;

    #   HTTP 평문 요청이 ssl 포트에 오면 https 리다이렉트 (nginx 497).
    error_page 497 https://\$host:\$server_port\$request_uri;

    include /etc/nginx/conf.d/_locations.inc;
}
EOF
ok "ssl.conf 생성 (listen ${SSL_PORT} ssl, server_name ${SERVER_NAME}, _locations.inc 재사용)"

# ── 3. docker-compose.override.yml 생성 (base 무수정, -f 병합) ─────────────
step "SSL 3/4  compose override 생성 ($OVERRIDE_FILE)"
cat > "$OVERRIDE_FILE" <<EOF
#   2026-06-04 jshan  고객존 SSL override — install.sh --ssl 자동 생성 (base compose 무수정).
#     docker compose -f docker-compose.${PRISM_ENV}.yml -f docker-compose.override.yml 자동 병합.
#     ports/volumes 는 append 병합 — base 의 \${GATEWAY_PORT}:80(http) 유지 + 아래 https 포트·cert 추가.
#     ../../certs = ${INSTALL_ROOT}/certs (compose 파일 기준 상대경로, base 의 ../nginx 와 동일 규칙).
#     재생성: install.sh --ssl 재실행. 직접 편집 시 다음 --ssl 이 덮음.
services:
  gateway:
    ports:
      - "${SSL_PORT}:${SSL_PORT}"
    volumes:
      - ../../certs:/etc/nginx/certs:ro
EOF
ok "override.yml 생성 (gateway +${SSL_PORT}:${SSL_PORT}, +../../certs:/etc/nginx/certs:ro)"

# ── 4. 검증 — nginx -t (throwaway) + compose config 병합 + subnet 안전 ─────
step "SSL 4/4  검증 (nginx -t, compose config, subnet 안전)"

# 4a. nginx -t : 실 컨테이너 무영향, 일회용 gateway 이미지로 문법 검증.
if docker image inspect "$GATEWAY_IMAGE" >/dev/null 2>&1; then
  if docker run --rm \
      -v "$INSTALL_ROOT/ops/nginx:/etc/nginx/conf.d:ro" \
      -v "$CERTS_DIR/server.crt:/etc/nginx/certs/server.crt:ro" \
      -v "$CERTS_DIR/server.key:/etc/nginx/certs/server.key:ro" \
      --entrypoint nginx "$GATEWAY_IMAGE" -t >/tmp/_ssl_nginx_t.log 2>&1; then
    ok "nginx -t PASS (default.conf + custom/ssl.conf + _locations.inc)"
  else
    cat /tmp/_ssl_nginx_t.log >&2
    die "nginx -t FAIL. ssl.conf 문법 오류 (위 로그). cert 경로/형식 점검."
  fi
else
  warn "gateway 이미지($GATEWAY_IMAGE) 미발견. nginx -t skip (images/ load 후 install_core 가 실행)"
fi

# 4b. compose config 병합 정상 + subnet 안전.
if docker compose --env-file "$INSTALL_ROOT/.env" \
      -f "$BASE_COMPOSE" -f "$OVERRIDE_FILE" -p "$PROJECT" config >/tmp/_ssl_compose.yml 2>/tmp/_ssl_compose.err; then
  ok "compose -f base -f override config 병합 정상"
else
  cat /tmp/_ssl_compose.err >&2
  die "compose config 병합 실패. override.yml 점검."
fi

SAFETY="$INSTALL_ROOT/ops/scripts/check_compose_safety.sh"
if [ -x "$SAFETY" ]; then
  if bash "$SAFETY" /tmp/_ssl_compose.yml >/tmp/_ssl_safety.log 2>&1; then
    ok "subnet 안전 검사 PASS (병합 결과)"
  else
    cat /tmp/_ssl_safety.log >&2
    die "subnet 안전 검사 실패. override 가 networks/ipam 을 건드렸는지 점검."
  fi
fi

echo
ok "SSL 구성 완료. 고객이 nginx/compose 를 전혀 안 건드림 (산출물 전부 고객존 '생성')."
echo "    https 포트: ${SSL_PORT}   server_name: ${SERVER_NAME}"
#   2026-07-08 yjmoon  종료코드 버그 수정 — 마지막 줄이 `[ ... ] && echo` 라 정식 cert(SELF_SIGNED=0)면
#     전부 성공하고도 exit 1 → 호출부(enable_ssl/install_core)의 `|| die "SSL 구성 실패"` 가 오발동.
#     self-signed 만 성공하고 정식 인증서는 항상 실패하던 근본 원인(기술지원 #265). if 문은 거짓이어도 exit 0.
if [ "$SELF_SIGNED" -eq 1 ]; then
  echo "    (self-signed. 브라우저 보안 경고. 정식 cert = --cert-path 재실행.)"
fi
