Spaces:
Running
Running
Download start.sh from smodusermc/server: direct link, hf CLI and curl.
- Browser
- Download file 21.8 kB
-
https://huggingface.co/spaces/smodusermc/server/resolve/main/start.sh
- Command line
-
hf download hf://spaces/smodusermc/server/start.sh
-
curl -L -o start.sh https://huggingface.co/spaces/smodusermc/server/resolve/main/start.sh
21.8 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 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 |