-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·271 lines (246 loc) · 11.6 KB
/
deploy.sh
File metadata and controls
executable file
·271 lines (246 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#!/usr/bin/env bash
# =============================================================
# Runewager — VPS Deployment Script
#
# Usage:
# bash /var/www/html/Runewager/deploy.sh [source]
#
# source: github | bot | vps (default: vps)
# github — triggered by a GitHub Actions merged-PR workflow
# bot — triggered by the /deploy Telegram command
# vps — triggered by a manual SSH/shell call on the VPS
#
# What it does:
# 0. Skip deploy if VPS already has the latest commit (and service is active)
# 1. Stop the systemd service (prevents file locks)
# 2. git fetch --all + git reset --hard origin/main + git clean
# 3. npm ci --omit=dev (install/update production deps)
# 4. systemctl start runewager
# 5. Confirm service status
#
# Admin notifications (silent Telegram messages) are sent at
# every step so admins always know what is happening.
# =============================================================
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_NAME="runewager"
DEPLOY_SOURCE="${1:-vps}"
say() { printf '[%s] %s\n' "$APP_NAME" "$*"; }
warn() { printf '[%s][warn] %s\n' "$APP_NAME" "$*" >&2; }
say "=== Runewager VPS Deployment ==="
say "Project: $PROJECT_DIR"
say "Source: $DEPLOY_SOURCE"
say "Date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
cd "$PROJECT_DIR"
# ---------------------------------------------------------
# Load BOT_TOKEN and ADMIN_IDS for Telegram notifications.
# When deploy.sh is spawned from the bot process (source=bot),
# these are already inherited via process.env — don't overwrite.
# When invoked from GitHub Actions or a cron, parse from .env.
# ---------------------------------------------------------
if [[ -z "${BOT_TOKEN:-}" && -f "$PROJECT_DIR/.env" ]]; then
BOT_TOKEN="$(grep -E '^BOT_TOKEN=' "$PROJECT_DIR/.env" | head -1 | cut -d= -f2- | sed "s/^['\"]//;s/['\"]$//" | tr -d $'\r')" || true
fi
if [[ -z "${ADMIN_IDS:-}" && -f "$PROJECT_DIR/.env" ]]; then
ADMIN_IDS="$(grep -E '^ADMIN_IDS=' "$PROJECT_DIR/.env" | head -1 | cut -d= -f2- | sed "s/^['\"]//;s/['\"]$//" | tr -d $'\r')" || true
fi
export BOT_TOKEN ADMIN_IDS
# ---------------------------------------------------------
# Silent Telegram admin notification helper
# Uses curl form-encoding so emoji and UTF-8 are handled
# correctly without requiring jq or python3.
# disable_notification=true → no phone buzz/sound.
# ---------------------------------------------------------
send_admin() {
local msg="$1"
[[ -z "${BOT_TOKEN:-}" ]] && return 0
[[ -z "${ADMIN_IDS:-}" ]] && return 0
IFS=',' read -ra _IDS <<< "$ADMIN_IDS"
for _chat_id in "${_IDS[@]}"; do
_chat_id="${_chat_id// /}"
[[ -z "$_chat_id" ]] && continue
curl -s --max-time 10 \
"https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
--data-urlencode "text=${msg}" \
--data "chat_id=${_chat_id}" \
--data "disable_notification=true" \
>/dev/null 2>&1 || true
done
}
# ---------------------------------------------------------
# Track whether the service was stopped so the ERR trap can
# attempt a recovery restart if the deploy fails mid-way.
# ---------------------------------------------------------
_SERVICE_STOPPED=false
# ---------------------------------------------------------
# Error trap — notify admins and attempt service recovery.
# ${BASH_LINENO[0]} gives the caller's line number when
# evaluated inside an ERR trap (more reliable than $LINENO).
# ---------------------------------------------------------
_on_error() {
local line="${BASH_LINENO[0]:-?}"
warn "Script failed at line $line"
send_admin "❌ Deploy FAILED at line $line — check VPS logs. (source: $DEPLOY_SOURCE)"
# If the service was already stopped before the failure, try to bring it
# back up on whatever code is currently on disk so the bot isn't left down.
if [[ "$_SERVICE_STOPPED" == "true" ]] && command -v systemctl >/dev/null 2>&1; then
warn "Attempting service recovery restart on existing code…"
systemctl start "${APP_NAME}.service" || true
local _recovery_status
_recovery_status="$(systemctl is-active "${APP_NAME}.service" 2>/dev/null || echo unknown)"
send_admin "🔁 Recovery restart attempted after deploy failure — service is now: $_recovery_status"
fi
}
trap '_on_error' ERR
# ---------------------------------------------------------
# 0) Skip deploy if VPS already has the latest commit
# Prevents revert commits and accidental re-deploys from
# stopping the service for nothing.
# ---------------------------------------------------------
REMOTE_HASH=$(git ls-remote origin -h refs/heads/main 2>/dev/null | cut -f1 || true)
LOCAL_HASH=$(git rev-parse HEAD 2>/dev/null || true)
if [[ -n "$REMOTE_HASH" && "$REMOTE_HASH" = "$LOCAL_HASH" ]]; then
ACTIVE="unknown"
if command -v systemctl >/dev/null 2>&1; then
ACTIVE="$(systemctl is-active "${APP_NAME}.service" 2>/dev/null || echo unknown)"
fi
if [[ "$ACTIVE" == "active" ]]; then
say "Already running latest code ($(git rev-parse --short HEAD)) — skipping deploy."
send_admin "✅ Bot is already running the latest version (commit: $(git rev-parse --short HEAD))."
exit 0
else
warn "Code is up to date but service status is: $ACTIVE — continuing deploy to (re)start service."
send_admin "ℹ️ Code is up to date but service status is: $ACTIVE — continuing deploy to (re)start service."
fi
fi
# Resolve human-readable source label for notifications
case "$DEPLOY_SOURCE" in
github) SOURCE_LABEL="GitHub merge" ;;
bot) SOURCE_LABEL="/deploy command" ;;
*) SOURCE_LABEL="VPS manual restart" ;;
esac
send_admin "🔄 Deploy started (source: $SOURCE_LABEL)"
# ---------------------------------------------------------
# 1) Stop service before touching files (prevents file locks)
# ---------------------------------------------------------
if command -v systemctl >/dev/null 2>&1; then
say "Step 1/4 — Stopping ${APP_NAME} service…"
send_admin "🛑 Stopping bot service…"
systemctl stop "${APP_NAME}.service" || true
_SERVICE_STOPPED=true
fi
# ---------------------------------------------------------
# 2) Pull latest code from origin/main (hard reset)
# IMPORTANT: git operations are wrapped in a conditional so
# that if fetch or reset fails (network, auth, bad ref) we
# can restart the existing service before exiting — the
# service was already stopped above, so without this guard
# a git failure would leave the bot permanently down.
# ---------------------------------------------------------
say "Step 2/4 — Fetching latest code from origin/main…"
send_admin "📥 Pulling latest code…"
GIT_FETCH_OUT=""
GIT_RESET_OUT=""
if GIT_FETCH_OUT=$(git fetch --all 2>&1) \
&& GIT_RESET_OUT=$(git reset --hard origin/main 2>&1); then
git clean -fd
say "Now at: $(git rev-parse --short HEAD)"
else
# Capture whatever output we have for diagnostics
GIT_DIAG="fetch: ${GIT_FETCH_OUT} | reset: ${GIT_RESET_OUT}"
warn "Git fetch/reset failed — restarting existing service without new code"
warn "Git output: $GIT_DIAG"
send_admin "❌ Git update failed — restarted old code. Error: ${GIT_DIAG:0:200} (source: $DEPLOY_SOURCE)"
# Restart the service so the bot comes back up on the old code
if command -v systemctl >/dev/null 2>&1; then
systemctl start "${APP_NAME}.service" || true
_SERVICE_STOPPED=false
fi
exit 1
fi
# ---------------------------------------------------------
# 3) Install / update production dependencies
# Wrapped in a conditional identical to the git guard:
# if npm fails the service is left down without this guard.
# On failure we restart on the existing node_modules so
# the bot comes back up on whatever was last working.
# ---------------------------------------------------------
say "Step 3/4 — Installing production dependencies…"
send_admin "📦 Installing dependencies…"
if [[ -f package-lock.json ]]; then
NPM_CMD=(npm ci --omit=dev)
else
NPM_CMD=(npm install --omit=dev)
fi
NPM_OUT=""
if NPM_OUT=$("${NPM_CMD[@]}" 2>&1); then
say "Dependencies installed."
mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs"
touch "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log"
if id -u "$APP_NAME" >/dev/null 2>&1; then
chown -R "$APP_NAME:$APP_NAME" "$PROJECT_DIR/data" "$PROJECT_DIR/logs" || true
[[ -f "$PROJECT_DIR/.env" ]] && chown "$APP_NAME:$APP_NAME" "$PROJECT_DIR/.env" || true
fi
chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs" || true
chmod 0640 "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log" || true
[[ -f "$PROJECT_DIR/.env" ]] && chmod 0600 "$PROJECT_DIR/.env" || true
else
warn "npm install failed — restarting bot on existing node_modules"
warn "npm output: $NPM_OUT"
send_admin "❌ npm install failed — restarting bot on existing deps. Error: ${NPM_OUT:0:200} (source: $DEPLOY_SOURCE)"
if command -v systemctl >/dev/null 2>&1; then
systemctl start "${APP_NAME}.service" || true
_SERVICE_STOPPED=false
fi
exit 1
fi
# ---------------------------------------------------------
# 3b) Auto-run tooltip generation script (must run before bot restart)
# ---------------------------------------------------------
say "Step 3b — Refreshing Helpful Tooltips…"
TOOLTIP_SCRIPT="$PROJECT_DIR/generate_tooltips.sh"
if [[ -x "$TOOLTIP_SCRIPT" ]]; then
if TOOLTIP_OUT=$(RUNEWAGER_DIR="$PROJECT_DIR" bash "$TOOLTIP_SCRIPT" 2>&1); then
say "Helpful tooltips refreshed."
else
warn "generate_tooltips.sh failed (non-fatal): $TOOLTIP_OUT"
# DM admin but continue deploy
send_admin "⚠️ generate_tooltips.sh failed during deploy: ${TOOLTIP_OUT:0:200} — continuing deploy."
fi
else
warn "generate_tooltips.sh not found or not executable at $TOOLTIP_SCRIPT — skipping tooltip refresh."
send_admin "⚠️ generate_tooltips.sh missing at $TOOLTIP_SCRIPT — tooltips not refreshed."
fi
# ---------------------------------------------------------
# 3c) Kill anything blocking the bot port before starting
# ---------------------------------------------------------
DEPLOY_PORT="$(grep -E '^PORT=' "$PROJECT_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2 | cut -d'#' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | tr -d $'\r')"
DEPLOY_PORT="${DEPLOY_PORT:-3000}"
# shellcheck source=scripts/helpers/free_port.sh
. "$PROJECT_DIR/scripts/helpers/free_port.sh"
free_port "$DEPLOY_PORT" "${APP_NAME}.service"
# ---------------------------------------------------------
# 4) Start bot via systemctl
# ---------------------------------------------------------
say "Step 4/4 — Starting bot via systemctl…"
send_admin "🚀 Starting bot service…"
if command -v systemctl >/dev/null 2>&1; then
systemctl start "${APP_NAME}.service"
_SERVICE_STOPPED=false
say "Bot started."
else
warn "systemctl not found. Start manually: node $PROJECT_DIR/index.js"
fi
# ---------------------------------------------------------
# 5) Confirm service status
# ---------------------------------------------------------
sleep 3
ACTIVE="$(systemctl is-active "${APP_NAME}.service" 2>/dev/null || echo unknown)"
say "Service status: $ACTIVE"
if [[ "$ACTIVE" == "active" ]]; then
send_admin "✅ Deploy complete — bot running latest version (commit: $(git rev-parse --short HEAD))."
else
send_admin "⚠️ Deploy finished but service status is: $ACTIVE — check VPS logs."
fi
say ""
say "=== Deployment complete. ==="