Skip to content

Claude/cpu guard script dn d8u (#130) #332

Claude/cpu guard script dn d8u (#130)

Claude/cpu guard script dn d8u (#130) #332

Workflow file for this run

name: Deploy
on:
push:
branches: [main]
pull_request:
types: [closed]
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run — skip VPS changes, just run quality gates and notify'
required: false
default: 'false'
skip_tests:
description: 'Skip unit tests (emergency deploy only)'
required: false
default: 'false'
permissions:
contents: write
pull-requests: write
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
# ADMIN_IDS stored as a GitHub secret to avoid exposing Telegram user IDs in plaintext
ADMIN_IDS: ${{ secrets.ADMIN_IDS }}
AFFILIATE_SOURCE: GambleCodez
DISCORD_CODE_GENERATION_IMAGE_URL: "https://raw.githubusercontent.com/gamblecodezcom/Runewager/main/images/discord_code_generation.png"
DISCORD_VERIFY_IMAGE_URL: "https://raw.githubusercontent.com/gamblecodezcom/Runewager/main/images/discord_verify.png"
MAX_ANALYTICS_EVENTS: "2000"
MAX_ONBOARDING_STEPS_HISTORY: "500"
MINI_APP_CLAIM_URL: "https://t.me/RuneWager_bot/claim"
MINI_APP_PLAY_URL: "https://t.me/RuneWager_bot/Play"
MINI_APP_PROFILE_URL: "https://t.me/RuneWager_bot/profile"
PORT: "3000"
PROMO_AMOUNT_SC: "3.5"
PROMO_BONUS_RULE: "Bonus added once your account is verified and meets eligibility requirements. Limit once per account/IP. New users are defined as accounts that have never redeemed a promo code before."
PROMO_CLAIM_LIMIT: "600"
PROMO_CODE: SPORTS3.5
PROMO_ENTRY_IMAGE_URL: "https://raw.githubusercontent.com/gamblecodezcom/Runewager/main/images/promo_entry.png"
RW_DISCORD_JOIN: "https://discord.gg/runewagers"
RW_DISCORD_LINK: "https://discord.com/channels/1100486422395355197/1249181934811349052"
RW_DISCORD_SUPPORT: "https://discord.com/channels/1100486422395355197/1249182067296567338"
DEPLOY_MODE: normal
jobs:
# ---------------------------------------------------------------------------
# Job 1: Quality Gates — syntax, tests, audit, metadata, secrets, guards
# ---------------------------------------------------------------------------
quality-gates:
name: Quality Gates
runs-on: ubuntu-latest
# Run on: direct push to main, merged PR to main, or manual workflow_dispatch.
# Closed-without-merge PRs are excluded by the merged == true check.
if: >
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request.merged == true)
outputs:
commit_hash: ${{ steps.meta.outputs.commit_hash }}
deploy_time: ${{ steps.meta.outputs.deploy_time }}
version: ${{ steps.meta.outputs.version }}
started_at: ${{ steps.start.outputs.started_at }}
duration_seconds: ${{ steps.duration.outputs.duration_seconds }}
steps:
- name: Record start time
id: start
run: echo "started_at=$(date +%s)" >> "$GITHUB_OUTPUT"
- name: Guard — verify valid merge to main
if: github.event_name == 'pull_request'
run: |
echo "PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}"
echo "Merged into: ${{ github.event.pull_request.base.ref }}"
echo "Merge commit: ${{ github.event.pull_request.merge_commit_sha }}"
echo "✅ Valid merge to main confirmed — proceeding with quality gates"
- uses: actions/checkout@v4
- name: Guard — skip if commit says so
run: |
MSG=$(git log -1 --format="%s %b")
if echo "$MSG" | grep -qi '\[skip deploy\]'; then
echo "Commit message contains [skip deploy] — aborting."
exit 1
fi
echo "✅ No [skip deploy] flag found"
- name: Verify required secrets exist
env:
_BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
_SERVER_KEY: ${{ secrets.SERVER_KEY }}
_SERVER_HOST: ${{ secrets.SERVER_HOST }}
_SERVER_USER: ${{ secrets.SERVER_USER }}
run: |
MISSING=0
[ -z "$_BOT_TOKEN" ] && echo "❌ Missing secret: BOT_TOKEN" && MISSING=1
[ -z "$_SERVER_KEY" ] && echo "❌ Missing secret: SERVER_KEY" && MISSING=1
[ -z "$_SERVER_HOST" ]&& echo "❌ Missing secret: SERVER_HOST"&& MISSING=1
[ -z "$_SERVER_USER" ]&& echo "❌ Missing secret: SERVER_USER"&& MISSING=1
[ "$MISSING" -eq 1 ] && exit 1
echo "✅ All required secrets present"
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Verify Node version is 20.x
run: |
echo "Node: $(node -v)"
echo "npm: $(npm -v)"
NODE_MAJOR=$(node -v | sed 's/v\([0-9]*\).*/\1/')
if [ "$NODE_MAJOR" -lt 20 ]; then
echo "ERROR: Node 20+ required, found $(node -v)" >&2
exit 1
fi
echo "✅ Node $(node -v) OK"
- name: Install deps
run: npm ci
- name: Build metadata
id: meta
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "deploy_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT"
echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- name: Syntax check
run: npm run check
- name: Tests
if: github.event.inputs.skip_tests != 'true'
run: npm test
- name: Security audit (non-blocking)
run: npm audit --audit-level=high || true
- name: Compute duration
id: duration
run: |
STARTED="${{ steps.start.outputs.started_at }}"
NOW=$(date +%s)
echo "duration_seconds=$((NOW - STARTED))" >> "$GITHUB_OUTPUT"
- name: Notify deploy start
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "🚀 *Deploy Starting*
Repo: \`${{ github.repository }}\`
Branch: \`${{ github.ref_name }}\`
Commit: \`${{ steps.meta.outputs.commit_hash }}\`
Version: \`${{ steps.meta.outputs.version }}\`
Mode: \`${DEPLOY_MODE}\`
By: \`${{ github.actor }}\`"
# ---------------------------------------------------------------------------
# Job 2: Deploy to VPS — pull latest main, restart service, health check
# Only triggers after quality gates pass on a valid PR merge to main.
# ---------------------------------------------------------------------------

Check failure on line 166 in .github/workflows/deploy.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/deploy.yml

Invalid workflow file

You have an error in your yaml syntax on line 166
deploy:
name: Deploy to VPS
runs-on: ubuntu-latest
needs: quality-gates
environment: Production
if: needs.quality-gates.result == 'success'
env:
DEPLOY_PASS: ${{ secrets.DEPLOY_PASS }}
steps:
- uses: actions/checkout@v4
- name: Install sshpass (password fallback)
run: sudo apt-get install -y sshpass
- name: Setup SSH
env:
SERVER_KEY: ${{ secrets.SERVER_KEY }}
SERVER_HOST: ${{ secrets.SERVER_HOST }}
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
printf '%s\n' "$SERVER_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$SERVER_HOST" >> ~/.ssh/known_hosts
cat >> ~/.ssh/config <<EOF
Host deploy_target
HostName $SERVER_HOST
User ${{ secrets.SERVER_USER }}
IdentityFile ~/.ssh/deploy_key
StrictHostKeyChecking yes
ConnectTimeout 15
EOF
- name: Notify — deploy job started
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "🚀 *Deploy Job Started*
Repo: \`${{ github.repository }}\`
Branch: \`${{ github.ref_name }}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Version: \`${{ needs.quality-gates.outputs.version }}\`
Actor: \`${{ github.actor }}\`"
- name: Save pre-deploy SHA
id: pre
run: |
SHA=$(ssh deploy_target "cd /var/www/html/Runewager && git rev-parse HEAD || echo none")
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
- name: Pre-deploy health snapshot
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
ssh deploy_target "
set -e
cd /var/www/html/Runewager
echo 'Checking .env exists...'
if [ ! -f .env ]; then
echo 'ERROR: .env missing on VPS' >&2
exit 1
fi
echo 'Checking port 3000 usage...'
lsof -i :3000 || true
echo 'Checking memory/disk...'
free -m || true
df -h . || true
"
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "📋 *Pre-Deploy Health Snapshot*
Path: \`/var/www/html/Runewager\`
Commit (pre): \`${{ steps.pre.outputs.sha }}\`"
- name: Deploy via deploy.sh (key first, then password fallback)
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
chmod +x scripts/notify-telegram.sh
DEPLOY_CMD='
set -e
cd /var/www/html/Runewager
echo "Pulling latest main..."
git fetch origin main
git reset --hard origin/main
echo "Ensuring .env exists and is preserved..."
if [ ! -f .env ]; then
echo "ERROR: .env missing on VPS" >&2
exit 1
fi
echo "Stopping systemd service (if running)..."
sudo systemctl stop runewager.service || true
echo "Killing any stray bot processes (scoped to this project only)..."
pkill -f "Runewager/index\.js" || true
echo "Installing production dependencies..."
npm ci --omit=dev
echo "Starting systemd service cleanly..."
sudo systemctl daemon-reload || true
sudo systemctl start runewager.service
echo "Checking systemd status..."
sudo systemctl is-active --quiet runewager.service
'
./scripts/notify-telegram.sh "🔑 *Deploy Phase — SSH Key Attempt*
Host: \`${SERVER_HOST}\`"
if ssh deploy_target "$DEPLOY_CMD"; then
echo "✅ Deployed via SSH key"
./scripts/notify-telegram.sh "✅ *Deploy Succeeded via SSH Key*
Host: \`${SERVER_HOST}\`"
else
echo "⚠️ SSH key failed — waiting 120 seconds before password retry..."
./scripts/notify-telegram.sh "⚠️ *SSH Key Failed — Waiting 2 Minutes Before Password Fallback*
Host: \`${SERVER_HOST}\`"
sleep 120
if sshpass -p "$DEPLOY_PASS" ssh \
-o StrictHostKeyChecking=yes \
-o ConnectTimeout=15 \
-o UserKnownHostsFile=~/.ssh/known_hosts \
"${SERVER_USER}@${SERVER_HOST}" "$DEPLOY_CMD"; then
echo "✅ Deployed via password fallback"
./scripts/notify-telegram.sh "✅ *Deploy Succeeded via Password Fallback*
Host: \`${SERVER_HOST}\`"
else
echo "❌ Both SSH attempts failed"
./scripts/notify-telegram.sh "❌ *Deploy Failed — SSH Key + Password Fallback Both Failed*
Host: \`${SERVER_HOST}\`
Action required: manual SSH + service check."
exit 1
fi
fi
- name: Download deploy report from VPS
if: always()
run: |
ssh deploy_target "cat /tmp/deploy-report.txt 2>/dev/null || echo 'No deploy report file found.'" \
> /tmp/deploy-report.txt || echo 'No deploy report available.' > /tmp/deploy-report.txt
- name: Upload deploy report as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: deploy-summary
path: /tmp/deploy-report.txt
if-no-files-found: ignore
retention-days: 7
- name: Post-deploy health check (30s delay)
if: success()
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "⏳ *Post-Deploy Health Check in 30s*"
sleep 30
HEALTH_STATUS="FAIL"
if ssh deploy_target "curl -fsS http://127.0.0.1:3000/health" >/dev/null 2>&1; then
HEALTH_STATUS="OK"
fi
./scripts/notify-telegram.sh "📡 *Post-Deploy Health Check*
Status: \`${HEALTH_STATUS}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`"
- name: Rollback on failure
if: failure()
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
PRE="${{ steps.pre.outputs.sha }}"
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "❌ *Deploy Failed — Starting Rollback*
Previous SHA: \`${PRE}\`"
ssh deploy_target "
cd /var/www/html/Runewager
if [ \"$PRE\" != \"none\" ] && [ -n \"$PRE\" ]; then
git reset --hard \"$PRE\"
npm ci --omit=dev || true
sudo systemctl stop runewager.service || true
pkill -f \"Runewager/index\.js\" || true
sudo systemctl daemon-reload || true
sudo systemctl start runewager.service || true
else
echo 'No previous SHA recorded, skipping git rollback'
fi
"
./scripts/notify-telegram.sh "🔄 *Rollback Complete*
Restored to: \`${PRE}\`"
- name: Final deploy report
if: always()
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
STATUS="${{ job.status }}"
COMMIT="${{ needs.quality-gates.outputs.commit_hash }}"
VERSION="${{ needs.quality-gates.outputs.version }}"
TIME="${{ needs.quality-gates.outputs.deploy_time }}"
REPORT_BODY=$(cat /tmp/deploy-report.txt 2>/dev/null || echo "No deploy report available.")
if [ "$STATUS" = "success" ]; then
MSG="🎉 *Final Deploy Report*
Status: ✅ Success
Commit: \`$COMMIT\`
Version: \`$VERSION\`
Time: \`$TIME\`
$REPORT_BODY"
else
MSG="⚠️ *Final Deploy Report*
Status: ❌ Failure
Commit: \`$COMMIT\`
Version: \`$VERSION\`
Time: \`$TIME\`
$REPORT_BODY"
fi
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "$MSG"
# ---------------------------------------------------------------------------
# Job 3: Dry-run report (only when dry_run=true)
# ---------------------------------------------------------------------------
dry-run-report:
name: Dry Run Report
runs-on: ubuntu-latest
needs: quality-gates
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Send dry-run notification
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "🧪 *Dry Run Only — No VPS Changes*
Repo: \`${{ github.repository }}\`
Branch: \`${{ github.ref_name }}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Version: \`${{ needs.quality-gates.outputs.version }}\`
Time: \`${{ needs.quality-gates.outputs.deploy_time }}\`
By: \`${{ github.actor }}\`
Quality gates passed. VPS was NOT modified."