Spaces:
Paused
Paused
Download start.sh from smodusermc/srv: direct link, hf CLI and curl.
- Browser
- Download file 19.9 kB
-
https://huggingface.co/spaces/smodusermc/srv/resolve/main/start.sh
- Command line
-
hf download hf://spaces/smodusermc/srv/start.sh
-
curl -L -o start.sh https://huggingface.co/spaces/smodusermc/srv/resolve/main/start.sh
19.9 kB
| 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" | |
| SYNC_INTERVAL="${SYNC_INTERVAL:-300}" | |
| IDLE_MODE=false | |
| # ============================================= | |
| # OP ACCOUNT — set your username here | |
| # ============================================= | |
| OP_USERNAME="CreppyBitch" | |
| CPU_CORES=$(nproc 2>/dev/null || echo 2) | |
| NETTY_THREADS=$CPU_CORES | |
| TOTAL_MEM_MB=$(free -m | awk '/^Mem:/{print $2}') | |
| BUNGEE_MAX_MB=1024 | |
| PAPER_MAX_MB=$(( TOTAL_MEM_MB - BUNGEE_MAX_MB - 768 )) | |
| [ "$PAPER_MAX_MB" -gt 8192 ] && PAPER_MAX_MB=8192 | |
| [ "$PAPER_MAX_MB" -lt 1024 ] && PAPER_MAX_MB=1024 | |
| PAPER_MIN_MB=8192 | |
| echo "========================================" | |
| echo " Eaglercraft 1.8.8 Survival" | |
| 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 — no GC thread limits | |
| # ============================================= | |
| PAPER_JVM_FLAGS=( | |
| -Xmx${PAPER_MAX_MB}M | |
| -Xms${PAPER_MIN_MB}M | |
| -XX:+UseG1GC | |
| -XX:+ParallelRefProcEnabled | |
| -XX:MaxGCPauseMillis=25 | |
| -XX:+UnlockExperimentalVMOptions | |
| -XX:+DisableExplicitGC | |
| -XX:G1NewSizePercent=40 | |
| -XX:G1MaxNewSizePercent=50 | |
| -XX:G1HeapRegionSize=8M | |
| -XX:G1ReservePercent=15 | |
| -XX:G1HeapWastePercent=10 | |
| -XX:G1MixedGCCountTarget=8 | |
| -XX:InitiatingHeapOccupancyPercent=60 | |
| -XX:G1MixedGCLiveThresholdPercent=90 | |
| -XX:G1RSetUpdatingPauseTimePercent=5 | |
| -XX:SurvivorRatio=32 | |
| -XX:+PerfDisableSharedMem | |
| -XX:MaxTenuringThreshold=1 | |
| -XX:+OptimizeStringConcat | |
| -XX:+UseCompressedOops | |
| -XX:MaxMetaspaceSize=256M | |
| -XX:CompressedClassSpaceSize=128M | |
| -XX:ReservedCodeCacheSize=128M | |
| -XX:-UseCodeCacheFlushing | |
| -Xss256k | |
| -Djline.terminal=jline.UnsupportedTerminal | |
| -Dio.netty.allocator.maxCachedBufferCapacity=524288 | |
| -Dio.netty.recycler.maxCapacityPerThread=0 | |
| -Dio.netty.eventLoopThreads=${NETTY_THREADS} | |
| -Dio.netty.allocator.numDirectArenas=${NETTY_THREADS} | |
| -Dio.netty.allocator.numHeapArenas=${NETTY_THREADS} | |
| -Dcom.mojang.eula.agree=true | |
| -DIReallyKnowWhatIAmDoingISwear | |
| -Dusing.aikars.flags=https://mcflags.emc.gs | |
| -Daikars.new.flags=true | |
| ) | |
| BUNGEE_JVM_FLAGS=( | |
| -Xmx${BUNGEE_MAX_MB}M | |
| -Xms128M | |
| -XX:+UseG1GC | |
| -XX:+ParallelRefProcEnabled | |
| -XX:MaxGCPauseMillis=30 | |
| -XX:+UnlockExperimentalVMOptions | |
| -XX:+DisableExplicitGC | |
| -XX:+PerfDisableSharedMem | |
| -XX:+OptimizeStringConcat | |
| -XX:+UseCompressedOops | |
| -XX:MaxMetaspaceSize=128M | |
| -XX:ReservedCodeCacheSize=64M | |
| -Xss256k | |
| -Dio.netty.allocator.maxCachedBufferCapacity=524288 | |
| -Dio.netty.recycler.maxCapacityPerThread=0 | |
| -Dio.netty.eventLoopThreads=${NETTY_THREADS} | |
| -Dio.netty.allocator.numDirectArenas=${NETTY_THREADS} | |
| -Dio.netty.allocator.numHeapArenas=${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() { | |
| mcrcon -H 127.0.0.1 -P 25575 -p "$RCON_PASS" "$@" 2>/dev/null | |
| } | |
| # ============================================================= | |
| # 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})" | |
| mc_command "op ${OP_USERNAME}" 2>/dev/null | |
| echo " RCON op command sent" | |
| } | |
| # ============================================================= | |
| # IDLE MODE | |
| # ============================================================= | |
| enter_idle_mode() { | |
| [ "$IDLE_MODE" = true ] && return | |
| IDLE_MODE=true | |
| mc_command "gamerule randomTickSpeed 0" "gamerule doMobSpawning false" "kill @e[type=!Player]" | |
| echo "[IDLE] Active" | |
| } | |
| exit_idle_mode() { | |
| [ "$IDLE_MODE" = false ] && return | |
| IDLE_MODE=false | |
| mc_command "gamerule randomTickSpeed 3" "gamerule doMobSpawning true" | |
| echo "[IDLE] 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 | |
| [ -e "$BACKEND_DIR/$item" ] && cp -a "$BACKEND_DIR/$item" "$STAGING/$item" | |
| 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/6] Bucket setup..." | |
| hf_authenticate | |
| hf_ensure_bucket | |
| hf_restore_saves | |
| echo "" | |
| # ============================================================= | |
| # STEP 1: World size | |
| # ============================================================= | |
| echo "[1/6] 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: Start WindSpigot | |
| # ============================================================= | |
| cd "$BACKEND_DIR" | |
| echo "eula=true" > eula.txt | |
| cat > server.properties << 'EOF' | |
| server-port=25565 | |
| server-ip=127.0.0.1 | |
| online-mode=false | |
| spawn-protection=0 | |
| max-players=10 | |
| view-distance=4 | |
| gamemode=0 | |
| difficulty=2 | |
| level-name=world | |
| level-type=DEFAULT | |
| level-seed=8678942899319966093 | |
| generate-structures=true | |
| motd=Eaglercraft Server | |
| pvp=true | |
| allow-flight=false | |
| white-list=false | |
| spawn-npcs=true | |
| spawn-animals=true | |
| spawn-monsters=true | |
| enable-command-block=true | |
| allow-nether=true | |
| use-native-transport=true | |
| network-compression-threshold=-1 | |
| entity-broadcast-range-percentage=50 | |
| max-tick-time=-1 | |
| enable-rcon=true | |
| rcon.port=25575 | |
| rcon.password=chunkystart | |
| EOF | |
| 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: 30 | |
| animals: 6 | |
| water-animals: 2 | |
| ambient: 1 | |
| chunk-gc: | |
| period-in-ticks: 600 | |
| ticks-per: | |
| animal-spawns: 600 | |
| monster-spawns: 4 | |
| autosave: 12000 | |
| EOF | |
| 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: 4 | |
| mob-spawn-range: 3 | |
| entity-activation-range: | |
| animals: 12 | |
| monsters: 20 | |
| misc: 6 | |
| tick-inactive-villagers: false | |
| entity-tracking-range: | |
| players: 48 | |
| animals: 32 | |
| monsters: 32 | |
| misc: 16 | |
| other: 48 | |
| ticks-per: | |
| hopper-transfer: 8 | |
| hopper-check: 1 | |
| hopper-amount: 1 | |
| max-entity-collisions: 2 | |
| merge-radius: | |
| exp: 6.0 | |
| item: 4.0 | |
| arrow-despawn-rate: 60 | |
| item-despawn-rate: 3000 | |
| nerf-spawner-mobs: true | |
| zombie-aggressive-towards-villager: false | |
| enable-zombie-pigmen-portal-spawns: false | |
| 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: 1 | |
| players-per-thread: 25 | |
| EOF | |
| cat > nacho.yml << 'EOF' | |
| settings: | |
| stop-notify-bungee: true | |
| anti-malware: true | |
| use-tcp-nodelay: true | |
| brand-name: "Survival" | |
| EOF | |
| # Write ops.json before server starts | |
| setup_op_account | |
| echo "[2/6] Starting WindSpigot (--noconsole to fix CPU bug)..." | |
| > /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 | |
| # Send OP command via RCON as backup | |
| if [ -n "$OP_USERNAME" ]; then | |
| mc_command "op ${OP_USERNAME}" | |
| echo " OP granted to ${OP_USERNAME} via RCON" | |
| fi | |
| echo "" | |
| echo " === PLUGINS ===" | |
| 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: Skip CPU check (removed) | |
| # ============================================================= | |
| echo "[3/6] Skipping CPU check..." | |
| echo "" | |
| # ============================================================= | |
| # STEP 4: Apply idle mode | |
| # ============================================================= | |
| echo "[4/6] Applying idle mode (no players)..." | |
| mc_command "gamerule randomTickSpeed 0" "gamerule doMobSpawning false" "kill @e[type=!Player]" | |
| IDLE_MODE=true | |
| echo "" | |
| # ============================================================= | |
| # STEP 5: BungeeCord | |
| # ============================================================= | |
| echo "[5/6] BungeeCord config..." | |
| cd "$BUNGEE_DIR" | |
| cat > config.yml << 'EOF' | |
| server_connect_timeout: 5000 | |
| remote_ping_cache: -1 | |
| forge_support: false | |
| player_limit: 10 | |
| 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' | |
| 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: 10 | |
| 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 | |
| LISTENERS_FILE=$(find_listeners_yml) | |
| if [ -z "$LISTENERS_FILE" ]; then | |
| echo "[6/6] 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/6] EaglerXBungee config exists" | |
| fi | |
| echo " Starting BungeeCord..." | |
| patch_eagler_port | |
| 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 " SERVER READY — EaglerCraft on :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 | |
| # ============================================================= | |
| # Sync loop | |
| # ============================================================= | |
| hf_sync_loop & | |
| SYNC_PID=$! | |
| # ============================================================= | |
| # Shutdown | |
| # ============================================================= | |
| graceful_shutdown() { | |
| echo " Shutting down..." | |
| mc_command "gamerule doMobSpawning true" "gamerule randomTickSpeed 3" "save-all" | |
| sleep 5 | |
| hf_push_saves | |
| kill $SYNC_PID $BACKEND_PID $BUNGEE_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 | |
| # Re-OP after restart | |
| if [ -n "$OP_USERNAME" ]; then | |
| mc_command "op ${OP_USERNAME}" 2>/dev/null | |
| fi | |
| enter_idle_mode | |
| 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 | |
| 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 | |
| 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 | |
| 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 |