diff --git a/deploy.sh b/deploy.sh index aa33a6a..c08825d 100755 --- a/deploy.sh +++ b/deploy.sh @@ -189,8 +189,17 @@ NPM_CMD="npm install --omit=dev" NPM_OUT="" if NPM_OUT=$(${NPM_CMD} 2>&1); then say "Dependencies installed." - mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/logs" + 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" diff --git a/prod-run.sh b/prod-run.sh index 17fde95..6626c58 100755 --- a/prod-run.sh +++ b/prod-run.sh @@ -27,11 +27,39 @@ ADMIN_EVENTS_LOG="$PROJECT_DIR/data/admin-events.log" SSHV_SESSIONS_FILE="$PROJECT_DIR/data/sshv-sessions.json" SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service" LOGROTATE_FILE="/etc/logrotate.d/${APP_NAME}" +HEALTH_TLS_INSECURE="${HEALTH_TLS_INSECURE:-0}" say() { printf '[%s] %s\n' "$APP_NAME" "$*"; } warn() { printf '[%s][warn] %s\n' "$APP_NAME" "$*" >&2; } err() { printf '[%s][error] %s\n' "$APP_NAME" "$*" >&2; } + +SERVICE_USER="$APP_NAME" +SERVICE_GROUP="$APP_NAME" + +ensure_service_user() { + if id -u "$APP_NAME" >/dev/null 2>&1; then + return 0 + fi + + if [[ "$(id -u)" -ne 0 ]]; then + SERVICE_USER="$(id -un)" + SERVICE_GROUP="$(id -gn)" + warn "System user '$APP_NAME' not found and current user is not root — using ${SERVICE_USER}:${SERVICE_GROUP}" + return 1 + fi + + if ! getent group "$APP_NAME" >/dev/null 2>&1; then + groupadd --system "$APP_NAME" + fi + + if ! id -u "$APP_NAME" >/dev/null 2>&1; then + useradd --system --gid "$APP_NAME" --home-dir "$PROJECT_DIR" --shell /usr/sbin/nologin "$APP_NAME" + fi + + say "Created system user/group '$APP_NAME' for service runtime" +} + read_env_value() { local key="$1" [[ -f "$PROJECT_DIR/.env" ]] || return 1 @@ -56,7 +84,12 @@ check_health_endpoint() { local url="$1" local code if [[ "$url" == https://* ]]; then - code="$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)" + if [[ "$HEALTH_TLS_INSECURE" == "1" ]]; then + warn "HEALTH_TLS_INSECURE=1 set; skipping TLS cert verification for health probe" + code="$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)" + else + code="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)" + fi else code="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)" fi @@ -79,17 +112,32 @@ wait_for_health() { is_port_listening() { local port="$1" + local port_hex if command -v ss >/dev/null 2>&1; then - ss -ltn "sport = :${port}" 2>/dev/null | awk 'NR>1 {found=1} END {exit(found ? 0 : 1)}' - else - return 1 + ss -ltn "sport = :${port}" 2>/dev/null | awk 'NR>1 {found=1} END {exit(found ? 0 : 1)}' && return 0 fi + + if command -v netstat >/dev/null 2>&1; then + netstat -ltn 2>/dev/null | awk -v p=":${port}" 'index($4,p){found=1} END{exit(found?0:1)}' && return 0 + fi + + if [[ -r /proc/net/tcp ]]; then + port_hex="$(printf '%04X' "$port" | tr '[:lower:]' '[:upper:]')" + awk -v p=":${port_hex}" 'NR>1 && index($2,p)==(length($2)-4) {found=1} END{exit(found?0:1)}' /proc/net/tcp && return 0 + fi + + return 1 } say "Starting production runner" say "Project directory: $PROJECT_DIR" say "Running as: $(id -un) (uid=$(id -u) gid=$(id -g))" +ensure_service_user || true +if [[ "$SERVICE_USER" != "$APP_NAME" ]]; then + warn "Using fallback service identity ${SERVICE_USER}:${SERVICE_GROUP}; ownership/service behavior may differ from root-managed installs" +fi + # --------------------------------------------------------- # 1) Git fetch + reset say "Fetching latest code from origin main..." @@ -110,13 +158,11 @@ mkdir -p "$PROJECT_DIR/data" mkdir -p "$PROJECT_DIR/data/backups" touch "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE" # create if missing; safe on existing files -if id -u "$APP_NAME" >/dev/null 2>&1; then - chown -R "$APP_NAME:$APP_NAME" "$LOG_DIR" "$PROJECT_DIR/data" - chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$LOG_DIR" - chmod 0640 "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE" -else - warn "System user '$APP_NAME' not found — using $(id -un):$(id -gn)" +if id -u "$SERVICE_USER" >/dev/null 2>&1; then + chown -R "$SERVICE_USER:$SERVICE_GROUP" "$LOG_DIR" "$PROJECT_DIR/data" fi +chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$LOG_DIR" +chmod 0640 "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE" say "Ensured runtime directories and log files exist" @@ -157,6 +203,14 @@ if [[ -f "$PROJECT_DIR/.env" ]]; then fi fi +# Lock down .env permissions (contains secrets) +if [[ -f "$PROJECT_DIR/.env" ]]; then + chmod 0600 "$PROJECT_DIR/.env" || true + if id -u "$SERVICE_USER" >/dev/null 2>&1; then + chown "$SERVICE_USER:$SERVICE_GROUP" "$PROJECT_DIR/.env" || true + fi +fi + # --------------------------------------------------------- # 6) Production dependency install say "Installing/updating production dependencies..." @@ -176,12 +230,10 @@ PID="$(get_bot_pid)" ensure_systemd_service() { local node_bin svc_user svc_group conf node_bin="$(command -v node || echo /usr/bin/node)" - if id -u "$APP_NAME" >/dev/null 2>&1; then - svc_user="$APP_NAME"; svc_group="$APP_NAME" - else - svc_user="$(id -un)"; svc_group="$(id -gn)" - warn "System user '$APP_NAME' not found — using $svc_user:$svc_group" - fi + + local svc_user svc_group + svc_user="$SERVICE_USER" + svc_group="$SERVICE_GROUP" conf=$(cat </dev/null || id -u)" + gid_num="$(id -g "$SERVICE_GROUP" 2>/dev/null || id -g)" + + local conf conf=$(cat </dev/null || echo inactive)" +HEALTH_URL="$(resolve_health_url)" +HEALTH_PORT="${HEALTH_URL#*://127.0.0.1:}" +HEALTH_PORT="${HEALTH_PORT%%/*}" + +if wait_for_health "$HEALTH_URL" 20 2; then + HEALTH_STATUS="healthy" +else + HEALTH_STATUS="unhealthy" +fi + +if is_port_listening "$HEALTH_PORT"; then + PORT_STATUS="listening" +else + PORT_STATUS="not-listening" +fi + say "✔ Systemd service: ${SYSTEMD_ACTIVE}" say "✔ Health URL: ${HEALTH_URL}" say "✔ Health endpoint: ${HEALTH_STATUS}" diff --git a/runewager.service b/runewager.service index 031386b..ab0cc19 100644 --- a/runewager.service +++ b/runewager.service @@ -48,6 +48,7 @@ ProtectSystem=strict ProtectHome=true PrivateTmp=true NoNewPrivileges=true +UMask=0077 # File descriptor limit (for concurrent connections) LimitNOFILE=65536 diff --git a/scripts/self-diagnose.sh b/scripts/self-diagnose.sh index 07f5208..170a380 100755 --- a/scripts/self-diagnose.sh +++ b/scripts/self-diagnose.sh @@ -1,28 +1,77 @@ #!/usr/bin/env bash # ============================================================================= -# self-diagnose.sh — Full system diagnostic. Can be called from CI or manually. -# -# Runs all available checks and prints a comprehensive status report. -# Does NOT abort on failure — reports everything and exits with the summary. +# self-diagnose.sh — Full system diagnostic snapshot. +# Captures state first, then prints one internally consistent report. +# Safe/read-only: does not restart services or modify system files. # ============================================================================= set -euo pipefail -# Resolve project root: prefer the script's own parent directory so this works -# regardless of where it is called from. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_DIR="${BASE_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}" CURRENT_DIR="$BASE_DIR" PORT="${PORT:-3000}" -# Load .env (best-effort) if [[ -f "${CURRENT_DIR}/.env" ]]; then - set -o allexport; source "${CURRENT_DIR}/.env" 2>/dev/null || true; set +o allexport + set -o allexport + # shellcheck disable=SC1090 + source "${CURRENT_DIR}/.env" 2>/dev/null || true + set +o allexport fi -PASS=0; FAIL=0; WARN=0 -ok() { echo " ✅ $*"; PASS=$((PASS+1)); } -fail() { echo " ❌ $*"; FAIL=$((FAIL+1)); } -warn() { echo " ⚠️ $*"; WARN=$((WARN+1)); } +PASS=0 +WARN=0 +FAIL=0 + +SYSTEM_LINES=() +FILES_LINES=() +ENV_LINES=() +SERVICE_LINES=() +PROCESS_LINES=() +NETWORK_LINES=() +RELEASE_LINES=() +LOG_LINES=() +SNAPSHOT_LINES=() + +RENDERED_LINE="" + +add_result() { + local level="$1" + local msg="$2" + case "$level" in + ok) + printf -v RENDERED_LINE ' ✅ %s' "$msg" + PASS=$((PASS + 1)) + ;; + warn) + printf -v RENDERED_LINE ' ⚠️ %s' "$msg" + WARN=$((WARN + 1)) + ;; + fail) + printf -v RENDERED_LINE ' ❌ %s' "$msg" + FAIL=$((FAIL + 1)) + ;; + *) + printf -v RENDERED_LINE ' • %s' "$msg" + ;; + esac +} + +append_line() { + local section="$1" + local level="$2" + local msg="$3" + add_result "$level" "$msg" + case "$section" in + system) SYSTEM_LINES+=("$RENDERED_LINE") ;; + files) FILES_LINES+=("$RENDERED_LINE") ;; + env) ENV_LINES+=("$RENDERED_LINE") ;; + service) SERVICE_LINES+=("$RENDERED_LINE") ;; + process) PROCESS_LINES+=("$RENDERED_LINE") ;; + network) NETWORK_LINES+=("$RENDERED_LINE") ;; + releases) RELEASE_LINES+=("$RENDERED_LINE") ;; + snapshot) SNAPSHOT_LINES+=("$RENDERED_LINE") ;; + esac +} banner() { echo "" @@ -31,105 +80,224 @@ banner() { echo "────────────────────────────────────────────" } -echo "════════════════════════════════════════════" -echo " RUNEWAGER BOT — SELF-DIAGNOSE REPORT" -echo " Generated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" -echo "════════════════════════════════════════════" +get_pid_start_ticks() { + local pid="$1" + [[ -n "$pid" ]] || return 1 + [[ -r "/proc/${pid}/stat" ]] || return 1 + awk '{print $22}' "/proc/${pid}/stat" 2>/dev/null +} -# ── System ──────────────────────────────────────────────────────────────────── -banner "1. System" -NODE_VER=$(node --version 2>/dev/null || echo "not found") -NODE_MAJOR=$(echo "$NODE_VER" | sed 's/v\([0-9]*\).*/\1/' 2>/dev/null || echo 0) -if [[ "$NODE_MAJOR" -ge 20 ]]; then ok "Node $NODE_VER"; else fail "Node $NODE_VER (need >= 20)"; fi -NPM_VER=$(npm --version 2>/dev/null || echo "not found") -ok "npm $NPM_VER" -OS_INFO=$(uname -srm 2>/dev/null || echo "unknown") -ok "OS: $OS_INFO" -DISK_AVAIL=$(df -BM "${CURRENT_DIR}" 2>/dev/null | awk 'NR==2 {gsub(/M/,"",$4); print $4}' || echo 0) -if [[ "$DISK_AVAIL" -gt 500 ]]; then ok "Disk: ${DISK_AVAIL}MB free" -elif [[ "$DISK_AVAIL" -gt 100 ]]; then warn "Disk: ${DISK_AVAIL}MB free (low)" -else fail "Disk: ${DISK_AVAIL}MB free (critical)"; fi -MEM_FREE=$(awk '/MemAvailable/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null || echo 0) -if [[ "$MEM_FREE" -gt 100 ]]; then ok "Memory: ${MEM_FREE}MB free" -elif [[ "$MEM_FREE" -gt 50 ]]; then warn "Memory: ${MEM_FREE}MB free (low)" -else fail "Memory: ${MEM_FREE}MB free (critical)"; fi -CPU_LOAD=$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo "?") -ok "CPU load (1m): $CPU_LOAD" - -# ── Files ───────────────────────────────────────────────────────────────────── -banner "2. Files" +get_bot_pid() { + pgrep -f "node.*${CURRENT_DIR}/index\\.js" | head -n 1 || true +} + +# -------- Snapshot capture begins (no output beyond this point until finished) -------- +SNAPSHOT_TS="$(date -u '+%Y-%m-%d %H:%M:%S UTC')" + +PID_BEFORE="$(get_bot_pid)" +PID_BEFORE_TICKS="$(get_pid_start_ticks "$PID_BEFORE" || true)" + +# 1) System +NODE_VER="$(node --version 2>/dev/null || echo 'not found')" +NODE_MAJOR="$(echo "$NODE_VER" | sed 's/v\([0-9]*\).*/\1/' 2>/dev/null || echo 0)" +if [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] && [[ "$NODE_MAJOR" -ge 20 ]]; then + append_line system ok "Node $NODE_VER" +else + append_line system fail "Node $NODE_VER (need >= 20)" +fi + +NPM_VER="$(npm --version 2>/dev/null || echo 'not found')" +append_line system ok "npm $NPM_VER" + +OS_INFO="$(uname -srm 2>/dev/null || echo 'unknown')" +append_line system ok "OS: $OS_INFO" + +DISK_AVAIL="$(df -BM "${CURRENT_DIR}" 2>/dev/null | awk 'NR==2 {gsub(/M/,"",$4); print $4}' || echo 0)" +if [[ "$DISK_AVAIL" =~ ^[0-9]+$ ]] && [[ "$DISK_AVAIL" -gt 500 ]]; then + append_line system ok "Disk: ${DISK_AVAIL}MB free" +elif [[ "$DISK_AVAIL" =~ ^[0-9]+$ ]] && [[ "$DISK_AVAIL" -gt 100 ]]; then + append_line system warn "Disk: ${DISK_AVAIL}MB free (low)" +else + append_line system fail "Disk: ${DISK_AVAIL}MB free (critical)" +fi + +MEM_FREE="$(awk '/MemAvailable/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null || echo 0)" +if [[ "$MEM_FREE" =~ ^[0-9]+$ ]] && [[ "$MEM_FREE" -gt 100 ]]; then + append_line system ok "Memory: ${MEM_FREE}MB free" +elif [[ "$MEM_FREE" =~ ^[0-9]+$ ]] && [[ "$MEM_FREE" -gt 50 ]]; then + append_line system warn "Memory: ${MEM_FREE}MB free (low)" +else + append_line system fail "Memory: ${MEM_FREE}MB free (critical)" +fi + +CPU_LOAD="$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo '?')" +append_line system ok "CPU load (1m): $CPU_LOAD" + +# 2) Files for f in index.js package.json .env; do - if [[ -f "${CURRENT_DIR}/${f}" ]]; then ok "${f} present" - else fail "${f} MISSING"; fi + if [[ -f "${CURRENT_DIR}/${f}" ]]; then + append_line files ok "${f} present" + else + append_line files fail "${f} MISSING" + fi done -if [[ -d "${CURRENT_DIR}/node_modules/telegraf" ]]; then ok "telegraf installed" -else fail "telegraf not installed"; fi -if [[ -d "${CURRENT_DIR}/data" ]]; then ok "data/ directory exists" -else warn "data/ missing (will be created on start)"; fi -if [[ -d "${CURRENT_DIR}/logs" ]]; then ok "logs/ directory exists" -else warn "logs/ missing (will be created on start)"; fi - -# ── Environment ─────────────────────────────────────────────────────────────── -banner "3. Environment" + +if [[ -d "${CURRENT_DIR}/node_modules/telegraf" ]]; then + append_line files ok "telegraf installed" +else + append_line files fail "telegraf not installed" +fi + +if [[ -d "${CURRENT_DIR}/data" ]]; then + append_line files ok "data/ directory exists" +else + append_line files warn "data/ missing (will be created on start)" +fi + +if [[ -d "${CURRENT_DIR}/logs" ]]; then + append_line files ok "logs/ directory exists" +else + append_line files warn "logs/ missing (will be created on start)" +fi + +# 3) Environment for var in BOT_TOKEN ADMIN_IDS PORT MINI_APP_PLAY_URL AFFILIATE_SOURCE; do val="${!var:-}" if [[ -n "$val" ]]; then [[ "$var" == "BOT_TOKEN" ]] && val="[hidden]" - ok "$var = $val" - else fail "$var is not set"; fi + append_line env ok "$var = $val" + else + append_line env fail "$var is not set" + fi done -# ── Service ─────────────────────────────────────────────────────────────────── -banner "4. Systemd Service" -SVC_STATUS=$(systemctl is-active runewager.service 2>/dev/null || echo "inactive") -if [[ "$SVC_STATUS" == "active" ]]; then ok "runewager.service active" -else fail "runewager.service: $SVC_STATUS"; fi -SVC_ENABLED=$(systemctl is-enabled runewager.service 2>/dev/null || echo "disabled") -if [[ "$SVC_ENABLED" == "enabled" ]]; then ok "Service enabled (auto-start)" -else warn "Service not enabled ($SVC_ENABLED)"; fi - -# ── Process ─────────────────────────────────────────────────────────────────── -banner "5. Bot Process" -BOT_PID=$(pgrep -f "node.*${CURRENT_DIR}/index\.js" | head -n 1 || true) +# 4) Service +SVC_STATUS="$(systemctl is-active runewager.service 2>/dev/null || echo inactive)" +if [[ "$SVC_STATUS" == "active" ]]; then + append_line service ok "runewager.service active" +else + append_line service fail "runewager.service: $SVC_STATUS" +fi + +SVC_ENABLED="$(systemctl is-enabled runewager.service 2>/dev/null || echo disabled)" +if [[ "$SVC_ENABLED" == "enabled" ]]; then + append_line service ok "Service enabled (auto-start)" +else + append_line service warn "Service not enabled ($SVC_ENABLED)" +fi + +# 5) Process +BOT_PID="$PID_BEFORE" if [[ -n "$BOT_PID" ]]; then - ok "Bot running (PID: $BOT_PID)" - BOT_MEM=$(awk '/VmRSS/ {printf "%d", $2/1024}' /proc/${BOT_PID}/status 2>/dev/null || echo "?") - ok "Bot memory: ${BOT_MEM}MB RSS" -else warn "Bot process not found (service may be restarting)"; fi + append_line process ok "Bot running (PID: $BOT_PID)" + BOT_MEM="$(awk '/VmRSS/ {printf "%d", $2/1024}' "/proc/${BOT_PID}/status" 2>/dev/null || echo '?')" + append_line process ok "Bot memory: ${BOT_MEM}MB RSS" +else + append_line process warn "Bot process not found (service may be restarting)" +fi + +# 6) Network +HEALTH_CODE="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:${PORT}/health" 2>/dev/null || echo 000)" +if [[ "$HEALTH_CODE" == "200" ]]; then + append_line network ok "Health endpoint OK (HTTP 200)" +else + append_line network fail "Health endpoint HTTP $HEALTH_CODE" +fi + +STATUS_CODE="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:${PORT}/status" 2>/dev/null || echo 000)" +if [[ "$STATUS_CODE" == "200" ]]; then + append_line network ok "Status endpoint OK (HTTP 200)" +else + append_line network warn "Status endpoint HTTP $STATUS_CODE" +fi + +TG_CODE="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://api.telegram.org" 2>/dev/null || echo 000)" +if [[ "$TG_CODE" =~ ^[0-9]+$ ]] && [[ "$TG_CODE" -ge 200 ]] && [[ "$TG_CODE" -lt 500 ]]; then + append_line network ok "Telegram API reachable (HTTP $TG_CODE)" +else + append_line network fail "Telegram API HTTP $TG_CODE" +fi + +if ss -tln 2>/dev/null | grep -q ":${PORT} "; then + append_line network ok "Port $PORT listening" +elif command -v netstat >/dev/null 2>&1 && netstat -tln 2>/dev/null | grep -q ":${PORT} "; then + append_line network ok "Port $PORT listening" +else + append_line network fail "Port $PORT not listening" +fi + +# 7) Releases +RELEASE_LIST="$(find "${BASE_DIR}/releases" -maxdepth 1 -type d -name 'release_*' 2>/dev/null || true)" +RELEASE_COUNT="$(printf '%s\n' "$RELEASE_LIST" | sed '/^$/d' | wc -l | tr -d ' ')" +append_line releases ok "$RELEASE_COUNT release(s) available in ${BASE_DIR}/releases/" + +LATEST_RELEASE_PATH="$(printf '%s\n' "$RELEASE_LIST" | sed '/^$/d' | sort | tail -n 1)" +if [[ -n "$LATEST_RELEASE_PATH" ]]; then + append_line releases ok "Latest release: $(basename "$LATEST_RELEASE_PATH")" +else + append_line releases ok "Latest release: none" +fi -# ── Network ─────────────────────────────────────────────────────────────────── -banner "6. Network" -HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:${PORT}/health" 2>/dev/null || echo "000") -if [[ "$HTTP_CODE" == "200" ]]; then ok "Health endpoint OK (HTTP 200)" -else fail "Health endpoint HTTP $HTTP_CODE"; fi -STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://127.0.0.1:${PORT}/status" 2>/dev/null || echo "000") -if [[ "$STATUS_CODE" == "200" ]]; then ok "Status endpoint OK (HTTP 200)" -else warn "Status endpoint HTTP $STATUS_CODE"; fi -TG_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://api.telegram.org" 2>/dev/null || echo "000") -if [[ "$TG_CODE" -ge 200 ]] && [[ "$TG_CODE" -lt 500 ]]; then ok "Telegram API reachable (HTTP $TG_CODE)" -else fail "Telegram API HTTP $TG_CODE"; fi -if ss -tlnp 2>/dev/null | grep -q ":${PORT} " || netstat -tlnp 2>/dev/null | grep -q ":${PORT} "; then - ok "Port $PORT listening" -else fail "Port $PORT not listening"; fi - -# ── Releases ────────────────────────────────────────────────────────────────── -banner "7. Releases" -RELEASE_COUNT=$(ls -1d "${BASE_DIR}/releases"/release_* 2>/dev/null | wc -l || echo 0) -ok "$RELEASE_COUNT release(s) available in ${BASE_DIR}/releases/" -LATEST_RELEASE=$(ls -1dt "${BASE_DIR}/releases"/release_* 2>/dev/null | head -n 1 || echo "none") -ok "Latest release: $(basename "$LATEST_RELEASE")" if [[ -f "${CURRENT_DIR}/.last_rollback" ]]; then - warn "Last rollback: $(cat "${CURRENT_DIR}/.last_rollback")" + append_line releases warn "Last rollback: $(cat "${CURRENT_DIR}/.last_rollback")" fi -# ── Logs ────────────────────────────────────────────────────────────────────── -banner "8. Recent Logs (last 5 lines)" +# 8) Logs snapshot LOG_FILE="${CURRENT_DIR}/logs/runewager.log" if [[ -f "$LOG_FILE" ]]; then - tail -n 5 "$LOG_FILE" | while IFS= read -r line; do echo " $line"; done -else warn "Log file not found: $LOG_FILE"; fi + while IFS= read -r line; do + LOG_LINES+=(" $line") + done < <(tail -n 5 "$LOG_FILE" 2>/dev/null || true) +else + add_result warn "Log file not found: $LOG_FILE" + LOG_LINES+=("$RENDERED_LINE") +fi + +# Restart/change detection at end of capture window +PID_AFTER="$(get_bot_pid)" +PID_AFTER_TICKS="$(get_pid_start_ticks "$PID_AFTER" || true)" + +if [[ "$PID_BEFORE" != "$PID_AFTER" ]]; then + append_line snapshot warn "Bot PID changed during snapshot (${PID_BEFORE:-none} -> ${PID_AFTER:-none})" +elif [[ -n "$PID_BEFORE" ]] && [[ -n "$PID_AFTER" ]] && [[ "$PID_BEFORE_TICKS" != "$PID_AFTER_TICKS" ]]; then + append_line snapshot warn "Bot process restarted during snapshot (same PID reused)" +else + append_line snapshot ok "No bot restart detected during snapshot" +fi + +# -------- Printing begins (no checks beyond this point) -------- +echo "════════════════════════════════════════════" +echo " RUNEWAGER BOT — SELF-DIAGNOSE REPORT" +echo " Snapshot: $SNAPSHOT_TS" +echo "════════════════════════════════════════════" + +banner "0. Snapshot Integrity" +printf '%s\n' "${SNAPSHOT_LINES[@]}" + +banner "1. System" +printf '%s\n' "${SYSTEM_LINES[@]}" + +banner "2. Files" +printf '%s\n' "${FILES_LINES[@]}" + +banner "3. Environment" +printf '%s\n' "${ENV_LINES[@]}" + +banner "4. Systemd Service" +printf '%s\n' "${SERVICE_LINES[@]}" + +banner "5. Bot Process" +printf '%s\n' "${PROCESS_LINES[@]}" + +banner "6. Network" +printf '%s\n' "${NETWORK_LINES[@]}" + +banner "7. Releases" +printf '%s\n' "${RELEASE_LINES[@]}" + +banner "8. Recent Logs (last 5 lines)" +printf '%s\n' "${LOG_LINES[@]}" -# ── Summary ─────────────────────────────────────────────────────────────────── echo "" echo "════════════════════════════════════════════" echo " SUMMARY" @@ -139,7 +307,7 @@ echo " ⚠️ Warnings: $WARN" echo " ❌ Failed: $FAIL" echo "" if [[ $FAIL -gt 0 ]]; then - echo " ❌ System has issues. Check the failures above." + echo " ❌ System has issues. Check failures above." exit 1 elif [[ $WARN -gt 0 ]]; then echo " ⚠️ System OK with warnings."