Merge pull request #45 from Team-StackUp/feature/realtime-notify #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: STACK-UP deploy ana-server | |
| # dev 브랜치 업데이트 시 AI / RealTime / Backend(Core) 를 빌드해 self-hosted runner 에서 재시작. | |
| # 상우 원격컴 전용 — 클라우드 이관 시 워크플로우 재설계 필요. | |
| on: | |
| push: | |
| branches: [dev] | |
| paths: | |
| - "ai/**" | |
| - "realtime/**" | |
| - "backend/**" | |
| - "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 "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: 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 |