From da65a373d59d732faaa873fc36a397e96fbd7e79 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 07:23:51 +0000 Subject: [PATCH] fix(ci): switch deploy strategy from rsync-to-/current/ to git pull on VPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of CI/CD failures: the pipeline rsynced code to a new /current/ subdirectory that the existing systemd service and /deploy bot command don't know about, so restarts continued running old code and post-deploy checks probed the wrong paths. Fix: mirror the working /deploy bot command — SSH to VPS and run git fetch origin main + git reset --hard origin/main in the flat /var/www/html/Runewager/ directory that the service already uses. Changes: - deploy.yml: replace archive+rsync steps with git pull approach; save pre-deploy SHA for git-based rollback on failure; remove dead dry-run guard; update all /current/ paths to flat path; rollback now does git reset --hard + npm ci + restart - runewager.service: revert all paths from /current/ back to flat /var/www/html/Runewager/ (service must match deploy target) - post-deploy-verify.sh: update DEPLOY_DIR default to flat path https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN --- .github/workflows/deploy.yml | 150 +++++++++++++++------------------- runewager.service | 16 ++-- scripts/post-deploy-verify.sh | 2 +- 3 files changed, 76 insertions(+), 92 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 539f9e9..1d08e7d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,7 +3,10 @@ name: Deploy # ────────────────────────────────────────────────────────────────────────────── # Triggers: # - Push to main branch (automatic production deploy) -# - Manual trigger (workflow_dispatch) with optional mode selector +# - Manual trigger (workflow_dispatch — no inputs, SLSA provenance safe) +# +# Deploy strategy: SSH → git pull origin main on VPS (mirrors /deploy bot cmd) +# Rollback strategy: git reset --hard on failure # ────────────────────────────────────────────────────────────────────────────── on: push: @@ -140,14 +143,6 @@ jobs: ConnectTimeout 15 EOF - # ── Dry-run guard ──────────────────────────────────────────────────────── - - name: Dry-run check - if: ${{ env.DEPLOY_MODE == 'dry-run' }} - run: | - echo "🧪 DRY-RUN mode — skipping file sync and service restart." - echo "All quality gates passed. Deploy would succeed." - exit 0 - # ── Write .env for deploy ──────────────────────────────────────────────── - name: Write .env file env: @@ -182,71 +177,60 @@ jobs: # Verify no empty critical vars leaked into .env grep -q "^BOT_TOKEN=.\+" .env || (echo "ERROR: BOT_TOKEN is empty in .env" && exit 1) - # ── Archive current release on VPS ─────────────────────────────────────── - - name: Archive current release on VPS + # ── Save pre-deploy commit SHA (used for git rollback on failure) ────────── + - name: Save pre-deploy state + id: predeploy + run: | + PRE_SHA=$(ssh deploy_target \ + "cd /var/www/html/Runewager && git rev-parse HEAD 2>/dev/null || echo none") + echo "pre_sha=${PRE_SHA}" >> "$GITHUB_OUTPUT" + + # ── Pull latest code on VPS ─────────────────────────────────────────────── + # Mirrors what the bot's /deploy command does — git fetch + reset to main. + - name: Pull latest code on VPS run: | - TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ) ssh deploy_target " set -euo pipefail - BASE=/var/www/html/Runewager - CURRENT=\${BASE}/current - RELEASES=\${BASE}/releases - mkdir -p \$RELEASES - if [[ -d \$CURRENT ]] && [[ -f \$CURRENT/index.js ]]; then - echo 'Archiving current release...' - cp -a \$CURRENT \$RELEASES/release_${TIMESTAMP} - echo 'Archived to: \$RELEASES/release_${TIMESTAMP}' - else - echo 'No existing current release to archive.' - fi + cd /var/www/html/Runewager + git fetch origin main + git reset --hard origin/main + echo \"Code updated to: \$(git rev-parse --short HEAD)\" " - # ── Rsync new code to VPS ───────────────────────────────────────────────── - - name: Rsync code to VPS - run: | - rsync -az --delete \ - --exclude='.git/' \ - --exclude='node_modules/' \ - --exclude='data/' \ - --exclude='logs/' \ - --exclude='.env' \ - -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ - ./ ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/var/www/html/Runewager/current/ - - # ── Deploy .env ────────────────────────────────────────────────────────── + # ── Upload .env to VPS ──────────────────────────────────────────────────── - name: Upload .env to VPS run: | scp -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=yes \ .env \ - ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/var/www/html/Runewager/current/.env + ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/var/www/html/Runewager/.env # ── npm ci on VPS ───────────────────────────────────────────────────────── - name: Install dependencies on VPS run: | ssh deploy_target " set -euo pipefail - cd /var/www/html/Runewager/current + cd /var/www/html/Runewager npm ci --omit=dev " - # ── Run smoke test on VPS (after deps installed) ────────────────────────── + # ── Smoke test (code staged, service still running previous version) ─────── - name: Smoke test on VPS id: smoke run: | ssh deploy_target " set -euo pipefail - DEPLOY_DIR=/var/www/html/Runewager/current + DEPLOY_DIR=/var/www/html/Runewager chmod +x \$DEPLOY_DIR/scripts/smoke-test.sh \$DEPLOY_DIR/scripts/smoke-test.sh \$DEPLOY_DIR " - # ── Verify .env on VPS ───────────────────────────────────────────────────── + # ── Verify .env contains all required vars ──────────────────────────────── - name: Verify .env on VPS run: | ssh deploy_target " set -euo pipefail - ENV_FILE=/var/www/html/Runewager/current/.env + ENV_FILE=/var/www/html/Runewager/.env for var in BOT_TOKEN ADMIN_IDS PORT MINI_APP_PLAY_URL; do if ! grep -q \"^\${var}=.\\+\" \$ENV_FILE 2>/dev/null; then echo \"ERROR: .env missing or empty: \$var\" @@ -274,10 +258,10 @@ jobs: run: | ssh deploy_target " set -euo pipefail - mkdir -p /var/www/html/Runewager/current/data/backups - mkdir -p /var/www/html/Runewager/current/logs - touch /var/www/html/Runewager/current/logs/runewager.log - touch /var/www/html/Runewager/current/logs/runewager-error.log + mkdir -p /var/www/html/Runewager/data/backups + mkdir -p /var/www/html/Runewager/logs + touch /var/www/html/Runewager/logs/runewager.log + touch /var/www/html/Runewager/logs/runewager-error.log echo 'Runtime directories OK' " @@ -304,33 +288,25 @@ jobs: run: | ssh deploy_target " set -euo pipefail - DEPLOY_DIR=/var/www/html/Runewager/current + DEPLOY_DIR=/var/www/html/Runewager chmod +x \$DEPLOY_DIR/scripts/post-deploy-verify.sh DEPLOY_DIR=\$DEPLOY_DIR \$DEPLOY_DIR/scripts/post-deploy-verify.sh " - # ── Write release artifact ───────────────────────────────────────────────── + # ── Write release metadata ───────────────────────────────────────────────── - name: Write release metadata run: | ssh deploy_target " set -euo pipefail - cat > /var/www/html/Runewager/current/.release_info < /var/www/html/Runewager/.release_info </dev/null || true + systemctl restart runewager.service 2>/dev/null || true + echo 'Rolled back to ${PRE_SHA}' + " || true + + ./scripts/notify-telegram.sh \ + "🔄 *Rollback Complete* +Restored to: \`${PRE_SHA}\`" || true + else + ./scripts/notify-telegram.sh \ + "⚠️ *Rollback Skipped* — no pre-deploy SHA was captured." || true + fi diff --git a/runewager.service b/runewager.service index d678961..52b9612 100644 --- a/runewager.service +++ b/runewager.service @@ -5,10 +5,10 @@ # The authoritative /etc/systemd/system/runewager.service is written and # maintained by prod-run.sh, which fills in the correct paths at runtime. # -# For CI/CD deploys the active code lives at: -# /var/www/html/Runewager/current/ +# Deploy strategy: git pull origin main to /var/www/html/Runewager/ (flat). +# The /deploy bot command and CI/CD pipeline both target this directory. # -# To install on a fresh VPS (if using CI/CD deploy structure): +# To install on a fresh VPS: # cp runewager.service /etc/systemd/system/runewager.service # systemctl daemon-reload # systemctl enable --now runewager @@ -23,11 +23,11 @@ Wants=network-online.target [Service] Type=simple -WorkingDirectory=/var/www/html/Runewager/current -ExecStart=/usr/bin/node /var/www/html/Runewager/current/index.js +WorkingDirectory=/var/www/html/Runewager +ExecStart=/usr/bin/node /var/www/html/Runewager/index.js User=root Group=root -EnvironmentFile=/var/www/html/Runewager/current/.env +EnvironmentFile=/var/www/html/Runewager/.env Environment=NODE_ENV=production # Restart policy: always restart on crash, with rate limiting @@ -41,8 +41,8 @@ KillSignal=SIGTERM TimeoutStopSec=30 # Logging: append mode — survives log rotation without restart -StandardOutput=append:/var/www/html/Runewager/current/logs/runewager.log -StandardError=append:/var/www/html/Runewager/current/logs/runewager-error.log +StandardOutput=append:/var/www/html/Runewager/logs/runewager.log +StandardError=append:/var/www/html/Runewager/logs/runewager-error.log # File descriptor limit (for concurrent connections) LimitNOFILE=65536 diff --git a/scripts/post-deploy-verify.sh b/scripts/post-deploy-verify.sh index baf1b3f..a493fcc 100755 --- a/scripts/post-deploy-verify.sh +++ b/scripts/post-deploy-verify.sh @@ -8,7 +8,7 @@ # ============================================================================= set -euo pipefail -DEPLOY_DIR="${DEPLOY_DIR:-/var/www/html/Runewager/current}" +DEPLOY_DIR="${DEPLOY_DIR:-/var/www/html/Runewager}" PORT="${PORT:-3000}" HEALTH_URL="http://127.0.0.1:${PORT}/health" HEALTH_RETRIES=5