#!/usr/bin/env bash # ============================================================================== # GoldenChart 공통 배포 스크립트 v2 — Docker Compose # 호출: Git post-receive (Track A) | Jenkins (Track B) # 설치: /Volumes/ADATA/git/deploy.sh # ============================================================================== set -euo pipefail GIT_DIR="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}" WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}" BRANCH="${BRANCH:-main}" DOCKER_COMPOSE_FILE="${DOCKER_COMPOSE_FILE:-${WORK_TREE}/docker-compose.yml}" DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/deploy.log}" LOG_TAG="[goldenChart-deploy]" SERVICE_NAME="${SERVICE_NAME:-all}" BACKEND_HEALTH_URL="${BACKEND_HEALTH_URL:-http://localhost:8080/api/indicators/list}" FRONTEND_URL="${FRONTEND_URL:-http://localhost:80}" export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}" mkdir -p "$(dirname "$DEPLOY_LOG")" exec >> "$DEPLOY_LOG" 2>&1 log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_TAG $*"; } info() { log "[INFO] $*"; } warn() { log "[WARN] $*"; } err() { log "[ERROR] $*"; exit 1; } log "======================================================" log " GoldenChart 배포 시작 (v2 Docker)" log " 서비스 : $SERVICE_NAME" log " 브랜치 : $BRANCH" log " 작업트리: $WORK_TREE" log " compose : $DOCKER_COMPOSE_FILE" log " 실행자 : $(whoami)" log "======================================================" LOCK_FILE="/tmp/goldenchart_deploy.lock" if [ -f "$LOCK_FILE" ]; then LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null || echo "") if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then err "다른 배포 진행 중 (PID: $LOCK_PID)" fi rm -f "$LOCK_FILE" fi echo $$ > "$LOCK_FILE" trap 'rm -f "$LOCK_FILE"; log "락 파일 해제"' EXIT # Phase 1 info "[Phase 1] Git checkout" [ ! -d "$WORK_TREE" ] && mkdir -p "$WORK_TREE" [ ! -d "$GIT_DIR" ] && err "GIT_DIR 없음: $GIT_DIR" git --work-tree="$WORK_TREE" --git-dir="$GIT_DIR" checkout -f "$BRANCH" info "체크아웃 완료: $(git --git-dir="$GIT_DIR" rev-parse --short HEAD)" [ ! -f "$DOCKER_COMPOSE_FILE" ] && err "docker-compose.yml 없음: $DOCKER_COMPOSE_FILE" cd "$WORK_TREE" # Phase 2 — Docker 이미지 빌드 (호스트 npm 사용 안 함 → postcss 홈 디렉터리 간섭 방지) info "[Phase 2] Docker 이미지 빌드 ($SERVICE_NAME)" build_frontend() { info "docker compose build frontend" docker compose -f "$DOCKER_COMPOSE_FILE" build frontend } build_backend() { info "docker compose build backend" docker compose -f "$DOCKER_COMPOSE_FILE" build backend } case "$SERVICE_NAME" in frontend) build_frontend ;; backend) build_backend ;; all) build_backend build_frontend ;; docker-only) info "빌드 스킵 (docker-only)" ;; python|ai-agent) warn "goldenChart는 PM2 python 배포 미사용 — docker-only 권장" ;; *) warn "알 수 없는 SERVICE_NAME=$SERVICE_NAME" ;; esac # Phase 3 info "[Phase 3] Docker Compose up" case "$SERVICE_NAME" in frontend) docker compose -f "$DOCKER_COMPOSE_FILE" up -d --no-deps frontend ;; backend) docker compose -f "$DOCKER_COMPOSE_FILE" up -d mysql docker compose -f "$DOCKER_COMPOSE_FILE" up -d --no-deps backend ;; all|docker-only) docker compose -f "$DOCKER_COMPOSE_FILE" up -d ;; esac # Phase 4 info "[Phase 4] 헬스체크" check_url() { local name="$1" url="$2" retries="${3:-12}" interval="${4:-5}" local i code for i in $(seq 1 "$retries"); do code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000") if [[ "$code" =~ ^2 ]]; then info "$name OK (HTTP $code)" return 0 fi warn "$name 대기 ($i/$retries) HTTP $code" sleep "$interval" done warn "$name 헬스체크 실패: $url" return 1 } case "$SERVICE_NAME" in frontend) check_url "Frontend" "$FRONTEND_URL" || true ;; backend) check_url "Backend" "$BACKEND_HEALTH_URL" || true ;; all|docker-only) check_url "Frontend" "$FRONTEND_URL" || true check_url "Backend" "$BACKEND_HEALTH_URL" || true ;; esac log "======================================================" log " 배포 완료: $(date '+%Y-%m-%d %H:%M:%S')" log " 커밋: $(git --git-dir="$GIT_DIR" log --oneline -1 2>/dev/null || echo N/A)" log "======================================================" docker compose -f "$DOCKER_COMPOSE_FILE" ps 2>/dev/null || docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"