Next.js 16 + Tailwind v4 + shadcn v4 dashboard for managing a modded Forge 1.20.1 server. Includes server controls, player management, mod manager with Modrinth search and dependency resolution, world backups, snapshots, analytics, logs, and chat bridge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
2.5 KiB
Bash
83 lines
2.5 KiB
Bash
#!/bin/bash
|
|
# Collect server metrics every minute — called by cron
|
|
# Appends JSON lines to analytics.jsonl
|
|
|
|
ANALYTICS_FILE="/home/minecraft/server/analytics.jsonl"
|
|
RCON_PASS="23991818cc169249f181436f2a29a013"
|
|
|
|
# Check if server is running
|
|
if ! systemctl is-active --quiet minecraft.service; then
|
|
exit 0
|
|
fi
|
|
|
|
# Get MC server PID
|
|
MC_PID=$(pgrep -f 'minecraft.*server' | head -1)
|
|
if [ -z "$MC_PID" ]; then
|
|
MC_PID=$(pgrep -f 'java.*forge' | head -1)
|
|
fi
|
|
|
|
# TPS via RCON
|
|
TPS="0"
|
|
TPS_RAW=$(echo -e "\x00" | timeout 3 python3 -c "
|
|
from rcon.source import Client
|
|
try:
|
|
with Client('127.0.0.1', 25575, passwd='${RCON_PASS}') as c:
|
|
r = c.run('forge tps')
|
|
print(r)
|
|
except: pass
|
|
" 2>/dev/null)
|
|
|
|
if echo "$TPS_RAW" | grep -q "Overall"; then
|
|
TPS=$(echo "$TPS_RAW" | grep "Overall" | grep -oP '[\d.]+(?= TPS)' | head -1)
|
|
elif echo "$TPS_RAW" | grep -q "Dim 0"; then
|
|
TPS=$(echo "$TPS_RAW" | grep "Dim 0\|overworld" | grep -oP '[\d.]+(?= TPS)' | head -1)
|
|
fi
|
|
[ -z "$TPS" ] && TPS="0"
|
|
|
|
# RAM from /proc
|
|
RAM_USED=0
|
|
RAM_TOTAL=8192
|
|
if [ -n "$MC_PID" ] && [ -f "/proc/$MC_PID/status" ]; then
|
|
RAM_KB=$(grep VmRSS "/proc/$MC_PID/status" 2>/dev/null | awk '{print $2}')
|
|
[ -n "$RAM_KB" ] && RAM_USED=$((RAM_KB / 1024))
|
|
fi
|
|
|
|
# CPU
|
|
CPU="0"
|
|
if [ -n "$MC_PID" ]; then
|
|
CPU=$(ps -p "$MC_PID" -o %cpu --no-headers 2>/dev/null | tr -d ' ')
|
|
fi
|
|
[ -z "$CPU" ] && CPU="0"
|
|
|
|
# Players via RCON
|
|
PLAYERS_JSON="[]"
|
|
PLAYERS_ONLINE=0
|
|
PLAYERS_RAW=$(timeout 3 python3 -c "
|
|
from rcon.source import Client
|
|
try:
|
|
with Client('127.0.0.1', 25575, passwd='${RCON_PASS}') as c:
|
|
r = c.run('list')
|
|
print(r)
|
|
except: pass
|
|
" 2>/dev/null)
|
|
|
|
if echo "$PLAYERS_RAW" | grep -q "players online"; then
|
|
PLAYERS_ONLINE=$(echo "$PLAYERS_RAW" | grep -oP '\d+(?= of)' | head -1)
|
|
NAMES=$(echo "$PLAYERS_RAW" | sed 's/.*: //')
|
|
if [ "$PLAYERS_ONLINE" -gt 0 ] 2>/dev/null && [ -n "$NAMES" ]; then
|
|
PLAYERS_JSON=$(echo "$NAMES" | python3 -c "import sys,json; print(json.dumps([n.strip() for n in sys.stdin.read().split(',') if n.strip()]))")
|
|
fi
|
|
fi
|
|
[ -z "$PLAYERS_ONLINE" ] && PLAYERS_ONLINE=0
|
|
|
|
# Timestamp
|
|
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
|
|
# Write JSON line
|
|
echo "{\"ts\":\"$TS\",\"tps\":$TPS,\"ramUsedMB\":$RAM_USED,\"ramTotalMB\":$RAM_TOTAL,\"cpuPercent\":$CPU,\"playersOnline\":$PLAYERS_ONLINE,\"players\":$PLAYERS_JSON}" >> "$ANALYTICS_FILE"
|
|
|
|
# Keep only last 48 hours of data (2880 lines at 1/min)
|
|
if [ $(wc -l < "$ANALYTICS_FILE") -gt 3000 ]; then
|
|
tail -n 2880 "$ANALYTICS_FILE" > "$ANALYTICS_FILE.tmp"
|
|
mv "$ANALYTICS_FILE.tmp" "$ANALYTICS_FILE"
|
|
fi
|