project-auto-wizard 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/project-auto-wizard.js +19 -0
- package/package.json +36 -0
- package/payload/coderabbit.yaml +19 -0
- package/payload/config/breaking-changes.json +1 -0
- package/payload/config/wizard-prompts.yml +62 -0
- package/payload/scripts/changelog_manager.py +726 -0
- package/payload/scripts/version_manager.py +617 -0
- package/payload/version.yml.template +48 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
- package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
- package/src/cli/args.js +95 -0
- package/src/cli/help.js +27 -0
- package/src/commands/full.js +53 -0
- package/src/commands/interactive.js +276 -0
- package/src/commands/revert.js +60 -0
- package/src/commands/version.js +31 -0
- package/src/commands/workflows.js +49 -0
- package/src/context.js +26 -0
- package/src/core/assets.js +47 -0
- package/src/core/branches.js +60 -0
- package/src/core/branding.js +15 -0
- package/src/core/breaking-check.js +65 -0
- package/src/core/breaking.js +26 -0
- package/src/core/copy/coderabbit.js +26 -0
- package/src/core/copy/gitignore.js +55 -0
- package/src/core/copy/readme.js +30 -0
- package/src/core/copy/simple.js +23 -0
- package/src/core/copy/workflows.js +233 -0
- package/src/core/detect-fs.js +106 -0
- package/src/core/detect.js +62 -0
- package/src/core/fsutil.js +46 -0
- package/src/core/options-ask.js +99 -0
- package/src/core/paths-resolve.js +261 -0
- package/src/core/paths.js +16 -0
- package/src/core/version-yml.js +190 -0
- package/src/core/wizard-env.js +101 -0
- package/src/core/wizard-labels.js +107 -0
- package/src/index.js +162 -0
- package/src/ui/ansi.js +26 -0
- package/src/ui/banner.js +29 -0
- package/src/ui/env-plan.js +207 -0
- package/src/ui/prompts.js +99 -0
- package/src/ui/readline-engine.js +257 -0
- package/src/ui/status-cards.js +56 -0
- package/src/ui/summary.js +127 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# Spring Boot 무중단 배포 (Traefik Blue-Green, SSH + Docker)
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# deploy 브랜치 푸시 시 Docker 이미지를 빌드하고
|
|
6
|
+
# Traefik 라벨 기반 Blue-Green 토글로 서버(SSH 접속 가능한 호스트)에 무중단 배포합니다.
|
|
7
|
+
#
|
|
8
|
+
# 본 워크플로우는 무중단 배포 옵션입니다.
|
|
9
|
+
# 기본 배포는 PROJECT-SPRING-SIMPLE-CICD.yaml 입니다.
|
|
10
|
+
# 전환하려면 아래 push.branches 주석 해제 + SIMPLE-CICD 의 push 트리거 주석 처리.
|
|
11
|
+
#
|
|
12
|
+
# ===================================================================
|
|
13
|
+
# 🔑 필수 GitHub Secrets
|
|
14
|
+
# ===================================================================
|
|
15
|
+
# APPLICATION_PROD_YML (선택): application-prod.yml 내용
|
|
16
|
+
# DOCKERHUB_USERNAME: DockerHub 사용자명
|
|
17
|
+
# DOCKERHUB_TOKEN: DockerHub 액세스 토큰
|
|
18
|
+
# SERVER_HOST: 배포 서버 주소
|
|
19
|
+
# SERVER_USER: SSH 사용자명
|
|
20
|
+
# SERVER_PASSWORD: SSH 비밀번호 (SSH_AUTH_METHOD=password일 때)
|
|
21
|
+
# SSH_KEY: SSH 개인키 .pem 내용 (SSH_AUTH_METHOD=key일 때, AWS EC2 등)
|
|
22
|
+
# ===================================================================
|
|
23
|
+
#
|
|
24
|
+
# 🔧 환경변수 설정 (env 섹션에서 설정)
|
|
25
|
+
# ===================================================================
|
|
26
|
+
#
|
|
27
|
+
# 🌐 포트 및 컨테이너:
|
|
28
|
+
# PROJECT_NAME: 프로젝트명 (컨테이너/이미지/Traefik router 이름)
|
|
29
|
+
# SERVICE_DOMAIN: 외부 진입 도메인
|
|
30
|
+
# CONTAINER_INTERNAL_PORT: Spring Boot 내부 포트 (기본: 8080)
|
|
31
|
+
#
|
|
32
|
+
# 🚦 Traefik:
|
|
33
|
+
# TRAEFIK_NETWORK: Traefik docker 네트워크 (기본: traefik-network)
|
|
34
|
+
# TRAEFIK_ENTRYPOINT: Traefik entrypoint 이름 (기본: web)
|
|
35
|
+
# TRAEFIK_INTERNAL_PORT: NAS 호스트의 Traefik 진입 포트 (기본: 8079)
|
|
36
|
+
# EXTRA_NETWORKS: 추가 docker 네트워크 (공백 구분, 예: selenium-chrome-network)
|
|
37
|
+
#
|
|
38
|
+
# 📦 볼륨/스토리지:
|
|
39
|
+
# ENABLE_VOLUME_MOUNT: 볼륨 마운트 여부 (true/false)
|
|
40
|
+
# VOLUME_HOST_PATH: 호스트 볼륨 경로
|
|
41
|
+
# VOLUME_CONTAINER_PATH: 컨테이너 내부 마운트 경로
|
|
42
|
+
#
|
|
43
|
+
# 🔌 SSH 연결:
|
|
44
|
+
# SSH_PORT: SSH 포트 (기본: 2022)
|
|
45
|
+
# SSH_COMMAND_TIMEOUT: 명령 타임아웃 (기본: 600s)
|
|
46
|
+
# SSH_CONNECTION_TIMEOUT: 연결 타임아웃 (기본: 60s)
|
|
47
|
+
#
|
|
48
|
+
# ⏱️ 헬스체크:
|
|
49
|
+
# HEALTHCHECK_PATH: Traefik 통한 GET 호출 path (기본: /)
|
|
50
|
+
# HEALTHCHECK_ACCEPT_CODES: 통과 인정 HTTP 코드 (| 구분, 기본: 200|301|302|308)
|
|
51
|
+
# HEALTHCHECK_MAX_RETRIES: 최대 시도 횟수 (기본: 36)
|
|
52
|
+
# HEALTHCHECK_RETRY_INTERVAL: 재시도 간격 초 (기본: 5)
|
|
53
|
+
# IN_FLIGHT_WAIT: old 컨테이너 제거 전 대기 초 (기본: 10)
|
|
54
|
+
#
|
|
55
|
+
# 💡 사전 조건:
|
|
56
|
+
# - 배포 서버에 Traefik 컨테이너가 traefik-network 에 띄워져 있어야 함
|
|
57
|
+
# - 역방향 프록시: ${SERVICE_DOMAIN}:443 → localhost:${TRAEFIK_INTERNAL_PORT}
|
|
58
|
+
# (예: Synology DSM 역방향 프록시, Nginx, Caddy 등)
|
|
59
|
+
# ===================================================================
|
|
60
|
+
|
|
61
|
+
name: PROJECT-SPRING-NONSTOP-TRAEFIK-CICD
|
|
62
|
+
|
|
63
|
+
# ===================================================================
|
|
64
|
+
# 트리거 설정
|
|
65
|
+
# ===================================================================
|
|
66
|
+
# deploy 브랜치 push 자동 트리거는 주석 처리되어 있습니다.
|
|
67
|
+
# 본 워크플로우로 전환하려면 아래 push.branches 주석을 해제하세요.
|
|
68
|
+
on:
|
|
69
|
+
# push:
|
|
70
|
+
# branches:
|
|
71
|
+
# - {{MAIN_BRANCH}}
|
|
72
|
+
workflow_dispatch:
|
|
73
|
+
|
|
74
|
+
# ===================================================================
|
|
75
|
+
# 환경 변수 설정
|
|
76
|
+
# ===================================================================
|
|
77
|
+
env:
|
|
78
|
+
# 🔧 프로젝트 설정 - 실제 프로젝트명으로 변경하세요
|
|
79
|
+
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
80
|
+
SERVICE_DOMAIN: "__SERVICE_DOMAIN__" # @wizard ask:example.com
|
|
81
|
+
|
|
82
|
+
# ☁️ Spring Boot 설정
|
|
83
|
+
SPRING_PROFILE: "prod"
|
|
84
|
+
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:21
|
|
85
|
+
GRADLE_OPTS: "-Dspring.profiles.active=prod"
|
|
86
|
+
|
|
87
|
+
# 📁 경로 설정
|
|
88
|
+
APPLICATION_YML_DIR: "__APPLICATION_YML_DIR__" # @wizard auto:spring-app-yml-dir
|
|
89
|
+
DOCKERFILE_PATH: "./Dockerfile"
|
|
90
|
+
|
|
91
|
+
# 🌐 포트 및 컨테이너 설정
|
|
92
|
+
CONTAINER_INTERNAL_PORT: "8080"
|
|
93
|
+
|
|
94
|
+
# 🚦 Traefik 설정 (서버의 Traefik 환경 구축 후 수정 금지)
|
|
95
|
+
TRAEFIK_NETWORK: "traefik-network"
|
|
96
|
+
TRAEFIK_ENTRYPOINT: "web"
|
|
97
|
+
TRAEFIK_INTERNAL_PORT: "8079"
|
|
98
|
+
EXTRA_NETWORKS: ""
|
|
99
|
+
|
|
100
|
+
# 📦 볼륨 마운트 설정
|
|
101
|
+
ENABLE_VOLUME_MOUNT: "false"
|
|
102
|
+
VOLUME_HOST_PATH: "/volume1/projects/__PROJECT_NAME__" # @wizard ask:/volume1/projects/__PROJECT_NAME__
|
|
103
|
+
VOLUME_CONTAINER_PATH: "/mnt/__PROJECT_NAME__" # @wizard ask:/mnt/__PROJECT_NAME__
|
|
104
|
+
|
|
105
|
+
# 🔐 SSH 인증 방식: "password"(Synology·일반 서버) | "key"(AWS EC2·.pem)
|
|
106
|
+
# password → SERVER_PASSWORD secret 사용
|
|
107
|
+
# key → SSH_KEY secret 사용 (.pem 파일 전체 내용을 secret 값으로)
|
|
108
|
+
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
109
|
+
|
|
110
|
+
# 🔌 SSH 연결 설정
|
|
111
|
+
SSH_PORT: "2022"
|
|
112
|
+
SSH_COMMAND_TIMEOUT: "600s"
|
|
113
|
+
SSH_CONNECTION_TIMEOUT: "60s"
|
|
114
|
+
|
|
115
|
+
# ⏱️ 헬스체크 설정
|
|
116
|
+
HEALTHCHECK_PATH: "/"
|
|
117
|
+
HEALTHCHECK_ACCEPT_CODES: "200|301|302|308"
|
|
118
|
+
HEALTHCHECK_MAX_RETRIES: "36"
|
|
119
|
+
HEALTHCHECK_RETRY_INTERVAL: "5"
|
|
120
|
+
IN_FLIGHT_WAIT: "10"
|
|
121
|
+
|
|
122
|
+
jobs:
|
|
123
|
+
# ===================================================================
|
|
124
|
+
# 빌드 작업
|
|
125
|
+
# ===================================================================
|
|
126
|
+
build:
|
|
127
|
+
name: Spring Boot 애플리케이션 빌드
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
|
|
130
|
+
steps:
|
|
131
|
+
- name: 코드 체크아웃
|
|
132
|
+
uses: actions/checkout@v5
|
|
133
|
+
|
|
134
|
+
- name: Java 설정
|
|
135
|
+
uses: actions/setup-java@v4
|
|
136
|
+
with:
|
|
137
|
+
java-version: ${{ env.JAVA_VERSION }}
|
|
138
|
+
distribution: 'temurin'
|
|
139
|
+
cache: 'gradle'
|
|
140
|
+
|
|
141
|
+
- name: Gradle Wrapper 실행권한 부여
|
|
142
|
+
run: chmod +x gradlew
|
|
143
|
+
|
|
144
|
+
- name: application-prod.yml 생성
|
|
145
|
+
run: |
|
|
146
|
+
mkdir -p ${{ env.APPLICATION_YML_DIR }}
|
|
147
|
+
cat << 'EOF' > ./${{ env.APPLICATION_YML_DIR }}/application-prod.yml
|
|
148
|
+
${{ secrets.APPLICATION_PROD_YML }}
|
|
149
|
+
EOF
|
|
150
|
+
echo "✅ application-prod.yml 파일 생성 완료 (${{ env.APPLICATION_YML_DIR }})"
|
|
151
|
+
|
|
152
|
+
- name: Build with Gradle
|
|
153
|
+
run: ./gradlew clean build -x test ${{ env.GRADLE_OPTS }}
|
|
154
|
+
|
|
155
|
+
- name: Docker 빌드환경 설정
|
|
156
|
+
uses: docker/setup-buildx-action@v3
|
|
157
|
+
|
|
158
|
+
- name: DockerHub 로그인
|
|
159
|
+
uses: docker/login-action@v3
|
|
160
|
+
with:
|
|
161
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
162
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
163
|
+
|
|
164
|
+
- name: Cache Docker layers
|
|
165
|
+
uses: actions/cache@v4
|
|
166
|
+
with:
|
|
167
|
+
path: /tmp/.buildx-cache
|
|
168
|
+
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }}
|
|
169
|
+
restore-keys: |
|
|
170
|
+
${{ runner.os }}-buildx-
|
|
171
|
+
|
|
172
|
+
- name: Docker 이미지 빌드 및 푸시
|
|
173
|
+
uses: docker/build-push-action@v5
|
|
174
|
+
with:
|
|
175
|
+
context: .
|
|
176
|
+
file: ${{ env.DOCKERFILE_PATH }}
|
|
177
|
+
push: true
|
|
178
|
+
tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.ref_name }}
|
|
179
|
+
cache-from: type=local,src=/tmp/.buildx-cache
|
|
180
|
+
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
|
181
|
+
|
|
182
|
+
- name: Move Docker cache
|
|
183
|
+
run: |
|
|
184
|
+
rm -rf /tmp/.buildx-cache
|
|
185
|
+
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
186
|
+
|
|
187
|
+
# ===================================================================
|
|
188
|
+
# 배포 작업 (Blue-Green 무중단)
|
|
189
|
+
# ===================================================================
|
|
190
|
+
deploy:
|
|
191
|
+
name: Blue-Green 무중단 배포
|
|
192
|
+
needs: build
|
|
193
|
+
runs-on: ubuntu-latest
|
|
194
|
+
|
|
195
|
+
steps:
|
|
196
|
+
- name: Deploy
|
|
197
|
+
uses: appleboy/ssh-action@v1.0.3
|
|
198
|
+
env:
|
|
199
|
+
PROJECT_NAME: ${{ env.PROJECT_NAME }}
|
|
200
|
+
SERVICE_DOMAIN: ${{ env.SERVICE_DOMAIN }}
|
|
201
|
+
CONTAINER_INTERNAL_PORT: ${{ env.CONTAINER_INTERNAL_PORT }}
|
|
202
|
+
SPRING_PROFILE: ${{ env.SPRING_PROFILE }}
|
|
203
|
+
ENABLE_VOLUME_MOUNT: ${{ env.ENABLE_VOLUME_MOUNT }}
|
|
204
|
+
VOLUME_HOST_PATH: ${{ env.VOLUME_HOST_PATH }}
|
|
205
|
+
VOLUME_CONTAINER_PATH: ${{ env.VOLUME_CONTAINER_PATH }}
|
|
206
|
+
EXTRA_NETWORKS: ${{ env.EXTRA_NETWORKS }}
|
|
207
|
+
TRAEFIK_NETWORK: ${{ env.TRAEFIK_NETWORK }}
|
|
208
|
+
TRAEFIK_ENTRYPOINT: ${{ env.TRAEFIK_ENTRYPOINT }}
|
|
209
|
+
TRAEFIK_INTERNAL_PORT: ${{ env.TRAEFIK_INTERNAL_PORT }}
|
|
210
|
+
HEALTHCHECK_PATH: ${{ env.HEALTHCHECK_PATH }}
|
|
211
|
+
HEALTHCHECK_ACCEPT_CODES: ${{ env.HEALTHCHECK_ACCEPT_CODES }}
|
|
212
|
+
HEALTHCHECK_MAX_RETRIES: ${{ env.HEALTHCHECK_MAX_RETRIES }}
|
|
213
|
+
HEALTHCHECK_RETRY_INTERVAL: ${{ env.HEALTHCHECK_RETRY_INTERVAL }}
|
|
214
|
+
IN_FLIGHT_WAIT: ${{ env.IN_FLIGHT_WAIT }}
|
|
215
|
+
BRANCH_REF: ${{ github.ref_name }}
|
|
216
|
+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
217
|
+
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
|
|
218
|
+
SSH_AUTH_METHOD: ${{ env.SSH_AUTH_METHOD }}
|
|
219
|
+
with:
|
|
220
|
+
host: ${{ secrets.SERVER_HOST }}
|
|
221
|
+
username: ${{ secrets.SERVER_USER }}
|
|
222
|
+
password: ${{ secrets.SERVER_PASSWORD }}
|
|
223
|
+
key: ${{ secrets.SSH_KEY }}
|
|
224
|
+
port: ${{ env.SSH_PORT }}
|
|
225
|
+
command_timeout: ${{ env.SSH_COMMAND_TIMEOUT }}
|
|
226
|
+
timeout: ${{ env.SSH_CONNECTION_TIMEOUT }}
|
|
227
|
+
envs: PROJECT_NAME,SERVICE_DOMAIN,CONTAINER_INTERNAL_PORT,SPRING_PROFILE,ENABLE_VOLUME_MOUNT,VOLUME_HOST_PATH,VOLUME_CONTAINER_PATH,EXTRA_NETWORKS,TRAEFIK_NETWORK,TRAEFIK_ENTRYPOINT,TRAEFIK_INTERNAL_PORT,HEALTHCHECK_PATH,HEALTHCHECK_ACCEPT_CODES,HEALTHCHECK_MAX_RETRIES,HEALTHCHECK_RETRY_INTERVAL,IN_FLIGHT_WAIT,BRANCH_REF,DOCKERHUB_USERNAME,SERVER_PASSWORD,SSH_AUTH_METHOD
|
|
228
|
+
script: |
|
|
229
|
+
set -e
|
|
230
|
+
export PATH=$PATH:/usr/local/bin
|
|
231
|
+
export PW="${SERVER_PASSWORD}"
|
|
232
|
+
|
|
233
|
+
# 🔐 SSH 인증 방식에 따른 sudo 추상화
|
|
234
|
+
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
235
|
+
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
236
|
+
SUDO() { sudo "$@"; }
|
|
237
|
+
else
|
|
238
|
+
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
239
|
+
fi
|
|
240
|
+
echo "🔐 SSH 인증 방식: ${SSH_AUTH_METHOD}"
|
|
241
|
+
|
|
242
|
+
IMAGE="${DOCKERHUB_USERNAME}/${PROJECT_NAME}:${BRANCH_REF}"
|
|
243
|
+
ROUTER_NAME="${PROJECT_NAME}"
|
|
244
|
+
|
|
245
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
246
|
+
echo "🚀 Blue-Green 무중단 배포 시작"
|
|
247
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
248
|
+
echo "📦 프로젝트: ${PROJECT_NAME}"
|
|
249
|
+
echo "🌐 도메인: ${SERVICE_DOMAIN}"
|
|
250
|
+
echo "🖼️ 이미지: ${IMAGE}"
|
|
251
|
+
echo "🔧 Traefik 진입: localhost:${TRAEFIK_INTERNAL_PORT} (entrypoint=${TRAEFIK_ENTRYPOINT})"
|
|
252
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
253
|
+
|
|
254
|
+
# ============================================================
|
|
255
|
+
# 1. 최신 이미지 Pull
|
|
256
|
+
# ============================================================
|
|
257
|
+
echo "📥 이미지 Pull: ${IMAGE}"
|
|
258
|
+
SUDO docker pull "${IMAGE}"
|
|
259
|
+
|
|
260
|
+
# ============================================================
|
|
261
|
+
# 2. 현재 active 컨테이너 색 판별 (Traefik 라벨 부착 여부 기준)
|
|
262
|
+
# ============================================================
|
|
263
|
+
ACTIVE=$(SUDO docker ps \
|
|
264
|
+
--filter "label=traefik.http.routers.${ROUTER_NAME}.rule" \
|
|
265
|
+
--filter "name=${PROJECT_NAME}-" \
|
|
266
|
+
--format "{{.Names}}" | head -1 || echo "")
|
|
267
|
+
|
|
268
|
+
if [ "$ACTIVE" = "${PROJECT_NAME}-blue" ]; then
|
|
269
|
+
NEW_COLOR="green"
|
|
270
|
+
OLD_COLOR="blue"
|
|
271
|
+
elif [ "$ACTIVE" = "${PROJECT_NAME}-green" ]; then
|
|
272
|
+
NEW_COLOR="blue"
|
|
273
|
+
OLD_COLOR="green"
|
|
274
|
+
else
|
|
275
|
+
# 최초 배포 또는 active 없음 → blue 부터 시작
|
|
276
|
+
NEW_COLOR="blue"
|
|
277
|
+
OLD_COLOR="green"
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
NEW_NAME="${PROJECT_NAME}-${NEW_COLOR}"
|
|
281
|
+
OLD_NAME="${PROJECT_NAME}-${OLD_COLOR}"
|
|
282
|
+
|
|
283
|
+
echo "🎨 active=${ACTIVE:-none} → new=${NEW_NAME}, old=${OLD_NAME}"
|
|
284
|
+
|
|
285
|
+
# ============================================================
|
|
286
|
+
# 3. 동일 슬롯(idle 쪽) 잔존 컨테이너 정리
|
|
287
|
+
# ============================================================
|
|
288
|
+
echo "🗑️ idle 슬롯 정리: ${NEW_NAME}"
|
|
289
|
+
SUDO docker rm -f "${NEW_NAME}" 2>/dev/null || true
|
|
290
|
+
|
|
291
|
+
# ============================================================
|
|
292
|
+
# 4. 볼륨 마운트 옵션 구성
|
|
293
|
+
# ============================================================
|
|
294
|
+
VOLUME_MOUNT_OPTION="-v /etc/localtime:/etc/localtime:ro"
|
|
295
|
+
if [ "${ENABLE_VOLUME_MOUNT}" = "true" ]; then
|
|
296
|
+
if [ ! -d "${VOLUME_HOST_PATH}" ]; then
|
|
297
|
+
echo "📁 볼륨 마운트 디렉토리 생성: ${VOLUME_HOST_PATH}"
|
|
298
|
+
SUDO mkdir -p "${VOLUME_HOST_PATH}"
|
|
299
|
+
fi
|
|
300
|
+
VOLUME_MOUNT_OPTION="${VOLUME_MOUNT_OPTION} -v ${VOLUME_HOST_PATH}:${VOLUME_CONTAINER_PATH}"
|
|
301
|
+
echo "📦 볼륨 마운트 활성화: ${VOLUME_HOST_PATH} → ${VOLUME_CONTAINER_PATH}"
|
|
302
|
+
else
|
|
303
|
+
echo "ℹ️ 볼륨 마운트 비활성화됨"
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
# ============================================================
|
|
307
|
+
# 5. 새 컨테이너 기동 (Traefik 라벨 포함)
|
|
308
|
+
# ============================================================
|
|
309
|
+
echo "🐳 새 컨테이너 기동: ${NEW_NAME}"
|
|
310
|
+
SUDO docker run -d \
|
|
311
|
+
--name "${NEW_NAME}" \
|
|
312
|
+
--network "${TRAEFIK_NETWORK}" \
|
|
313
|
+
--label "traefik.enable=true" \
|
|
314
|
+
--label "traefik.http.routers.${ROUTER_NAME}.rule=Host(\`${SERVICE_DOMAIN}\`)" \
|
|
315
|
+
--label "traefik.http.routers.${ROUTER_NAME}.entrypoints=${TRAEFIK_ENTRYPOINT}" \
|
|
316
|
+
--label "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=${CONTAINER_INTERNAL_PORT}" \
|
|
317
|
+
-e TZ=Asia/Seoul \
|
|
318
|
+
-e "SPRING_PROFILES_ACTIVE=${SPRING_PROFILE}" \
|
|
319
|
+
${VOLUME_MOUNT_OPTION} \
|
|
320
|
+
"${IMAGE}"
|
|
321
|
+
|
|
322
|
+
# ============================================================
|
|
323
|
+
# 6. 추가 네트워크 연결 (EXTRA_NETWORKS, 공백 구분)
|
|
324
|
+
# ============================================================
|
|
325
|
+
if [ -n "${EXTRA_NETWORKS}" ]; then
|
|
326
|
+
for net in ${EXTRA_NETWORKS}; do
|
|
327
|
+
echo "🔗 추가 네트워크 연결: ${net}"
|
|
328
|
+
SUDO docker network connect "${net}" "${NEW_NAME}" 2>/dev/null || true
|
|
329
|
+
done
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
# ============================================================
|
|
333
|
+
# 7. Traefik 라벨 디버그 출력 (escape 검증용)
|
|
334
|
+
# ============================================================
|
|
335
|
+
echo "🔍 Traefik 라벨 확인:"
|
|
336
|
+
SUDO docker inspect "${NEW_NAME}" --format '{{range $k, $v := .Config.Labels}} {{$k}} = {{$v}}{{"\n"}}{{end}}' | grep traefik || true
|
|
337
|
+
|
|
338
|
+
# ============================================================
|
|
339
|
+
# 8. 헬스체크 — Traefik 통한 라우팅 확인
|
|
340
|
+
# ============================================================
|
|
341
|
+
MAX_WAIT=$((HEALTHCHECK_MAX_RETRIES * HEALTHCHECK_RETRY_INTERVAL))
|
|
342
|
+
echo "⏳ 헬스체크 시작 (최대 ${MAX_WAIT}초, ${HEALTHCHECK_MAX_RETRIES}회 × ${HEALTHCHECK_RETRY_INTERVAL}초)"
|
|
343
|
+
|
|
344
|
+
RETRY_COUNT=0
|
|
345
|
+
HEALTHCHECK_SUCCESS=false
|
|
346
|
+
|
|
347
|
+
while [ $RETRY_COUNT -lt $HEALTHCHECK_MAX_RETRIES ]; do
|
|
348
|
+
sleep ${HEALTHCHECK_RETRY_INTERVAL}
|
|
349
|
+
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
350
|
+
|
|
351
|
+
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${NEW_NAME}" 2>/dev/null || echo "not_found")
|
|
352
|
+
|
|
353
|
+
if [ "$STATUS" = "exited" ]; then
|
|
354
|
+
echo "❌ 컨테이너 비정상 종료 (exited). 로그 200줄:"
|
|
355
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
356
|
+
SUDO docker logs --tail 200 "${NEW_NAME}"
|
|
357
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
358
|
+
echo "ℹ️ 컨테이너 ${NEW_NAME} 유지 (자동 삭제 안 함) — 수동 검증/디버깅"
|
|
359
|
+
echo "ℹ️ old=${OLD_NAME} 존재 시 그대로 유지 → 사실상 자동 롤백"
|
|
360
|
+
exit 1
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
if [ "$STATUS" = "running" ]; then
|
|
364
|
+
HTTP_CODE=$(curl -sm 5 -o /dev/null -w "%{http_code}" \
|
|
365
|
+
-H "Host: ${SERVICE_DOMAIN}" \
|
|
366
|
+
"http://localhost:${TRAEFIK_INTERNAL_PORT}${HEALTHCHECK_PATH}" 2>/dev/null || echo "000")
|
|
367
|
+
|
|
368
|
+
if echo "$HTTP_CODE" | grep -Eq "^(${HEALTHCHECK_ACCEPT_CODES})$"; then
|
|
369
|
+
echo "✅ 헬스체크 통과 (${RETRY_COUNT}/${HEALTHCHECK_MAX_RETRIES}) HTTP=${HTTP_CODE}"
|
|
370
|
+
HEALTHCHECK_SUCCESS=true
|
|
371
|
+
break
|
|
372
|
+
fi
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
echo "⏳ 대기 (${RETRY_COUNT}/${HEALTHCHECK_MAX_RETRIES}) status=${STATUS} http=${HTTP_CODE:-?}"
|
|
376
|
+
done
|
|
377
|
+
|
|
378
|
+
if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
|
|
379
|
+
echo "❌ 헬스체크 타임아웃 (${MAX_WAIT}초)"
|
|
380
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
381
|
+
echo "🔍 ${NEW_NAME} 최근 200줄 로그:"
|
|
382
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
383
|
+
SUDO docker logs --tail 200 "${NEW_NAME}"
|
|
384
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
385
|
+
echo "ℹ️ 컨테이너 ${NEW_NAME} 유지 (자동 삭제 안 함) — 수동 검증/디버깅"
|
|
386
|
+
echo "ℹ️ old=${OLD_NAME} 존재 시 그대로 유지 → 사실상 자동 롤백"
|
|
387
|
+
exit 1
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# ============================================================
|
|
391
|
+
# 9. in-flight 처리 + old 컨테이너 자동 제거
|
|
392
|
+
# ============================================================
|
|
393
|
+
if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${OLD_NAME}$"; then
|
|
394
|
+
echo "⏱️ in-flight 요청 처리 대기 (${IN_FLIGHT_WAIT}초)"
|
|
395
|
+
sleep ${IN_FLIGHT_WAIT}
|
|
396
|
+
echo "🗑️ old=${OLD_NAME} 제거"
|
|
397
|
+
SUDO docker rm -f "${OLD_NAME}"
|
|
398
|
+
else
|
|
399
|
+
echo "ℹ️ old=${OLD_NAME} 없음 — 정리 생략"
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# ============================================================
|
|
403
|
+
# 10. dangling 이미지 정리
|
|
404
|
+
# ============================================================
|
|
405
|
+
echo "🧹 dangling(<none>) 이미지 정리..."
|
|
406
|
+
SUDO docker image prune -af
|
|
407
|
+
|
|
408
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
409
|
+
echo "✅ 무중단 배포 완료. active=${NEW_NAME}"
|
|
410
|
+
echo "🌐 도메인: https://${SERVICE_DOMAIN}"
|
|
411
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
412
|
+
|
|
413
|
+
# ===================================================================
|
|
414
|
+
# 📝 사용 방법
|
|
415
|
+
# ===================================================================
|
|
416
|
+
#
|
|
417
|
+
# 1) GitHub Secrets 등록:
|
|
418
|
+
# APPLICATION_PROD_YML, DOCKERHUB_USERNAME, DOCKERHUB_TOKEN,
|
|
419
|
+
# SERVER_HOST, SERVER_USER, SERVER_PASSWORD
|
|
420
|
+
#
|
|
421
|
+
# 2) 본 워크플로우의 env 블록에서 프로젝트별 설정 수정:
|
|
422
|
+
# - PROJECT_NAME, SERVICE_DOMAIN
|
|
423
|
+
# - APPLICATION_YML_DIR, CONTAINER_INTERNAL_PORT
|
|
424
|
+
# - ENABLE_VOLUME_MOUNT, VOLUME_HOST_PATH, VOLUME_CONTAINER_PATH
|
|
425
|
+
# - EXTRA_NETWORKS (예: selenium-chrome-network)
|
|
426
|
+
# - HEALTHCHECK_PATH, HEALTHCHECK_ACCEPT_CODES
|
|
427
|
+
#
|
|
428
|
+
# 3) 서버 사전 준비:
|
|
429
|
+
# - Traefik 컨테이너가 traefik-network 에 띄워져 있어야 함
|
|
430
|
+
# - 역방향 프록시: ${SERVICE_DOMAIN}:443 → localhost:${TRAEFIK_INTERNAL_PORT}
|
|
431
|
+
# (예: Synology DSM 역방향 프록시, Nginx, Caddy 등)
|
|
432
|
+
#
|
|
433
|
+
# 4) 본 워크플로우로 전환:
|
|
434
|
+
# - 본 파일의 push.branches 주석 해제
|
|
435
|
+
# - PROJECT-SPRING-SIMPLE-CICD.yaml 의 push 트리거 주석 처리
|
|
436
|
+
#
|
|
437
|
+
# ===================================================================
|