Merge pull request #29 from Team-StackUp/fix/workflow-env-sync #5
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.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" | |
| - 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 |