Skip to content

Commit e6c4be2

Browse files
committed
ci: overhaul deploy job with inline commands, env approval gate, and granular Telegram notifications
- Add `environment: Production` to require manual approval gate before VPS deploy - Replace deploy.sh invocation with fully inline deployment commands (git fetch/reset, npm ci, systemctl stop/start) - Add cleanup of stray Node processes (pkill) before service restart - Add dedicated "Notify — deploy job started" step at the top of the deploy job - Add Telegram notification after pre-deploy health snapshot - Add per-phase Telegram alerts: key attempt, key success, password fallback triggered, password success, and full failure with action required - Move post-deploy health check (30s) before rollback/final-report steps; add leading "⏳ in 30s" notification - Update rollback to use same inline style (stop, pkill, reset, start) with "Starting Rollback" + "Rollback Complete" notifications - Remove duration field from final deploy report (not available in deploy job scope) https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN
1 parent 5a81126 commit e6c4be2

1 file changed

Lines changed: 100 additions & 51 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 100 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,14 @@ Mode: \`${DEPLOY_MODE}\`
160160
By: \`${{ github.actor }}\`"
161161

162162
# ---------------------------------------------------------------------------
163-
# Job 2: Deploy to VPS — SSH into VPS and run deploy.sh
163+
# Job 2: Deploy to VPS — pull latest main, restart service, health check
164164
# Only triggers after quality gates pass on a valid PR merge to main.
165165
# ---------------------------------------------------------------------------
166166
deploy:
167167
name: Deploy to VPS
168168
runs-on: ubuntu-latest
169169
needs: quality-gates
170+
environment: Production
170171
if: needs.quality-gates.result == 'success'
171172

172173
env:
@@ -175,7 +176,7 @@ By: \`${{ github.actor }}\`"
175176
steps:
176177
- uses: actions/checkout@v4
177178

178-
- name: Install sshpass (password SSH fallback)
179+
- name: Install sshpass (password fallback)
179180
run: sudo apt-get install -y sshpass
180181

181182
- name: Setup SSH
@@ -185,9 +186,12 @@ By: \`${{ github.actor }}\`"
185186
run: |
186187
mkdir -p ~/.ssh
187188
chmod 700 ~/.ssh
189+
188190
printf '%s\n' "$SERVER_KEY" > ~/.ssh/deploy_key
189191
chmod 600 ~/.ssh/deploy_key
192+
190193
ssh-keyscan -H "$SERVER_HOST" >> ~/.ssh/known_hosts
194+
191195
cat >> ~/.ssh/config <<EOF
192196
Host deploy_target
193197
HostName $SERVER_HOST
@@ -197,13 +201,27 @@ By: \`${{ github.actor }}\`"
197201
ConnectTimeout 15
198202
EOF
199203
204+
- name: Notify — deploy job started
205+
env:
206+
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
207+
run: |
208+
chmod +x scripts/notify-telegram.sh
209+
./scripts/notify-telegram.sh "🚀 *Deploy Job Started*
210+
Repo: \`${{ github.repository }}\`
211+
Branch: \`${{ github.ref_name }}\`
212+
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
213+
Version: \`${{ needs.quality-gates.outputs.version }}\`
214+
Actor: \`${{ github.actor }}\`"
215+
200216
- name: Save pre-deploy SHA
201217
id: pre
202218
run: |
203219
SHA=$(ssh deploy_target "cd /var/www/html/Runewager && git rev-parse HEAD || echo none")
204220
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
205221
206222
- name: Pre-deploy health snapshot
223+
env:
224+
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
207225
run: |
208226
ssh deploy_target "
209227
set -e
@@ -221,49 +239,87 @@ By: \`${{ github.actor }}\`"
221239
echo 'Checking memory/disk...'
222240
free -m || true
223241
df -h . || true
224-
225-
echo 'Checking disk free threshold...'
226-
FREE_KB=\$(df . | awk 'NR==2 {print \$4}')
227-
FREE_MB=\$((FREE_KB / 1024))
228-
if [ \"\$FREE_MB\" -lt 100 ]; then
229-
echo 'WARNING: Less than 100MB free disk space (' \$FREE_MB 'MB). Proceeding with caution.'
230-
else
231-
echo \"✅ Disk: \${FREE_MB}MB free\"
232-
fi
233242
"
234243
235-
- name: Deploy via deploy.sh (with password fallback)
244+
chmod +x scripts/notify-telegram.sh
245+
./scripts/notify-telegram.sh "📋 *Pre-Deploy Health Snapshot*
246+
Path: \`/var/www/html/Runewager\`
247+
Commit (pre): \`${{ steps.pre.outputs.sha }}\`"
248+
249+
- name: Deploy via deploy.sh (key first, then password fallback)
236250
env:
237251
SERVER_HOST: ${{ secrets.SERVER_HOST }}
238252
SERVER_USER: ${{ secrets.SERVER_USER }}
253+
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
239254
run: |
240-
DEPLOY_CMD="bash /var/www/html/Runewager/deploy.sh github"
255+
chmod +x scripts/notify-telegram.sh
256+
257+
DEPLOY_CMD='
258+
set -e
259+
cd /var/www/html/Runewager
260+
261+
echo "Pulling latest main..."
262+
git fetch origin main
263+
git reset --hard origin/main
264+
265+
echo "Ensuring .env exists and is preserved..."
266+
if [ ! -f .env ]; then
267+
echo "ERROR: .env missing on VPS" >&2
268+
exit 1
269+
fi
270+
271+
echo "Stopping systemd service (if running)..."
272+
sudo systemctl stop runewager.service || true
273+
274+
echo "Killing any stray Node/bot processes..."
275+
pkill -f "Runewager" || true
276+
pkill -f "index.js" || true
277+
pkill -f "node" || true
278+
279+
echo "Installing production dependencies..."
280+
npm ci --omit=dev
281+
282+
echo "Starting systemd service cleanly..."
283+
sudo systemctl daemon-reload || true
284+
sudo systemctl start runewager.service
285+
286+
echo "Checking systemd status..."
287+
sudo systemctl is-active --quiet runewager.service
288+
'
289+
290+
./scripts/notify-telegram.sh "🔑 *Deploy Phase — SSH Key Attempt*
291+
Host: \`${SERVER_HOST}\`"
241292

242-
# First attempt: SSH with key
243293
if ssh deploy_target "$DEPLOY_CMD"; then
244294
echo "✅ Deployed via SSH key"
295+
./scripts/notify-telegram.sh "✅ *Deploy Succeeded via SSH Key*
296+
Host: \`${SERVER_HOST}\`"
245297
else
246-
echo "⚠️ SSH key attempt failed — waiting 120 seconds before password retry…"
298+
echo "⚠️ SSH key failed — waiting 120 seconds before password retry..."
299+
./scripts/notify-telegram.sh "⚠️ *SSH Key Failed — Waiting 2 Minutes Before Password Fallback*
300+
Host: \`${SERVER_HOST}\`"
247301
sleep 120
248302

249-
# Second attempt: password fallback via sshpass
250303
if sshpass -p "$DEPLOY_PASS" ssh \
251304
-o StrictHostKeyChecking=no \
252305
-o ConnectTimeout=15 \
253306
"${SERVER_USER}@${SERVER_HOST}" "$DEPLOY_CMD"; then
254307
echo "✅ Deployed via password fallback"
308+
./scripts/notify-telegram.sh "✅ *Deploy Succeeded via Password Fallback*
309+
Host: \`${SERVER_HOST}\`"
255310
else
256-
echo "❌ Both SSH attempts failed — sending admin alert"
257-
chmod +x scripts/notify-telegram.sh
258-
./scripts/notify-telegram.sh "❌ Deploy failed — SSH could not connect to VPS after key + password retry. Manual intervention required."
311+
echo "❌ Both SSH attempts failed"
312+
./scripts/notify-telegram.sh "❌ *Deploy Failed — SSH Key + Password Fallback Both Failed*
313+
Host: \`${SERVER_HOST}\`
314+
Action required: manual SSH + service check."
259315
exit 1
260316
fi
261317
fi
262318

263319
- name: Download deploy report from VPS
264320
if: always()
265321
run: |
266-
ssh deploy_target "cat /tmp/deploy-report.txt 2>/dev/null || echo 'No deploy report file found on server.'" \
322+
ssh deploy_target "cat /tmp/deploy-report.txt 2>/dev/null || echo 'No deploy report file found.'" \
267323
> /tmp/deploy-report.txt || echo 'No deploy report available.' > /tmp/deploy-report.txt
268324
269325
- name: Upload deploy report as artifact
@@ -275,35 +331,48 @@ By: \`${{ github.actor }}\`"
275331
if-no-files-found: ignore
276332
retention-days: 7
277333

278-
- name: Notify success
334+
- name: Post-deploy health check (30s delay)
279335
if: success()
280336
env:
281337
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
282338
run: |
283339
chmod +x scripts/notify-telegram.sh
284-
./scripts/notify-telegram.sh "✅ *Deploy Successful*
285-
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`
286-
Version: \`${{ needs.quality-gates.outputs.version }}\`
287-
Time: \`${{ needs.quality-gates.outputs.deploy_time }}\`"
340+
./scripts/notify-telegram.sh "⏳ *Post-Deploy Health Check in 30s*"
341+
sleep 30
342+
343+
HEALTH_STATUS="FAIL"
344+
if ssh deploy_target "curl -fsS http://127.0.0.1:3000/health" >/dev/null 2>&1; then
345+
HEALTH_STATUS="OK"
346+
fi
347+
348+
./scripts/notify-telegram.sh "📡 *Post-Deploy Health Check*
349+
Status: \`${HEALTH_STATUS}\`
350+
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`"
288351

