forked from Team-StackUp/stackup
-
Notifications
You must be signed in to change notification settings - Fork 0
165 lines (150 loc) · 6.18 KB
/
Copy pathdeploy-app.yml
File metadata and controls
165 lines (150 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
name: STACK-UP deploy ana-server
# dev 브랜치 업데이트 시 AI / RealTime / Backend(Core) 를 빌드해 self-hosted runner 에서 재시작.
# 상우 원격컴 전용 — 클라우드 이관 시 워크플로우 재설계 필요.
on:
push:
branches: [dev]
paths:
- "ai/**"
- "realtime/**"
- "backend/**"
- "frontend/**"
- "docker-compose.yml"
- "infra/**"
- ".github/workflows/deploy-app.yml"
workflow_dispatch:
concurrency:
group: deploy-app-${{ github.ref }}
cancel-in-progress: false
jobs:
deploy:
runs-on: self-hosted
timeout-minutes: 30
env:
DEPLOY_DIR: /home/stackup/stackup
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Sync repo to deploy dir (preserve .env and data)
run: |
mkdir -p "$DEPLOY_DIR"
rsync -a --delete \
--exclude='.git' \
--exclude='.env' \
--exclude='var/' \
--exclude='**/__pycache__' \
--exclude='**/.venv' \
--exclude='**/node_modules' \
"$GITHUB_WORKSPACE/" "$DEPLOY_DIR/"
# 5/22 시점 임시 만든 docker-compose.override.yml 은 backend 가 정식 compose 로 들어와서 불필요.
# rsync --delete 가 자동 제거하지만, 안전하게 명시적 제거.
rm -f "$DEPLOY_DIR/docker-compose.override.yml"
- name: Ensure .env exists and inject Secrets
env:
# GitHub Repository Secrets. 비어있으면 application-local.yml / docker-compose.yml 의 dev default 가 적용됨.
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
CORE_INTERNAL_API_KEY: ${{ secrets.CORE_INTERNAL_API_KEY }}
GITHUB_OAUTH_CLIENT_ID: ${{ secrets.GH_OAUTH_CLIENT_ID }}
GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.GH_OAUTH_CLIENT_SECRET }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
run: |
cd "$DEPLOY_DIR"
if [ ! -f .env ]; then
cp .env.example .env
fi
chmod 600 .env
# .env.example 에 추가됐는데 .env 에 없는 키는 default 와 함께 append.
# 이 단계가 없으면 docker-compose 의 ${VAR:-} 가 빈 문자열을 주입해 Spring 의
# @NotBlank 검증을 깨뜨림 (예: JWT_SECRET, ENCRYPTION_KEY 누락 → backend exit 1).
appended=$(mktemp)
awk -F= '
NR==FNR && /^[A-Z_]+=/ { existing[$1] = 1; next }
/^[A-Z_]+=/ && !($1 in existing) { print }
' .env .env.example > "$appended"
if [ -s "$appended" ]; then
echo " sync new keys from .env.example:"
cut -d= -f1 "$appended" | sed 's/^/ + /'
echo "" >> .env
echo "# ---- synced from .env.example by deploy-app.yml ----" >> .env
cat "$appended" >> .env
chmod 600 .env
fi
rm -f "$appended"
# 같은 키를 .env 에 upsert. 값이 비어있으면 skip (기존 값 유지).
upsert_env() {
local key="$1"
local value="$2"
if [ -z "$value" ]; then
echo " skip $key (secret 미설정)"
return 0
fi
local tmp
tmp=$(mktemp)
awk -v k="$key" -v v="$value" '
BEGIN { written = 0 }
$0 ~ ("^" k "=") { print k "=" v; written = 1; next }
{ print }
END { if (!written) print k "=" v }
' .env > "$tmp"
mv "$tmp" .env
chmod 600 .env
echo " upserted $key"
}
upsert_env "LLM_API_KEY" "$LLM_API_KEY"
upsert_env "JWT_SECRET" "$JWT_SECRET"
upsert_env "ENCRYPTION_KEY" "$ENCRYPTION_KEY"
upsert_env "CORE_INTERNAL_API_KEY" "$CORE_INTERNAL_API_KEY"
upsert_env "GITHUB_OAUTH_CLIENT_ID" "$GITHUB_OAUTH_CLIENT_ID"
upsert_env "GITHUB_OAUTH_CLIENT_SECRET" "$GITHUB_OAUTH_CLIENT_SECRET"
upsert_env "GITHUB_OAUTH_REDIRECT_URI" "https://stack-up.shop/auth/callback"
upsert_env "OPENAI_API_KEY" "$OPENAI_API_KEY"
upsert_env "DEEPGRAM_API_KEY" "$DEEPGRAM_API_KEY"
# STT_PROVIDER 자동 선택. auto 로 두면 AI factory 가 보유 키 기준으로 선택 (deepgram > openai_whisper > mock).
# 키가 1개라도 있으면 auto 로 설정해두면 충분 — 명시적 전환 불필요.
if [ -n "$DEEPGRAM_API_KEY" ] || [ -n "$OPENAI_API_KEY" ]; then
upsert_env "STT_PROVIDER" "auto"
fi
- name: Build and restart app services
run: |
cd "$DEPLOY_DIR"
docker compose up -d --build ai realtime backend
- name: Build and deploy frontend
env:
VITE_API_BASE_URL: https://stack-up.shop
VITE_REALTIME_BASE_URL: https://stack-up.shop
VITE_SSE_BASE_URL: https://stack-up.shop
run: |
cd "$GITHUB_WORKSPACE/frontend"
npm ci
npm run build
mkdir -p /var/www/stackup/frontend/dist
rsync -a --delete "$GITHUB_WORKSPACE/frontend/dist/" /var/www/stackup/frontend/dist/
- name: Wait for healthy
run: |
cd "$DEPLOY_DIR"
set +e
rc=0
for svc in ai realtime backend; do
container="stackup-$svc"
ok=0
# backend 는 Spring 부팅이 길어 36*5=180s 까지 대기.
for i in $(seq 1 36); do
h=$(docker inspect --format '{{ .State.Health.Status }}' "$container" 2>/dev/null || echo "missing")
echo " [$i] $container: $h"
if [ "$h" = "healthy" ]; then ok=1; break; fi
sleep 5
done
if [ "$ok" -ne 1 ]; then
echo "::error::$container did not become healthy"
docker compose logs --tail=100 "$svc" || true
rc=1
fi
done
docker compose ps
exit $rc
- name: Prune dangling images (best-effort)
if: always()
run: docker image prune -f >/dev/null 2>&1 || true