From 030c2307c22a9cd689956e44dd05dd145410629a Mon Sep 17 00:00:00 2001 From: Docker Build Date: Sun, 10 May 2026 15:19:59 -0500 Subject: [PATCH] scripts: add vps-paragon-diagnostics.sh for native VPS triage Co-authored-by: Cursor --- scripts/vps-paragon-diagnostics.sh | 240 +++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100755 scripts/vps-paragon-diagnostics.sh diff --git a/scripts/vps-paragon-diagnostics.sh b/scripts/vps-paragon-diagnostics.sh new file mode 100755 index 0000000..8ce567b --- /dev/null +++ b/scripts/vps-paragon-diagnostics.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +# Collect VPS evidence for Paragon / DBUpdater / binary staleness triage. +# Run ON the VPS (Linux). Safe: read-only; does not restart services. +# +# Usage (from clone): +# bash scripts/vps-paragon-diagnostics.sh +# +# Optional environment: +# FRACTURED_REPO — absolute path to Fractured git root (default: parent of scripts/) +# FRACTURED_WS_BIN — path to worldserver binary (default: auto-detect) +# FRACTURED_WORLDSERVER_CONF — path to worldserver.conf (default: guess from BIN + common layouts) +# FRACTURED_SYSTEMD_UNITS — space-separated units to try (default: "fractured-world worldserver ac-worldserver") +# FRACTURED_MYSQL — prefix to invoke mysql, e.g. 'mysql -uacore -h127.0.0.1' (password via ~/.my.cnf or -p) +# If unset, SQL blocks are printed for manual copy-paste only. +# +# Paste the full script output to your maintainer (redact any paths/passwords you consider sensitive). + +set -u + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="${FRACTURED_REPO:-$(cd "$SCRIPT_DIR/.." && pwd)}" + +hr() { printf '\n%s\n' "================================================================================"; } +sub() { printf '\n-- %s\n' "$1"; } + +detect_worldserver_bin() { + local bin="" es path u units + if [[ -n "${FRACTURED_WS_BIN:-}" ]]; then + readlink -f "$FRACTURED_WS_BIN" 2>/dev/null && return + echo "$FRACTURED_WS_BIN" + return + fi + + units="${FRACTURED_SYSTEMD_UNITS:-fractured-world worldserver ac-worldserver}" + for u in $units; do + if systemctl is-active --quiet "$u" 2>/dev/null || systemctl is-enabled --quiet "$u" 2>/dev/null; then + es=$(systemctl show "$u" -p ExecStart --value 2>/dev/null || true) + if [[ -n "$es" ]]; then + if [[ "$es" == \{*path=* ]]; then + path=$(printf '%s' "$es" | sed -n 's/.*path=\([^;]*\).*/\1/p') + else + path=$(printf '%s' "$es" | awk '{print $1}' | sed 's/^path=//') + fi + if [[ -n "$path" && -x "$path" ]]; then + readlink -f "$path" 2>/dev/null && return + fi + fi + fi + done + + local pid + pid=$(pgrep -xo worldserver 2>/dev/null || true) + if [[ -n "$pid" ]]; then + readlink -f "/proc/$pid/exe" 2>/dev/null && return + fi + + if command -v worldserver >/dev/null 2>&1; then + readlink -f "$(command -v worldserver)" 2>/dev/null && return + fi + + echo "" +} + +guess_worldserver_conf() { + local bin="$1" + local d cands=() + [[ -z "$bin" ]] && return + d=$(dirname "$bin") + cands+=("$d/../etc/worldserver.conf") + cands+=("$d/../../etc/worldserver.conf") + cands+=("$HOME/azeroth-server/etc/worldserver.conf") + cands+=("$HOME/env/dist/etc/worldserver.conf") + for f in "${cands[@]}"; do + f=$(readlink -f "$f" 2>/dev/null || true) + if [[ -n "$f" && -f "$f" ]]; then + echo "$f" + return + fi + done + echo "" +} + +binary_strings_paths() { + local ws="$1" + [[ -z "$ws" || ! -f "$ws" ]] && return + strings "$ws" 2>/dev/null | grep -iE '/(home|root|opt|srv|var)[^[:space:]]*/(Fractured|fractured|azeroth|AzerothCore|acore)' | sort -u | head -40 +} + +hr +echo "Fractured Paragon / native VPS diagnostics" +echo "Date (UTC): $(date -u '+%Y-%m-%d %H:%M:%S UTC')" +echo "Repo (expected): $REPO" + +sub "1A — worldserver binary" +WS=$(detect_worldserver_bin || true) +if [[ -z "$WS" ]]; then + echo "ERROR: Could not find worldserver. Set FRACTURED_WS_BIN=/full/path/to/worldserver and re-run." +else + echo "Binary: $WS" + if stat -c 'binary mtime: %y' "$WS" 2>/dev/null; then + : + else + stat -f 'binary mtime: %Sm' -t '%Y-%m-%d %H:%M:%S %z' "$WS" 2>/dev/null || stat "$WS" + fi +fi + +sub "1B — repo HEAD + Paragon_Essence.cpp mtime" +if [[ -d "$REPO/.git" ]]; then + (cd "$REPO" && git log -1 --format='HEAD commit: %h %ci %s') +else + echo "WARN: not a git repo: $REPO (set FRACTURED_REPO)" +fi +PE="$REPO/modules/mod-paragon/src/Paragon_Essence.cpp" +if [[ -f "$PE" ]]; then + if stat -c 'Paragon_Essence.cpp mtime: %y' "$PE" 2>/dev/null; then + : + else + stat -f 'Paragon_Essence.cpp mtime: %Sm' -t '%Y-%m-%d %H:%M:%S %z' "$PE" 2>/dev/null || stat "$PE" + fi +else + echo "WARN: missing $PE" +fi + +sub "1C — strings heuristics (0 can mean stripped binary — use 1A+1B)" +if [[ -n "$WS" && -f "$WS" ]]; then + c1=$(strings "$WS" 2>/dev/null | grep -c 'CLASS_PARAGON' || true) + c2=$(strings "$WS" 2>/dev/null | grep -c 'C BUILD SAVE_CURRENT' || true) + c3=$(strings "$WS" 2>/dev/null | grep -c 'character_paragon_build_share_archive' || true) + echo "CLASS_PARAGON count: $c1" + echo "C BUILD SAVE_CURRENT count: $c2" + echo "character_paragon_build_share_archive count: $c3" +else + echo "(skipped — no binary)" +fi + +CONF="${FRACTURED_WORLDSERVER_CONF:-}" +if [[ -z "$CONF" && -n "$WS" ]]; then + CONF=$(guess_worldserver_conf "$WS") +fi + +sub "2B — worldserver.conf updater / source (first match)" +if [[ -n "$CONF" && -f "$CONF" ]]; then + echo "Using conf: $CONF" + grep -E '^SourceDirectory|^Updates\.EnableDatabases|^Updates\.AutoSetup|^[[:space:]]*SourceDirectory|^[[:space:]]*Updates\.EnableDatabases|^[[:space:]]*Updates\.AutoSetup' "$CONF" 2>/dev/null || echo "(no matching lines or unreadable)" +else + echo "WARN: worldserver.conf not found. Set FRACTURED_WORLDSERVER_CONF=/path/to/worldserver.conf" +fi + +sub "2A — path-like strings from binary (candidate source roots)" +if [[ -n "$WS" && -f "$WS" ]]; then + binary_strings_paths "$WS" || true +else + echo "(skipped)" +fi + +sub "Resolved source root for 2D" +RESOLVED="" +if [[ -n "$CONF" && -f "$CONF" ]]; then + sd=$(awk -F= '/^[[:space:]]*SourceDirectory[[:space:]]*=/ { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); + gsub(/^["'\'']|["'\'']$/, "", $2); + print $2; exit }' "$CONF" 2>/dev/null || true) + if [[ -n "${sd:-}" ]]; then + RESOLVED="$sd" + fi +fi +if [[ -z "$RESOLVED" ]]; then + RESOLVED="$REPO" +fi +echo "Using RESOLVED=$RESOLVED (from SourceDirectory if set in conf, else FRACTURED_REPO)" + +sub "2D — Paragon SQL dirs under RESOLVED" +for subdir in \ + "$RESOLVED/modules/mod-paragon/data/sql/db-world/updates/" \ + "$RESOLVED/modules/mod-paragon/data/sql/db-characters/updates/"; do + if [[ -d "$subdir" ]]; then + echo "Listing: $subdir" + ls -la "$subdir" 2>/dev/null | tail -15 + else + echo "MISSING: $subdir" + fi +done + +sub "CMake build dir hints (common Fractured layouts)" +for cand in "$REPO/var/build/obj" "$REPO/build" "$REPO/../build"; do + if [[ -f "$cand/CMakeCache.txt" ]]; then + echo "Found CMakeCache: $cand/CMakeCache.txt" + grep -E '^CMAKE_HOME_DIRECTORY:|^MODULES:|^CMAKE_INSTALL_PREFIX:' "$cand/CMakeCache.txt" 2>/dev/null | head -5 + fi +done + +sub "DATABASE — updates rows (2026_05_10 / paragon)" +SQL_WORLD=$(cat <<'EOS' +SELECT name, hash, speed FROM updates + WHERE name LIKE '2026_05_10%' OR name LIKE '%paragon%' + ORDER BY name DESC LIMIT 30; +EOS +) +SQL_CHAR="$SQL_WORLD" + +if [[ -n "${FRACTURED_MYSQL:-}" ]]; then + echo "--- acore_world ---" + $FRACTURED_MYSQL acore_world -e "$SQL_WORLD" || echo "(mysql failed for acore_world)" + echo "--- acore_characters ---" + $FRACTURED_MYSQL acore_characters -e "$SQL_CHAR" || echo "(mysql failed for acore_characters)" +else + echo "FRACTURED_MYSQL not set — run manually (example: export FRACTURED_MYSQL='mysql -uUSER -hHOST')" + echo "acore_world:" + echo "$SQL_WORLD" + echo "acore_characters:" + echo "$SQL_CHAR" +fi + +sub "mod_paragon.conf vs .dist (install etc)" +ETC="" +if [[ -n "$WS" ]]; then + ETC=$(readlink -f "$(dirname "$WS")/../etc" 2>/dev/null || true) +fi +if [[ -z "$ETC" || ! -d "$ETC" ]]; then + ETC=$(readlink -f "$HOME/azeroth-server/etc" 2>/dev/null || true) +fi +if [[ -n "$ETC" && -d "$ETC/modules" ]]; then + MP="$ETC/modules/mod_paragon.conf" + MPD="$ETC/modules/mod_paragon.conf.dist" + if [[ -f "$MP" && -f "$MPD" ]]; then + diff -u "$MP" "$MPD" 2>/dev/null | head -80 || true + else + echo "ETC=$ETC — mod_paragon.conf or .dist missing (MP=$MP MPD=$MPD)" + fi +else + echo "Could not find install etc/modules (set paths manually for diff)." +fi + +hr +echo "DELIVERABLE for maintainer:" +echo "1) Paste sections 1A, 1B, 1C above." +echo "2) Paste DATABASE query results (or run SQL manually if FRACTURED_MYSQL was unset)." +echo "3) Paste 2A path strings + 2D listings (or MISSING lines)." +echo "4) Add ONE sentence: exact in-game symptom for testers." +echo "Done."