name: Deploy API Server on: push: branches: [main] workflow_dispatch: env: DATABASE_URL: ${{ secrets.DATABASE_URL }} WORKDIR: /tmp/api-server ARTIFACT_DIR: /tmp/api-server-artifacts jobs: # ═══════════════════════════════════════════════════════════ # Job 1: Build # ═══════════════════════════════════════════════════════════ build: runs-on: prod steps: - name: Checkout run: | if [ -d $WORKDIR ]; then cd $WORKDIR git fetch origin && git reset --hard origin/main else git clone http://10.2.0.7:3000/wangdl/api-server.git $WORKDIR fi - name: Install dependencies run: cd $WORKDIR && npm ci timeout-minutes: 5 - name: Prisma generate & validate run: | cd $WORKDIR npx prisma generate npx prisma validate - name: Build run: cd $WORKDIR && npm run build - name: Save build artifacts run: | rm -rf $ARTIFACT_DIR mkdir -p $ARTIFACT_DIR cp -a $WORKDIR/dist $ARTIFACT_DIR/dist cp -a $WORKDIR/node_modules $ARTIFACT_DIR/node_modules cp -a $WORKDIR/prisma $ARTIFACT_DIR/prisma cp $WORKDIR/package.json $ARTIFACT_DIR/package.json cp $WORKDIR/package-lock.json $ARTIFACT_DIR/package-lock.json # ═══════════════════════════════════════════════════════════ # Job 2: Deploy # ═══════════════════════════════════════════════════════════ deploy: needs: build runs-on: prod steps: - name: Restore build artifacts run: | cp -a $ARTIFACT_DIR/dist $WORKDIR/dist cp -a $ARTIFACT_DIR/node_modules $WORKDIR/node_modules cp -a $ARTIFACT_DIR/prisma $WORKDIR/prisma - name: Ensure infrastructure run: | docker start mysql redis qdrant 2>/dev/null || true sleep 2 - name: Resolve failed migrations run: | cd $WORKDIR MYSQL_CMD="docker exec mysql mysql -u ${{ secrets.DB_USER }} -p${{ secrets.DB_PASSWORD }} ${{ secrets.DB_NAME }}" FAILED=$($MYSQL_CMD -N -e "SELECT migration_name FROM _prisma_migrations WHERE logs LIKE '%failed%' LIMIT 1;" 2>/dev/null || true) if [ -n "$FAILED" ]; then echo "[deploy] Found failed migration: $FAILED, cleaning up..." $MYSQL_CMD -e "DELETE FROM _prisma_migrations WHERE migration_name = '$FAILED';" echo "[deploy] Cleaned up failed migration $FAILED" else echo "[deploy] No failed migrations found" fi - name: Apply database migrations run: | cd $WORKDIR HAS_MIGS=$(npx prisma db execute --stdin <<< "SELECT COUNT(*) as cnt FROM _prisma_migrations" 2>/dev/null | grep -o '[0-9]*' | head -1 || echo "0") if [ "$HAS_MIGS" = "0" ] || [ -z "$HAS_MIGS" ]; then echo "[deploy] Baselining existing migrations..." for m in prisma/migrations/*/; do m_name=$(basename "$m") if [ "$m_name" != "backfill_chat_scope.sql" ] && [ "$m_name" != "migration_lock.toml" ]; then DATABASE_URL="${{ secrets.DATABASE_URL }}" npx prisma migrate resolve --applied "$m_name" 2>/dev/null || true fi done echo "[deploy] Baseline complete." fi DATABASE_URL="${{ secrets.DATABASE_URL }}" npx prisma migrate deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} - name: Deploy API run: | sudo rsync -av --delete $WORKDIR/dist/ /opt/zhixi/backend/dist/ sudo rsync -av --delete $WORKDIR/node_modules/ /opt/zhixi/backend/node_modules/ sudo rsync -av $WORKDIR/prisma/ /opt/zhixi/backend/prisma/ sudo rsync -av $WORKDIR/package.json /opt/zhixi/backend/package.json cd /opt/zhixi/backend && npx prisma generate sudo systemctl reset-failed zhixi-api 2>/dev/null || true sudo systemctl restart zhixi-api sleep 3 if curl -sf http://localhost:3000/api > /dev/null 2>&1; then echo "[deploy] API health OK" else echo "[deploy] Health check failed" sudo journalctl -u zhixi-api --no-pager -n 20 exit 1 fi - name: Deploy Worker run: | cd /opt/zhixi/backend sudo cp deploy/zhixi-nest-worker.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl reset-failed zhixi-nest-worker 2>/dev/null || true sudo systemctl restart zhixi-nest-worker sleep 5 echo "[deploy] Worker: $(sudo systemctl is-active zhixi-nest-worker)" sudo journalctl -u zhixi-nest-worker --no-pager -n 10