Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
153 changes: 100 additions & 53 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,24 +160,23 @@ Mode: \`${DEPLOY_MODE}\`
By: \`${{ github.actor }}\`"

# ---------------------------------------------------------------------------
# Job 2: Deploy to VPS — SSH into VPS and run deploy.sh
# 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.
# ---------------------------------------------------------------------------
deploy:
name: Deploy to VPS
runs-on: ubuntu-latest
needs: quality-gates
environment: Production
if: needs.quality-gates.result == 'success'
environment:
name: production

env:
DEPLOY_PASS: ${{ secrets.DEPLOY_PASS }}

steps:
- uses: actions/checkout@v4

- name: Install sshpass (password SSH fallback)
- name: Install sshpass (password fallback)
run: sudo apt-get install -y sshpass

- name: Setup SSH
Expand All @@ -187,9 +186,12 @@ By: \`${{ github.actor }}\`"
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
Expand All @@ -199,13 +201,27 @@ By: \`${{ github.actor }}\`"
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
Expand All @@ -223,49 +239,87 @@ By: \`${{ github.actor }}\`"
echo 'Checking memory/disk...'
free -m || true
df -h . || true

echo 'Checking disk free threshold...'
FREE_KB=\$(df . | awk 'NR==2 {print \$4}')
FREE_MB=\$((FREE_KB / 1024))
if [ \"\$FREE_MB\" -lt 100 ]; then
echo 'WARNING: Less than 100MB free disk space (' \$FREE_MB 'MB). Proceeding with caution.'
else
echo \"✅ Disk: \${FREE_MB}MB free\"
fi
"

- name: Deploy via deploy.sh (with password fallback)
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: |
DEPLOY_CMD="bash /var/www/html/Runewager/deploy.sh github"
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 Node/bot processes..."
pkill -f "Runewager" || true
pkill -f "index.js" || true
pkill -f "node" || 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}\`"

# First attempt: SSH with key
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 attempt failed — waiting 120 seconds before password retry…"
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

# Second attempt: password fallback via sshpass
if sshpass -p "$DEPLOY_PASS" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=15 \
"${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 — sending admin alert"
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "❌ Deploy failed — SSH could not connect to VPS after key + password retry. Manual intervention required."
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 on server.'" \
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
Expand All @@ -277,35 +331,48 @@ By: \`${{ github.actor }}\`"
if-no-files-found: ignore
retention-days: 7

- name: Notify success
- 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 "✅ *Deploy Successful*
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
Version: \`${{ needs.quality-gates.outputs.version }}\`
Time: \`${{ needs.quality-gates.outputs.deploy_time }}\`"
./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 — Rolling Back*
Previous SHA: \`${PRE}\`
Reason: Deployment failed — see GitHub Actions logs for full stack trace."

./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
git reset --hard \"$PRE\"
npm ci --omit=dev || true
systemctl restart runewager.service || bash prod-run.sh restart || true

sudo systemctl stop runewager.service || true
pkill -f \"Runewager\" || true
pkill -f \"index.js\" || true
pkill -f \"node\" || true

sudo systemctl daemon-reload || true
sudo systemctl start runewager.service || true
else
echo 'No previous SHA recorded, skipping git rollback'
fi
Expand All @@ -323,7 +390,6 @@ Restored to: \`${PRE}\`"
COMMIT="${{ needs.quality-gates.outputs.commit_hash }}"
VERSION="${{ needs.quality-gates.outputs.version }}"
TIME="${{ needs.quality-gates.outputs.deploy_time }}"
DURATION="${{ needs.quality-gates.outputs.duration_seconds }}"

REPORT_BODY=$(cat /tmp/deploy-report.txt 2>/dev/null || echo "No deploy report available.")

Expand All @@ -333,7 +399,6 @@ Status: ✅ Success
Commit: \`$COMMIT\`
Version: \`$VERSION\`
Time: \`$TIME\`
Duration: \`${DURATION}s\`

$REPORT_BODY"
else
Expand All @@ -342,31 +407,13 @@ Status: ❌ Failure
Commit: \`$COMMIT\`
Version: \`$VERSION\`
Time: \`$TIME\`
Duration: \`${DURATION}s\`
Reason: Deployment failed — see GitHub Actions logs.

$REPORT_BODY"
fi

chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "$MSG"

- name: Post-deploy health check (30s delay)
if: success()
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
echo "Waiting 30 seconds before post-deploy health check..."
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
chmod +x scripts/notify-telegram.sh
./scripts/notify-telegram.sh "📡 *Post-Deploy Health Check (30s)*
Status: \`${HEALTH_STATUS}\`
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`"

# ---------------------------------------------------------------------------
# Job 3: Dry-run report (only when dry_run=true)
# ---------------------------------------------------------------------------
Expand Down
Loading
Loading