srv / start.sh
smodusermc's picture
Update start.sh
b673618 verified
Raw History Blame Contribute Delete
19.9 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"
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