Revert "Merge pull request #14 from azin-tech/claude-fix/issue-13" #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: Deploy to production VM | |
| # Pushes to master → sync snake-game-production to origin/master | |
| # and restart the Python server. Idempotent; skipped when only | |
| # .github/** or markdown files change. | |
| on: | |
| push: | |
| branches: [master] | |
| paths-ignore: | |
| - '.github/**' | |
| - '**.md' | |
| concurrency: | |
| group: deploy-production | |
| cancel-in-progress: true | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Install grpcurl | |
| run: | | |
| set -euo pipefail | |
| curl -fsSL -o /tmp/grpcurl.tar.gz \ | |
| https://github.com/fullstorydev/grpcurl/releases/download/v1.9.1/grpcurl_1.9.1_linux_x86_64.tar.gz | |
| tar -xzf /tmp/grpcurl.tar.gz -C /tmp grpcurl | |
| sudo mv /tmp/grpcurl /usr/local/bin/grpcurl | |
| - name: Exchange API key for JWT | |
| env: | |
| BOXD_API_KEY: ${{ secrets.BOXD_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| jwt=$(curl -fsSL -X POST https://boxd-stg.sh/api/v1/auth/token \ | |
| -H 'Content-Type: application/json' \ | |
| -d "{\"api_key\":\"$BOXD_API_KEY\"}" | jq -r .token) | |
| [[ -n "$jwt" && "$jwt" != "null" ]] || { echo "key exchange failed"; exit 1; } | |
| echo "::add-mask::$jwt" | |
| echo "BOXD_JWT=$jwt" >> "$GITHUB_ENV" | |
| - name: Resolve production VM id | |
| run: | | |
| set -euo pipefail | |
| vm_id=$(grpcurl -plaintext -H "authorization: Bearer $BOXD_JWT" -d '{}' \ | |
| boxd-stg.sh:9443 boxd.api.v1.BoxdApi/ListVms \ | |
| | jq -r '.vms[] | select(.name=="snake-game-production") | .vmId') | |
| [[ -n "$vm_id" ]] || { echo "snake-game-production VM not found"; exit 1; } | |
| echo "PROD_VM_ID=$vm_id" >> "$GITHUB_ENV" | |
| - name: Pull latest master + restart server | |
| run: | | |
| set -euo pipefail | |
| # One-line the command so printf %q doesn't emit ANSI-C quoting | |
| # that would be destroyed by the JSON round-trip through grpcurl. | |
| # Kill the server by port (fuser), not by name (pkill -f), because | |
| # the deploy script itself contains the literal "python3 server.py" | |
| # and pkill -f would match its own bash and commit suicide. | |
| inner='cd /home/boxd/first-boot && git fetch origin && git reset --hard origin/master && (fuser -k 3000/tcp 2>/dev/null || true) && sleep 1 && (setsid nohup python3 server.py >/tmp/server.log 2>&1 </dev/null &) && sleep 2 && curl -sf -o /dev/null http://localhost:3000/ && echo "deployed $(git rev-parse --short HEAD)"' | |
| wrapped="bash -lc $(printf %q "$inner")" | |
| msg=$(jq -nc --arg vm "$PROD_VM_ID" --arg cmd "$wrapped" '{vm_id:$vm,command:$cmd}') | |
| for attempt in 1 2 3 4 5; do | |
| if out=$(printf '%s' "$msg" | grpcurl -plaintext -emit-defaults -max-time 120 \ | |
| -H "authorization: Bearer $BOXD_JWT" -d @ \ | |
| boxd-stg.sh:9443 boxd.api.v1.BoxdApi/Exec 2>&1); then | |
| echo "$out" | jq -s -r 'map(.data // "") | join("")' | base64 -d 2>/dev/null || true | |
| rc=$(echo "$out" | jq -s 'last.exitCode // 0') | |
| exit "$rc" | |
| fi | |
| if echo "$out" | grep -qE "connection to VM|Unavailable|No route to host"; then | |
| echo "transient VM-unreachable (attempt $attempt), retrying in $((attempt*3))s..." | |
| sleep $((attempt*3)) | |
| continue | |
| fi | |
| echo "exec failed:" | |
| echo "$out" | |
| exit 1 | |
| done | |
| echo "exec failed after retries" | |
| exit 1 | |
| - name: Verify production URL | |
| run: | | |
| set -euo pipefail | |
| for i in 1 2 3 4 5; do | |
| code=$(curl -sS -o /dev/null -w '%{http_code}' https://snake-game-production.boxd-stg.sh/ --max-time 10 || true) | |
| echo "try $i: $code" | |
| [[ "$code" == "200" ]] && exit 0 | |
| sleep 3 | |
| done | |
| echo "production URL did not return 200" | |
| exit 1 |