server / start.sh
smodusermc's picture
Update start.sh
a448ecd verified
Raw History Blame Contribute Delete
21.8 kB
#!/bin/bash
JAVA_HOME_DIR=$(find /usr/lib/jvm -maxdepth 1 -name "java-17-openjdk-*" -type d 2>/dev/null | head -1)
if [ -z "$JAVA_HOME_DIR" ]; then
echo "ERROR: Java 17 not found!"
exit 1
fi
JAVA="$JAVA_HOME_DIR/bin/java"
BUNGEE_DIR="/opt/server/bungee"
BACKEND_DIR="/opt/server/backend"
PLUGIN_DIR="$BACKEND_DIR/plugins"
mkdir -p "$PLUGIN_DIR"
HF_BUCKET_HANDLE="hf://buckets/smodusermc/mc"
SAVE_DIRS="world world_nether world_the_end players banned-ips.json banned-players.json ops.json whitelist.json plugins"
SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
IDLE_MODE=false
# =============================================
# OP ACCOUNT
# =============================================
OP_USERNAME="CreppyBitch"
CPU_CORES=$(nproc 2>/dev/null || echo 2)
NETTY_THREADS=4
TOTAL_MEM_MB=$(free -m | awk '/^Mem:/{print $2}')
BUNGEE_MAX_MB=512
PAPER_MAX_MB=$(( TOTAL_MEM_MB - BUNGEE_MAX_MB - 512 ))
[ "$PAPER_MAX_MB" -gt 6144 ] && PAPER_MAX_MB=6144
[ "$PAPER_MAX_MB" -lt 1024 ] && PAPER_MAX_MB=1024
# FIX: Min should be less than or equal to max
PAPER_MIN_MB=$(( PAPER_MAX_MB / 2 ))
[ "$PAPER_MIN_MB" -lt 512 ] && PAPER_MIN_MB=512
echo "========================================"
echo " Eaglercraft 1.8.8 Survival Server"
echo " WindSpigot + HuggingFace Buckets"
echo "========================================"
echo ""
echo " CPUs: $CPU_CORES | RAM: ${TOTAL_MEM_MB}MB"
echo " Server: ${PAPER_MIN_MB}-${PAPER_MAX_MB}MB | Bungee: ${BUNGEE_MAX_MB}MB"
echo " Java: $($JAVA -version 2>&1 | head -1)"
echo " Bucket: $HF_BUCKET_HANDLE"
[ -n "$OP_USERNAME" ] && echo " OP Account: $OP_USERNAME"
echo ""
# =============================================
# JVM FLAGS - Optimized for limited resources
# =============================================
PAPER_JVM_FLAGS=(
-Xmx${PAPER_MAX_MB}M
-Xms${PAPER_MIN_MB}M
-XX:+UseG1GC
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=50
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
-XX:G1HeapRegionSize=8M
-XX:G1ReservePercent=20
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=15
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
-XX:+OptimizeStringConcat
-XX:+UseCompressedOops
-XX:MaxMetaspaceSize=192M
-XX:CompressedClassSpaceSize=96M
-XX:ReservedCodeCacheSize=96M
-Xss256k
-Djline.terminal=jline.UnsupportedTerminal
-Dio.netty.allocator.maxCachedBufferCapacity=262144
-Dio.netty.recycler.maxCapacityPerThread=0
-Dio.netty.eventLoopThreads=${NETTY_THREADS}
-Dcom.mojang.eula.agree=true
-DIReallyKnowWhatIAmDoingISwear
)
BUNGEE_JVM_FLAGS=(
-Xmx${BUNGEE_MAX_MB}M
-Xms128M
-XX:+UseG1GC
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=50
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:+PerfDisableSharedMem
-XX:+OptimizeStringConcat
-XX:+UseCompressedOops
-XX:MaxMetaspaceSize=96M
-Xss256k
-Dio.netty.allocator.maxCachedBufferCapacity=262144
-Dio.netty.recycler.maxCapacityPerThread=0
-Dio.netty.eventLoopThreads=${NETTY_THREADS}
-Deaglerxbungee.stfu=true
)
# =============================================================
# RCON
# =============================================================
RCON_PASS="chunkystart"
get_player_count() {
local RESULT
RESULT=$(mcrcon -H 127.0.0.1 -P 25575 -p "$RCON_PASS" "list" 2>/dev/null)
echo "$RESULT" | grep -oE 'are [0-9]+' | grep -oE '[0-9]+' || echo "0"
}
mc_command() {
for cmd in "$@"; do
mcrcon -H 127.0.0.1 -P 25575 -p "$RCON_PASS" "$cmd" 2>/dev/null
done
}
# =============================================================
# WindSpigot starter
# =============================================================
start_windspigot() {
cd "$BACKEND_DIR"
$JAVA "${PAPER_JVM_FLAGS[@]}" -jar server.jar nogui --noconsole >> /tmp/paper.log 2>&1 &
BACKEND_PID=$!
}
# =============================================================
# OP Account Setup
# =============================================================
setup_op_account() {
if [ -z "$OP_USERNAME" ]; then
return
fi
echo " Setting up OP for: $OP_USERNAME"
local OFFLINE_UUID
OFFLINE_UUID=$(echo -n "OfflinePlayer:${OP_USERNAME}" | md5sum | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)/\1\2\3\4-\5\6-\7\8-\9\10-\11\12\13\14\15\16/')
local V3_UUID
V3_UUID=$(echo "$OFFLINE_UUID" | sed 's/.\{1\}\(.\{3\}-\)/3\1/' | sed 's/\(.\{14\}-\).\(.\{3\}-\)/\1'"$(echo "$OFFLINE_UUID" | cut -c15 | tr '0-9a-f' '89ab89ab89ab89ab')"'\2/')
cat > "$BACKEND_DIR/ops.json" << OPEOF
[
{
"uuid": "${V3_UUID}",
"name": "${OP_USERNAME}",
"level": 4,
"bypassesPlayerLimit": true
}
]
OPEOF
echo " ops.json written (level 4, UUID: ${V3_UUID})"
}
# =============================================================
# IDLE MODE - Reduced tick rate but keeps mob spawning logic
# =============================================================
enter_idle_mode() {
[ "$IDLE_MODE" = true ] && return
IDLE_MODE=true
# Only reduce tick speed, don't disable spawning entirely
mc_command "gamerule randomTickSpeed 1"
# Clear excess hostile mobs to reduce lag when idle
mc_command "kill @e[type=Zombie,r=200]"
mc_command "kill @e[type=Skeleton,r=200]"
mc_command "kill @e[type=Spider,r=200]"
mc_command "kill @e[type=Creeper,r=200]"
echo "[IDLE] Low-power mode — reduced ticks, cleared distant mobs"
}
exit_idle_mode() {
[ "$IDLE_MODE" = false ] && return
IDLE_MODE=false
mc_command "gamerule randomTickSpeed 2"
echo "[IDLE] Normal gameplay restored"
}
# =============================================================
# Port fix
# =============================================================
find_listeners_yml() {
find "$BUNGEE_DIR/plugins" -name "listeners.yml" -type f 2>/dev/null | head -1
}
patch_eagler_port() {
local FILE=$(find_listeners_yml)
[ -z "$FILE" ] && return 1
grep -q ":7860" "$FILE" && return 0
sed -i 's/\(address:[[:space:]]*"[^:]*:\)[0-9]*/\17860/' "$FILE"
sed -i "s/\(address:[[:space:]]*[^\"][^:]*:\)[0-9]*/\17860/" "$FILE"
echo " Port -> 7860"
}
start_bungee() {
cd "$BUNGEE_DIR"
$JAVA "${BUNGEE_JVM_FLAGS[@]}" \
-cp "sqlite-jdbc.jar:BungeeCord.jar" \
net.md_5.bungee.Bootstrap >> /tmp/bungee.log 2>&1 &
BUNGEE_PID=$!
}
# =============================================================
# HuggingFace Bucket
# =============================================================
hf_authenticate() {
if [ -n "$HF_TOKEN" ]; then
hf auth login --token "$HF_TOKEN" --add-to-git-credential 2>/dev/null || true
echo " Authenticated"
else
echo " No HF_TOKEN"
fi
}
hf_ensure_bucket() {
local BUCKET_ID=$(echo "$HF_BUCKET_HANDLE" | sed 's|hf://buckets/||')
hf buckets create "$BUCKET_ID" --exist-ok 2>/dev/null
}
hf_restore_saves() {
echo " Restoring game data..."
hf buckets sync "${HF_BUCKET_HANDLE}/game-data" "$BACKEND_DIR" 2>&1 | tail -5
for dir in $SAVE_DIRS; do
[ -e "$BACKEND_DIR/$dir" ] && echo " Found: $dir"
done
}
hf_push_saves() {
local STAGING="/tmp/hf-staging"
rm -rf "$STAGING" && mkdir -p "$STAGING"
for item in $SAVE_DIRS; do
if [ -e "$BACKEND_DIR/$item" ]; then
mkdir -p "$STAGING/$(dirname "$item")"
cp -a "$BACKEND_DIR/$item" "$STAGING/$item"
fi
done
hf buckets sync "$STAGING" "${HF_BUCKET_HANDLE}/game-data" --delete 2>&1 | tail -3
[ $? -eq 0 ] && echo "[SYNC] OK $(date '+%H:%M:%S')" \
|| echo "[SYNC] FAIL $(date '+%H:%M:%S')"
rm -rf "$STAGING"
}
hf_sync_loop() {
while true; do
sleep "$SYNC_INTERVAL"
hf_push_saves
done
}
# =============================================================
# STEP 0: Bucket
# =============================================================
echo "[0/7] Bucket setup..."
hf_authenticate
hf_ensure_bucket
hf_restore_saves
echo ""
# =============================================================
# STEP 1: World size
# =============================================================
echo "[1/7] World analysis..."
for WORLD_DIR in world world_nether world_the_end; do
if [ -d "$BACKEND_DIR/$WORLD_DIR" ]; then
SIZE=$(du -sh "$BACKEND_DIR/$WORLD_DIR" 2>/dev/null | awk '{print $1}')
REGIONS=$(find "$BACKEND_DIR/$WORLD_DIR" -name "*.mca" 2>/dev/null | wc -l)
echo " $WORLD_DIR: $SIZE ($REGIONS region files)"
fi
done
echo ""
# =============================================================
# STEP 2: Core server configs + Start WindSpigot
# =============================================================
cd "$BACKEND_DIR"
echo "eula=true" > eula.txt
echo "[2/7] Writing core server configs + starting WindSpigot..."
# FIX: Enable mobs, use DEFAULT world type for survival
cat > server.properties << 'EOF'
server-port=25565
server-ip=127.0.0.1
online-mode=false
spawn-protection=0
max-players=20
view-distance=6
gamemode=0
difficulty=2
level-name=world
level-type=DEFAULT
level-seed=
generate-structures=true
motd=Survival Eaglercraft
pvp=true
allow-flight=false
white-list=false
spawn-npcs=true
spawn-animals=true
spawn-monsters=true
enable-command-block=false
allow-nether=true
use-native-transport=true
network-compression-threshold=256
entity-broadcast-range-percentage=75
max-tick-time=60000
enable-rcon=true
rcon.port=25575
rcon.password=chunkystart
EOF
# FIX: Balanced mob spawning - not too many, not too few
cat > bukkit.yml << 'EOF'
settings:
allow-end: true
warn-on-overload: true
connection-throttle: -1
shutdown-message: Server closed
save-user-cache-on-stop-only: true
spawn-limits:
monsters: 15
animals: 20
water-animals: 3
ambient: 3
chunk-gc:
period-in-ticks: 400
ticks-per:
animal-spawns: 400
monster-spawns: 2
autosave: 6000
EOF
# FIX: Better entity activation ranges for survival gameplay
cat > spigot.yml << 'EOF'
config-version: 8
settings:
bungeecord: true
timeout-time: 60
netty-threads: 2
async-catcher-enabled: false
save-user-cache-on-stop-only: true
moved-wrongly-threshold: 0.0625
moved-too-quickly-multiplier: 10.0
item-dirty-ticks: 20
player-shuffle: 0
commands:
tab-complete: 0
log: false
world-settings:
default:
verbose: false
view-distance: 6
mob-spawn-range: 4
entity-activation-range:
animals: 24
monsters: 32
raiders: 48
misc: 8
tick-inactive-villagers: true
entity-tracking-range:
players: 64
animals: 48
monsters: 48
misc: 32
other: 64
ticks-per:
hopper-transfer: 8
hopper-check: 1
hopper-amount: 1
max-entity-collisions: 4
merge-radius:
exp: 4.0
item: 3.5
arrow-despawn-rate: 300
item-despawn-rate: 6000
nerf-spawner-mobs: false
zombie-aggressive-towards-villager: true
enable-zombie-pigmen-portal-spawns: true
max-tnt-per-tick: 20
EOF
cat > windspigot.yml << 'EOF'
settings:
async:
entity-tracking: true
path-searching: true
tnt-explosions: true
lighting: true
tnt:
optimize-movement: true
optimize-liquid-explosions: true
fast-operators: true
stop-decoding-itemstack-on-place: true
modern-tick-loop:
enabled: false
tcp-fast-open: true
reduced-chunk-loads: true
lag-compensated-potions: false
anti-xray:
enabled: false
hit-detection:
enabled: true
threshold: 1.0
combat:
knockback:
friction: 2.0
horizontal: 0.35
vertical: 0.35
vertical-limit: 0.4
extra-horizontal: 0.425
extra-vertical: 0.085
chunk:
threads: 2
players-per-thread: 7
EOF
cat > nacho.yml << 'EOF'
settings:
stop-notify-bungee: true
anti-malware: true
use-tcp-nodelay: true
brand-name: "Survival"
EOF
setup_op_account
> /tmp/paper.log
start_windspigot
echo " WindSpigot PID: $BACKEND_PID"
for i in $(seq 1 120); do
if grep -q "Done" /tmp/paper.log 2>/dev/null; then
echo " WindSpigot READY (~${i}s)"
break
fi
if ! kill -0 $BACKEND_PID 2>/dev/null; then
echo " WINDSPIGOT CRASHED!"
tail -30 /tmp/paper.log
exit 1
fi
[ $((i % 15)) -eq 0 ] && echo " Loading... (${i}s)"
sleep 1
done
for i in $(seq 1 30); do
nc -z 127.0.0.1 25575 2>/dev/null && break
sleep 1
done
if [ -n "$OP_USERNAME" ]; then
mc_command "op ${OP_USERNAME}"
echo " OP granted to ${OP_USERNAME} via RCON"
fi
echo ""
echo " === PLUGINS LOADED ==="
grep -i "Enabling" /tmp/paper.log | grep -oP "Enabling \K[^\s]+" 2>/dev/null | while read p; do
echo " - $p"
done
echo " ======================"
echo ""
# =============================================================
# STEP 3: Survival gamerules
# =============================================================
echo "[3/7] Setting Survival gamerules..."
mc_command "gamerule pvp true"
mc_command "gamerule keepInventory false"
mc_command "gamerule naturalRegeneration true"
mc_command "gamerule doFireTick true"
mc_command "gamerule mobGriefing false"
mc_command "gamerule doMobSpawning true"
mc_command "gamerule doDaylightCycle true"
mc_command "gamerule doWeatherCycle true"
mc_command "gamerule randomTickSpeed 2"
mc_command "gamerule spawnRadius 10"
mc_command "gamerule maxEntityCramming 12"
mc_command "difficulty 1"
mc_command "defaultgamemode survival"
echo " Survival gamerules set"
echo ""
# =============================================================
# STEP 4: Initial idle mode if no players
# =============================================================
echo "[4/7] Checking for players..."
PLAYER_COUNT=$(get_player_count)
if [ "$PLAYER_COUNT" = "0" ]; then
enter_idle_mode
else
echo " Players online: $PLAYER_COUNT"
fi
echo ""
# =============================================================
# STEP 5: Write BungeeCord config
# =============================================================
echo "[5/7] Writing BungeeCord config..."
cd "$BUNGEE_DIR"
cat > config.yml << 'EOF'
server_connect_timeout: 5000
remote_ping_cache: -1
forge_support: false
player_limit: 20
permissions:
default:
- bungeecord.command.server
admin:
- bungeecord.command.alert
timeout: 30000
log_commands: false
network_compression_threshold: 256
online_mode: false
disabled_commands:
- disabledcommandhere
servers:
lobby:
motd: '&aEaglercraft Survival'
address: 127.0.0.1:25565
restricted: false
listeners:
- query_port: 25577
motd: '&6Eaglercraft 1.8.8 Survival'
tab_list: GLOBAL_PING
query_enabled: false
proxy_protocol: false
forced_hosts: {}
ping_passthrough: false
priorities:
- lobby
bind_local_address: true
host: 127.0.0.1:25577
max_players: 20
tab_size: 60
force_default_server: true
ip_forward: true
remote_ping_timeout: 5000
prevent_proxy_connections: false
groups:
default:
- default
connection_throttle: -1
connection_throttle_limit: 0
stats: none
log_pings: false
EOF
echo " BungeeCord config.yml written"
echo ""
# =============================================================
# STEP 6: EaglerXBungee generation + Start BungeeCord
# =============================================================
LISTENERS_FILE=$(find_listeners_yml)
if [ -z "$LISTENERS_FILE" ]; then
echo "[6/7] Generating EaglerXBungee config..."
cd "$BUNGEE_DIR"
$JAVA "${BUNGEE_JVM_FLAGS[@]}" \
-cp "sqlite-jdbc.jar:BungeeCord.jar" \
net.md_5.bungee.Bootstrap >> /tmp/bungee-gen.log 2>&1 &
GEN_PID=$!
for i in $(seq 1 60); do
if nc -z 127.0.0.1 8081 2>/dev/null || nc -z 127.0.0.1 7860 2>/dev/null; then
echo " EaglerXBungee started (~$((i*2))s)"
break
fi
if ! kill -0 $GEN_PID 2>/dev/null; then
echo " Generation failed"
tail -20 /tmp/bungee-gen.log
break
fi
sleep 2
done
sleep 3
kill $GEN_PID 2>/dev/null
wait $GEN_PID 2>/dev/null
for i in $(seq 1 15); do
nc -z 127.0.0.1 8081 2>/dev/null || break
sleep 1
done
sleep 2
else
echo "[6/7] EaglerXBungee config exists"
fi
echo " Starting BungeeCord..."
patch_eagler_port
# MOTD Patch
LISTENERS_NOW=$(find_listeners_yml)
if [ -n "$LISTENERS_NOW" ]; then
sed -i 's/An EaglercraftX server/\&a\&lSurvival Server/g' "$LISTENERS_NOW"
fi
EAGLER_DIR=$(dirname "$(find_listeners_yml)" 2>/dev/null)
if [ -n "$EAGLER_DIR" ]; then
mkdir -p "$EAGLER_DIR/drivers"
cp -f "$BUNGEE_DIR/sqlite-jdbc.jar" "$EAGLER_DIR/drivers/sqlite-jdbc.jar" 2>/dev/null
fi
> /tmp/bungee.log
start_bungee
echo " BungeeCord PID: $BUNGEE_PID"
PORT_READY=false
for i in $(seq 1 45); do
if nc -z 127.0.0.1 7860 2>/dev/null; then
PORT_READY=true
echo " Port 7860 OPEN (~$((i*2))s)"
break
fi
if ! kill -0 $BUNGEE_PID 2>/dev/null; then
echo " BungeeCord crashed!"
tail -20 /tmp/bungee.log
break
fi
sleep 2
done
if [ "$PORT_READY" = true ]; then
echo ""
echo "============================================"
echo " SURVIVAL SERVER READY — EaglerCraft :7860"
[ -n "$OP_USERNAME" ] && echo " OP: $OP_USERNAME (level 4)"
echo "============================================"
else
echo " Port 7860 NOT open!"
for port in 7860 8081 25565 25577; do
nc -z 127.0.0.1 $port 2>/dev/null && echo " OK $port" || echo " FAIL $port"
done
LISTENERS_NOW=$(find_listeners_yml)
if [ -n "$LISTENERS_NOW" ] && grep -q ":8081" "$LISTENERS_NOW"; then
kill $BUNGEE_PID 2>/dev/null
wait $BUNGEE_PID 2>/dev/null
sleep 3
patch_eagler_port
start_bungee
sleep 20
nc -z 127.0.0.1 7860 2>/dev/null && echo " Port 7860 open!" || echo " Failed"
fi
fi
# =============================================================
# STEP 7: Final confirmation
# =============================================================
echo ""
echo "[7/7] Final status check..."
echo " === ACTIVE PLUGINS ==="
RELOAD_CHECK=$(mc_command "plugins")
echo " $RELOAD_CHECK"
echo " ======================"
echo ""
# =============================================================
# Sync loop
# =============================================================
hf_sync_loop &
SYNC_PID=$!
# =============================================================
# Shutdown
# =============================================================
graceful_shutdown() {
echo " Shutting down..."
mc_command "gamerule randomTickSpeed 3"
mc_command "save-all"
sleep 5
hf_push_saves
kill $SYNC_PID 2>/dev/null
mc_command "stop"
sleep 5
kill $BUNGEE_PID 2>/dev/null
kill -0 $BACKEND_PID 2>/dev/null && kill $BACKEND_PID 2>/dev/null
exit 0
}
trap graceful_shutdown SIGTERM SIGINT SIGHUP
# =============================================================
# Monitor loop
# =============================================================
echo ""
echo "Monitor loop started..."
LAST_LOG_LINE=$(wc -l < /tmp/paper.log 2>/dev/null || echo 0)
LOOP_COUNT=0
while true; do
LOOP_COUNT=$((LOOP_COUNT + 1))
if ! kill -0 $BACKEND_PID 2>/dev/null; then
echo "[$(date '+%H:%M:%S')] WindSpigot crashed — restarting..."
hf_push_saves
IDLE_MODE=false
start_windspigot
sleep 45
for i in $(seq 1 30); do
nc -z 127.0.0.1 25575 2>/dev/null && break
sleep 1
done
if [ -n "$OP_USERNAME" ]; then
mc_command "op ${OP_USERNAME}"
fi
mc_command "difficulty 1"
mc_command "gamerule doMobSpawning true"
mc_command "gamerule randomTickSpeed 2"
fi
if ! kill -0 $BUNGEE_PID 2>/dev/null; then
echo "[$(date '+%H:%M:%S')] BungeeCord crashed — restarting..."
patch_eagler_port
start_bungee
fi
if ! kill -0 $SYNC_PID 2>/dev/null; then
hf_sync_loop &
SYNC_PID=$!
fi
if kill -0 $BACKEND_PID 2>/dev/null; then
PLAYER_COUNT=$(get_player_count)
if [ "$PLAYER_COUNT" != "0" ] && [ "$IDLE_MODE" = true ]; then
exit_idle_mode
elif [ "$PLAYER_COUNT" = "0" ] && [ "$IDLE_MODE" = false ]; then
enter_idle_mode
fi
fi
# Log monitoring
if [ $((LOOP_COUNT % 5)) -eq 0 ]; then
CURRENT_LINE=$(wc -l < /tmp/paper.log 2>/dev/null || echo 0)
if [ "$CURRENT_LINE" -gt "$LAST_LOG_LINE" ]; then
NEW_ERRORS=$(tail -n +"$((LAST_LOG_LINE + 1))" /tmp/paper.log | grep -c "ERROR\|SEVERE" || echo 0)
[ "$NEW_ERRORS" -gt 0 ] && echo "[$(date '+%H:%M:%S')] $NEW_ERRORS errors" && \
tail -n +"$((LAST_LOG_LINE + 1))" /tmp/paper.log | grep "ERROR\|SEVERE" | tail -3
LAST_LOG_LINE=$CURRENT_LINE
fi
fi
# Log trimming
if [ $((LOOP_COUNT % 30)) -eq 0 ]; then
for LF in /tmp/paper.log /tmp/bungee.log; do
LS=$(stat -c%s "$LF" 2>/dev/null || echo 0)
if [ "$LS" -gt 10485760 ]; then
tail -1000 "$LF" > "${LF}.tmp" && mv "${LF}.tmp" "$LF"
echo "[$(date '+%H:%M:%S')] Trimmed $(basename $LF)"
fi
done
LAST_LOG_LINE=$(wc -l < /tmp/paper.log 2>/dev/null || echo 0)
fi
# Status output
if [ $((LOOP_COUNT % 5)) -eq 0 ]; then
RSS=$(ps -p $BACKEND_PID -o rss= 2>/dev/null | awk '{printf "%.0f", $1/1024}')
echo "[STATUS] Players: ${PLAYER_COUNT:-?} | RAM: ${RSS:-?}MB | $([ "$IDLE_MODE" = true ] && echo IDLE || echo ACTIVE)"
fi
sleep 60
done