exdev 자동 배포(Jenkins/post-receive) 및 billcare.exdev.co.kr HTTPS 연동
git push main 시 Docker Compose 빌드·배포하고 Caddy로 https://billcare.exdev.co.kr 접속을 지원합니다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
pipeline {
|
||||
agent any
|
||||
parameters {
|
||||
choice(name: 'SERVICE_NAME', choices: ['all', 'frontend', 'backend', 'docker-only'], description: '배포 대상')
|
||||
booleanParam(name: 'DEPLOY_APPROVED', defaultValue: true, description: '배포 승인')
|
||||
}
|
||||
stages {
|
||||
stage('Approve') {
|
||||
steps {
|
||||
script {
|
||||
if (!params.DEPLOY_APPROVED) {
|
||||
error('DEPLOY_APPROVED=true 필요')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:$PATH"
|
||||
echo "=== BillCare Jenkins Build #${BUILD_NUMBER} ==="
|
||||
echo "시각: $(date)"
|
||||
SERVICE_NAME=${SERVICE_NAME} /Volumes/ADATA/git/deploy-billcare.sh
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh 'tail -40 /Volumes/ADATA/logs/deploy/billcare-deploy.log || true'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# BillCare 서버 배포 가이드 (exdev.co.kr + Jenkins)
|
||||
|
||||
## 개요
|
||||
|
||||
| 트랙 | 트리거 | 동작 |
|
||||
|------|--------|------|
|
||||
| Track A | `git push origin main` | `post-receive` → `deploy-billcare.sh` |
|
||||
| Track B | Jenkins `BillCare-Pipeline` | `deploy-billcare.sh` |
|
||||
|
||||
## 경로
|
||||
|
||||
| 항목 | 경로 |
|
||||
|------|------|
|
||||
| bare repo | `/Volumes/ADATA/git/BillCare.git` |
|
||||
| work-tree | `/Users/aidev/apps/BillCare` |
|
||||
| 배포 스크립트 | `/Volumes/ADATA/git/deploy-billcare.sh` |
|
||||
| 로그 | `/Volumes/ADATA/logs/deploy/billcare-deploy.log` |
|
||||
| HTTPS | https://billcare.exdev.co.kr |
|
||||
|
||||
## 호스트 포트
|
||||
|
||||
| 서비스 | 포트 |
|
||||
|--------|------|
|
||||
| frontend (Caddy upstream) | **83** |
|
||||
| backend (직접 헬스체크) | **18082** |
|
||||
| mysql | **3308** |
|
||||
|
||||
## 로컬 push
|
||||
|
||||
```bash
|
||||
cd /Users/macbook/dev/billCare
|
||||
git push origin main
|
||||
```
|
||||
+7
-4
@@ -2,13 +2,14 @@ services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: billcare-mysql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: billcare
|
||||
MYSQL_USER: billcare
|
||||
MYSQL_PASSWORD: billcare
|
||||
ports:
|
||||
- "3308:3306"
|
||||
- "${MYSQL_PORT:-3308}:3306"
|
||||
volumes:
|
||||
- billcare_mysql:/var/lib/mysql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
@@ -24,6 +25,7 @@ services:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: billcare-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
@@ -31,12 +33,12 @@ services:
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/billcare?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
|
||||
SPRING_DATASOURCE_USERNAME: billcare
|
||||
SPRING_DATASOURCE_PASSWORD: billcare
|
||||
APP_CORS_ORIGINS: http://localhost:82,http://127.0.0.1:82,http://localhost:5174
|
||||
APP_CORS_ORIGINS: ${APP_CORS_ORIGINS:-http://localhost:82,http://127.0.0.1:82,http://localhost:5174,https://billcare.exdev.co.kr}
|
||||
APP_UPLOAD_DIR: /app/storage/uploads
|
||||
volumes:
|
||||
- billcare_uploads:/app/storage/uploads
|
||||
ports:
|
||||
- "8082:8080"
|
||||
- "${BACKEND_PORT:-8082}:8080"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/api/health"]
|
||||
interval: 5s
|
||||
@@ -49,11 +51,12 @@ services:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: billcare-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "82:80"
|
||||
- "${FRONTEND_PORT:-82}:80"
|
||||
|
||||
volumes:
|
||||
billcare_mysql:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# BillCare — exdev Caddy 서브도메인 스니펫
|
||||
# host 포트 83 → billcare-frontend
|
||||
# 실제 적용: ex-chatbot-stack Caddyfile.exdev 에 병합 후 caddy reload
|
||||
|
||||
(billcare_upstream) {
|
||||
reverse_proxy host.docker.internal:{$BILLCARE_HOST_PORT:83} {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
read_timeout 120s
|
||||
write_timeout 120s
|
||||
versions 1.1 2
|
||||
}
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
}
|
||||
}
|
||||
|
||||
https://{$BILLCARE_DOMAIN:billcare.exdev.co.kr} {
|
||||
# 공유 cert SAN 에 없을 수 있음 → Caddy 자체 ACME(TLS-ALPN) 사용
|
||||
handle {
|
||||
import billcare_upstream
|
||||
}
|
||||
}
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# BillCare 배포 스크립트 — Docker Compose
|
||||
# 호출: Git post-receive (Track A) | Jenkins (Track B)
|
||||
# 서버 설치: /Volumes/ADATA/git/deploy-billcare.sh
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/git/BillCare.git}"
|
||||
WORK_TREE="${WORK_TREE:-/Users/aidev/apps/BillCare}"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
DOCKER_COMPOSE_FILE="${DOCKER_COMPOSE_FILE:-${WORK_TREE}/docker-compose.yml}"
|
||||
DEPLOY_LOG="${DEPLOY_LOG:-/Volumes/ADATA/logs/deploy/billcare-deploy.log}"
|
||||
LOG_TAG="[billcare-deploy]"
|
||||
SERVICE_NAME="${SERVICE_NAME:-all}"
|
||||
DOCKER_NO_CACHE="${DOCKER_NO_CACHE:-0}"
|
||||
BACKEND_HEALTH_URL="${BACKEND_HEALTH_URL:-http://127.0.0.1:18082/api/health}"
|
||||
FRONTEND_URL="${FRONTEND_URL:-http://127.0.0.1:83/}"
|
||||
|
||||
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
|
||||
# 서버 포트 (host) — 로컬 개발 82/8082 와 분리, 8082는 kakao-skill 사용 중
|
||||
export FRONTEND_PORT="${FRONTEND_PORT:-83}"
|
||||
export BACKEND_PORT="${BACKEND_PORT:-18082}"
|
||||
export MYSQL_PORT="${MYSQL_PORT:-3308}"
|
||||
export APP_CORS_ORIGINS="${APP_CORS_ORIGINS:-https://billcare.exdev.co.kr,http://billcare.exdev.co.kr,http://localhost:83,http://127.0.0.1:83}"
|
||||
|
||||
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 " BillCare 배포 시작"
|
||||
log " 서비스 : $SERVICE_NAME"
|
||||
log " 브랜치 : $BRANCH"
|
||||
log " 작업트리: $WORK_TREE"
|
||||
log " compose : $DOCKER_COMPOSE_FILE"
|
||||
log " 실행자 : $(whoami)"
|
||||
log "======================================================"
|
||||
|
||||
LOCK_FILE="/tmp/billcare_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"
|
||||
COMMIT="$(git --git-dir="$GIT_DIR" rev-parse HEAD)"
|
||||
info "체크아웃 완료: $(git --git-dir="$GIT_DIR" rev-parse --short HEAD)"
|
||||
|
||||
if [ -d "$WORK_TREE/.git" ]; then
|
||||
err "work-tree에 중첩 .git 존재 — 제거 후 재배포: rm -rf $WORK_TREE/.git"
|
||||
fi
|
||||
WT_HEAD="$(git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" rev-parse HEAD)"
|
||||
[ "$WT_HEAD" = "$COMMIT" ] || err "work-tree HEAD 불일치: $WT_HEAD != $COMMIT"
|
||||
|
||||
[ ! -f "$DOCKER_COMPOSE_FILE" ] && err "docker-compose.yml 없음: $DOCKER_COMPOSE_FILE"
|
||||
cd "$WORK_TREE"
|
||||
|
||||
# Phase 2
|
||||
info "[Phase 2] Docker 이미지 빌드 ($SERVICE_NAME)"
|
||||
|
||||
_dc_build() {
|
||||
local svc="$1"
|
||||
if [ "$DOCKER_NO_CACHE" = "1" ]; then
|
||||
info "docker compose build --no-cache $svc"
|
||||
docker compose -f "$DOCKER_COMPOSE_FILE" build --no-cache "$svc"
|
||||
else
|
||||
info "docker compose build $svc"
|
||||
docker compose -f "$DOCKER_COMPOSE_FILE" build "$svc"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$SERVICE_NAME" in
|
||||
frontend) _dc_build frontend ;;
|
||||
backend) _dc_build backend ;;
|
||||
all)
|
||||
_dc_build backend
|
||||
_dc_build frontend
|
||||
;;
|
||||
docker-only) info "빌드 스킵 (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:-20}" 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 "Backend" "$BACKEND_HEALTH_URL" || true
|
||||
check_url "Frontend" "$FRONTEND_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 " URL: https://billcare.exdev.co.kr"
|
||||
log "======================================================"
|
||||
|
||||
docker compose -f "$DOCKER_COMPOSE_FILE" ps 2>/dev/null || docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# BillCare post-receive — Track A (git push main → deploy-billcare.sh)
|
||||
# 설치: cp scripts/post-receive /Volumes/ADATA/git/BillCare.git/hooks/post-receive && chmod +x ...
|
||||
set -euo pipefail
|
||||
|
||||
GIT_DIR="/Volumes/ADATA/git/BillCare.git"
|
||||
WORK_TREE="${WORK_TREE:-/Users/aidev/apps/BillCare}"
|
||||
DEPLOY_SCRIPT="/Volumes/ADATA/git/deploy-billcare.sh"
|
||||
SUMMARY_SCRIPT="/Volumes/ADATA/git/git-push-summary.sh"
|
||||
DEPLOY_LOG="/Volumes/ADATA/logs/deploy/billcare-deploy.log"
|
||||
SUMMARY_DIR="/Volumes/ADATA/logs/deploy/push-summaries"
|
||||
HOOK_TAG="[billcare-post-receive]"
|
||||
|
||||
mkdir -p "$(dirname "$DEPLOY_LOG")" "$SUMMARY_DIR"
|
||||
|
||||
echo ""
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG =============================="
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG Track A — git push 수신"
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG =============================="
|
||||
|
||||
while read -r OLDREV NEWREV REFNAME; do
|
||||
BRANCH_NAME="${REFNAME#refs/heads/}"
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG 브랜치: $BRANCH_NAME ($OLDREV → $NEWREV)"
|
||||
|
||||
if [ -x "$SUMMARY_SCRIPT" ]; then
|
||||
SUMMARY_FILE="$("$SUMMARY_SCRIPT" "$GIT_DIR" "$OLDREV" "$NEWREV" "$REFNAME" "$SUMMARY_DIR" || true)"
|
||||
if [ -n "${SUMMARY_FILE:-}" ] && [ -f "$SUMMARY_FILE" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG 변경 요약: $SUMMARY_FILE"
|
||||
head -20 "$SUMMARY_FILE" | sed "s/^/$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG /"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$BRANCH_NAME" = "main" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG main → deploy-billcare.sh 실행"
|
||||
export SERVICE_NAME="${SERVICE_NAME:-all}"
|
||||
if [ ! -x "$DEPLOY_SCRIPT" ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG [ERROR] 배포 스크립트 없음: $DEPLOY_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
"$DEPLOY_SCRIPT" 2>&1 | tee -a "$DEPLOY_LOG"
|
||||
EXIT_CODE=${PIPESTATUS[0]}
|
||||
if [ "$EXIT_CODE" -ne 0 ]; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG [ERROR] 배포 실패 (exit: $EXIT_CODE)"
|
||||
exit "$EXIT_CODE"
|
||||
fi
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG 배포 완료 → https://billcare.exdev.co.kr"
|
||||
else
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG $BRANCH_NAME — 배포 생략 (main만 자동 배포)"
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user