Skip to content

Merge pull request #75 from Team-StackUp/feature/feedback-scoring-hybrid #47

Merge pull request #75 from Team-StackUp/feature/feedback-scoring-hybrid

Merge pull request #75 from Team-StackUp/feature/feedback-scoring-hybrid #47

Workflow file for this run

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