name: Deploy API Server on: push: branches: [main] env: DATABASE_URL: mysql://zhixi_user:hKHQ+N0wBjJAiLukFu5OMEI8@127.0.0.1:3306/zhixi_prod jobs: build-and-deploy: runs-on: prod steps: - name: Checkout latest code run: | if [ -d /tmp/api-server ]; then cd /tmp/api-server && git fetch origin && git reset --hard origin/main else git clone http://10.2.0.7:3000/wangdl/api-server.git /tmp/api-server fi - name: Install dependencies run: | cd /tmp/api-server npm ci - name: Build run: | cd /tmp/api-server npx prisma generate npm run build if [ ! -f dist/main.js ] && [ -f dist/src/main.js ]; then echo "[build] Moving dist/src/* to dist/" mv dist/src/* dist/ rm -rf dist/src fi ls -la dist/main.js - name: Ensure infrastructure is ready run: | docker start mysql redis qdrant 2>/dev/null || true sleep 2 - name: Resolve failed migrations run: | MYSQL_CMD="docker exec mysql mysql -u zhixi_user -phKHQ+N0wBjJAiLukFu5OMEI8 zhixi_prod" 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 "DROP TABLE IF EXISTS AiUsageLog;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS WaitlistEntry;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS ModelRoute;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS ProviderConfig;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS FallbackEvent;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS ViolationRecord;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS UserDevice;" 2>/dev/null || true $MYSQL_CMD -e "DROP TABLE IF EXISTS AccountDeletionRequest;" 2>/dev/null || true $MYSQL_CMD -e "ALTER TABLE UploadedFile DROP COLUMN objectKey;" 2>/dev/null || true $MYSQL_CMD -e "ALTER TABLE UploadedFile DROP COLUMN bucket;" 2>/dev/null || true $MYSQL_CMD -e "DROP INDEX UploadedFile_objectKey_idx ON UploadedFile;" 2>/dev/null || true $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: Run database migrations run: | cd /tmp/api-server npx prisma migrate deploy - name: Deploy NestJS API run: | rsync -av --delete \ /tmp/api-server/dist/ /opt/zhixi/backend/dist/ rsync -av --delete \ /tmp/api-server/node_modules/ /opt/zhixi/backend/node_modules/ rsync -av \ /tmp/api-server/prisma/ /opt/zhixi/backend/prisma/ rsync -av \ /tmp/api-server/package.json /opt/zhixi/backend/package.json - name: Generate Prisma client run: | cd /opt/zhixi/backend && npx prisma generate - name: Test-run and health check run: | # Run app directly on alternate port to avoid systemd conflict cd /opt/zhixi/backend PORT=3001 node dist/main.js > /tmp/zhixi-startup.log 2>&1 & APP_PID=$! echo "[deploy] App PID: $APP_PID (port 3001)" # Wait for app to start (up to 30s) for i in $(seq 1 30); do sleep 1 if curl -sf http://localhost:3001/api > /dev/null 2>&1; then echo "[deploy] API healthy on port 3001 after ${i}s!" break fi if ! kill -0 $APP_PID 2>/dev/null; then echo "[deploy] App crashed after ${i}s — startup log:" cat /tmp/zhixi-startup.log exit 1 fi done # Kill test instance kill $APP_PID 2>/dev/null wait $APP_PID 2>/dev/null - name: Restart API service run: | sudo systemctl restart zhixi-api sleep 5 if sudo systemctl is-active zhixi-api; then echo "[deploy] zhixi-api active OK" else echo "[deploy] zhixi-api FAILED to start" exit 1 fi - name: Deploy RAG Worker run: | set -e WORKER_DIR="/opt/zhixi/backend/rag-worker" mkdir -p "$WORKER_DIR" rsync -av --delete --exclude='.env' --exclude='__pycache__' \ /tmp/api-server/rag-worker/ "$WORKER_DIR/" sudo cp "$WORKER_DIR/zhixi-worker.service" /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl restart zhixi-worker sleep 5 sudo systemctl is-active zhixi-worker echo "[deploy] zhixi-worker active OK" - name: Health check run: | curl -sf http://localhost:3000/api && echo "[deploy] API health OK" || echo "[deploy] API health check failed (non-fatal)"