Files
Fractured/scripts/vps-paragon-diagnostics.sh
T
Docker Build 88f8dcb0e7 scripts: extend vps-paragon-diagnostics for rune/RP DBC and binary parity
- Binary sha256 + revision-like strings for dev vs VPS compare
- worldserver.conf Rate.RunicPower and mod_paragon.conf Paragon.* keys
- MySQL: chrclasses_dbc 6/12, spell_dbc sample, spellrunecost join
- FRACTURED_SPELL_IDS override for custom spell spot-checks

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 15:46:35 -05:00

300 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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.
# FRACTURED_SPELL_IDS — space-separated spell IDs for spell_dbc spot-check (defaults to common DK rune spenders)
#
# 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
sub "1D — binary fingerprint (compare sha256 across dev vs VPS)"
if [[ -n "$WS" && -f "$WS" ]]; then
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$WS"
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$WS"
else
echo "(no sha256sum — install coreutils)"
fi
echo "Embedded revision / version strings (first matches):"
strings "$WS" 2>/dev/null | grep -iE 'azerothcore|revision|git|commit|build.*20[0-9]{2}' | head -25 || echo "(none matched)"
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 / rates / paragon)"
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)"
echo "--- Rate.RunicPower (if set) ---"
grep -iE '^Rate\.RunicPower|^[[:space:]]*Rate\.RunicPower' "$CONF" 2>/dev/null || echo "(not set — server uses default)"
echo "--- Paragon.* module options (if any) ---"
grep -iE '^Paragon\.|^[[:space:]]*Paragon\.' "$CONF" 2>/dev/null || echo "(no Paragon.* keys in worldserver.conf — check etc/modules/mod_paragon.conf)"
else
echo "WARN: worldserver.conf not found. Set FRACTURED_WORLDSERVER_CONF=/path/to/worldserver.conf"
fi
if [[ -n "$WS" && -f "$WS" ]]; then
ETCGuess=$(readlink -f "$(dirname "$WS")/../etc" 2>/dev/null || true)
MPC="$ETCGuess/modules/mod_paragon.conf"
if [[ -f "$MPC" ]]; then
sub "2B2 — mod_paragon.conf Paragon.* toggles (non-comment)"
grep -E '^Paragon\.' "$MPC" 2>/dev/null | head -40 || echo "(no uncommented Paragon.* lines)"
fi
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)"
sub "DATABASE — DBC parity for runes / Paragon (acore_world)"
# Common DK rune spenders (WotLK). Override: export FRACTURED_SPELL_IDS='45477 45462'
SPELL_IDS="${FRACTURED_SPELL_IDS:-45477 45462 49923 55050 56815}"
IDS_CSV=$(echo "$SPELL_IDS" | tr ' ' ',')
echo "--- spell_dbc table size (world DB overrides; 0 rows = all spells from disk DBC only) ---"
$FRACTURED_MYSQL acore_world -e "SELECT COUNT(*) AS spell_dbc_rows FROM spell_dbc;" 2>/dev/null || echo "(spell_dbc missing or no access)"
echo "--- chrclasses_dbc class 12 (DisplayPower: 0=mana, 5=POWER_RUNE in AC) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, DisplayPower, Name_Lang_enUS FROM chrclasses_dbc WHERE ID IN (6,12);
" 2>/dev/null || echo "(query failed — chrclasses_dbc missing?)"
echo "--- spell_dbc sample (PowerType 5 = rune for server checks) ---"
$FRACTURED_MYSQL acore_world -e "
SELECT ID, PowerType, ManaCost, RuneCostID FROM spell_dbc WHERE ID IN ($IDS_CSV);
" 2>/dev/null || echo "(query failed — spell_dbc missing or wrong schema)"
echo "--- spellrunecost_dbc for RuneCostIDs from those spells ---"
$FRACTURED_MYSQL acore_world -e "
SELECT s.ID AS spell_id, s.RuneCostID, r.Blood, r.Unholy, r.Frost, r.RunicPower
FROM spell_dbc s
LEFT JOIN spellrunecost_dbc r ON r.ID = s.RuneCostID
WHERE s.ID IN ($IDS_CSV);
" 2>/dev/null || echo "(join failed — check spellrunecost_dbc)"
echo "Compare this block to a known-good dev DB: mismatched PowerType/RuneCostID on the same spell_id = data drift."
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"
echo ""
echo "Optional DBC parity (acore_world) — run after connecting:"
echo " SELECT ID, DisplayPower, Name_Lang_enUS FROM chrclasses_dbc WHERE ID IN (6,12);"
echo " SELECT ID, PowerType, ManaCost, RuneCostID FROM spell_dbc WHERE ID IN (45477,45462,49923,55050,56815);"
echo " SELECT s.ID, s.RuneCostID, r.Blood, r.Unholy, r.Frost, r.RunicPower FROM spell_dbc s"
echo " LEFT JOIN spellrunecost_dbc r ON r.ID = s.RuneCostID WHERE s.ID IN (45477,45462,49923,55050,56815);"
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 1A1D (binary mtime, git HEAD, strings, sha256 + revision strings)."
echo "2) Paste DATABASE blocks: updates + DBC parity (chrclasses 12, spell_dbc, spellrunecost join)."
echo "3) Paste 2A path strings + 2D listings (or MISSING lines)."
echo "4) From dev: same 1D sha256 of worldserver OR same SQL block — proves binary/data parity."
echo "5) ONE sentence: exact in-game symptom."
echo "Done."