Merge pull request #28 from Team-StackUp/feature/us-28 #4
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 }} | |
| run: | | |
| cd "$DEPLOY_DIR" | |
| if [ ! -f .env ]; then | |
| cp .env.example .env | |
| fi | |
| chmod 600 .env | |
| # 같은 키를 .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" | |
| - 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 |