Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 67 additions & 83 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pre-deploy SHA> on failure
# ──────────────────────────────────────────────────────────────────────────────
on:
push:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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\"
Expand Down Expand Up @@ -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'
"

Expand All @@ -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 <<EOF
version=${{ needs.quality-gates.outputs.version }}
commit=${{ needs.quality-gates.outputs.commit_hash }}
deploy_time=${{ needs.quality-gates.outputs.deploy_time }}
deployed_by=${{ github.actor }}
status=success
EOF
cat > /var/www/html/Runewager/.release_info <<EOF
version=${{ needs.quality-gates.outputs.version }}
commit=${{ needs.quality-gates.outputs.commit_hash }}
deploy_time=${{ needs.quality-gates.outputs.deploy_time }}
deployed_by=${{ github.actor }}
status=success
EOF
"

# ── Prune old releases ─────────────────────────────────────────────────────
- name: Cleanup old releases
run: |
ssh deploy_target "
chmod +x /var/www/html/Runewager/scripts/cleanup-releases.sh
BASE_DIR=/var/www/html/Runewager /var/www/html/Runewager/scripts/cleanup-releases.sh 5
" || true # non-fatal

# ── Notify: deploy success ────────────────────────────────────────────────
- name: Notify deploy success
if: success()
Expand All @@ -339,32 +315,40 @@ jobs:
run: |
./scripts/notify-telegram.sh \
"✅ *Deploy Successful*
Version: \`${{ needs.quality-gates.outputs.version }}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Deploy time: \`${{ needs.quality-gates.outputs.deploy_time }}\`
By: \`${{ github.actor }}\`

Version: \`${{ needs.quality-gates.outputs.version }}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Deploy time: \`${{ needs.quality-gates.outputs.deploy_time }}\`
Mode: \`${DEPLOY_MODE}\`
By: \`${{ github.actor }}\`
Health: ✅ OK"

Health: ✅ OK"

# ── Notify: deploy failure + auto-rollback ────────────────────────────────
# ── Notify: deploy failure + git-based auto-rollback ─────────────────────
- name: Rollback on failure
if: failure()
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
PRE_SHA="${{ steps.predeploy.outputs.pre_sha }}"

./scripts/notify-telegram.sh \
"❌ *Deploy Failed — Initiating Rollback*

Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Mode: \`${DEPLOY_MODE}\`

Rolling back to last stable release..." || true

ssh deploy_target "
chmod +x /var/www/html/Runewager/scripts/rollback.sh
BOT_TOKEN='${{ secrets.BOT_TOKEN }}' \
ADMIN_IDS='${ADMIN_IDS}' \
/var/www/html/Runewager/scripts/rollback.sh
" || true
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Rolling back to: \`${PRE_SHA:-unknown}\`..." || true

if [[ -n "${PRE_SHA}" ]] && [[ "${PRE_SHA}" != "none" ]]; then
ssh deploy_target "
set -euo pipefail
cd /var/www/html/Runewager
git reset --hard ${PRE_SHA}
npm ci --omit=dev 2>/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
16 changes: 8 additions & 8 deletions runewager.service
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion scripts/post-deploy-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down