289352
- name: Rollback on failure
290353
if: failure()
291354
env:
292355
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
293356
run: |
294357
PRE="${{ steps.pre.outputs.sha }}"
295-
296358
chmod +x scripts/notify-telegram.sh
297-
./scripts/notify-telegram.sh "❌ *Deploy Failed — Rolling Back*
298-
Previous SHA: \`${PRE}\`
299-
Reason: Deployment failed — see GitHub Actions logs for full stack trace."
359+
360+
./scripts/notify-telegram.sh "❌ *Deploy Failed — Starting Rollback*
361+
Previous SHA: \`${PRE}\`"
300362

301363
ssh deploy_target "
302364
cd /var/www/html/Runewager
303365
if [ \"$PRE\" != \"none\" ] && [ -n \"$PRE\" ]; then
304-
git reset --hard $PRE
366+
git reset --hard \"$PRE\"
305367
npm ci --omit=dev || true
306-
systemctl restart runewager.service || bash prod-run.sh restart || true
368+
369+
sudo systemctl stop runewager.service || true
370+
pkill -f \"Runewager\" || true
371+
pkill -f \"index.js\" || true
372+
pkill -f \"node\" || true
373+
374+
sudo systemctl daemon-reload || true
375+
sudo systemctl start runewager.service || true
307376
else
308377
echo 'No previous SHA recorded, skipping git rollback'
309378
fi
@@ -321,7 +390,6 @@ Restored to: \`${PRE}\`"
321390
COMMIT="${{ needs.quality-gates.outputs.commit_hash }}"
322391
VERSION="${{ needs.quality-gates.outputs.version }}"
323392
TIME="${{ needs.quality-gates.outputs.deploy_time }}"
324-
DURATION="${{ needs.quality-gates.outputs.duration_seconds }}"
325393
326394
REPORT_BODY=$(cat /tmp/deploy-report.txt 2>/dev/null || echo "No deploy report available.")
327395
@@ -331,7 +399,6 @@ Status: ✅ Success
331399
Commit: \`$COMMIT\`
332400
Version: \`$VERSION\`
333401
Time: \`$TIME\`
334-
Duration: \`${DURATION}s\`
335402

336403
$REPORT_BODY"
337404
else
@@ -340,31 +407,13 @@ Status: ❌ Failure
340407
Commit: \`$COMMIT\`
341408
Version: \`$VERSION\`
342409
Time: \`$TIME\`
343-
Duration: \`${DURATION}s\`
344-
Reason: Deployment failed — see GitHub Actions logs.
345410

346411
$REPORT_BODY"
347412
fi
348413

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

352-
- name: Post-deploy health check (30s delay)
353-
if: success()
354-
env:
355-
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
356-
run: |
357-
echo "Waiting 30 seconds before post-deploy health check..."
358-
sleep 30
359-
HEALTH_STATUS="FAIL"
360-
if ssh deploy_target "curl -fsS http://127.0.0.1:3000/health" >/dev/null 2>&1; then
361-
HEALTH_STATUS="OK"
362-
fi
363-
chmod +x scripts/notify-telegram.sh
364-
./scripts/notify-telegram.sh "📡 *Post-Deploy Health Check (30s)*
365-
Status: \`${HEALTH_STATUS}\`
366-
Commit: \`${{ needs.quality-gates.outputs.commit_hash }}\`"
367-
368417
# ---------------------------------------------------------------------------
369418
# Job 3: Dry-run report (only when dry_run=true)
370419
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)