theme / start.sh
eaglercraft's picture
Update start.sh
464e12f verified
Raw
History Blame Contribute Delete
13.4 kB
#!/bin/bash
set -euo pipefail
JAVA11="/usr/lib/jvm/temurin-11-jre-amd64/bin/java"
JAVA17="/usr/lib/jvm/java-17-openjdk-amd64/bin/java"
BUNGEE_DIR="/opt/server/bungee"
BACKEND_DIR="/opt/server/backend"
WORLD_DIR="$BACKEND_DIR/world"
PAPER_PIPE="$BACKEND_DIR/paper_input"
TOTAL_MEM_MB=$(free -m | awk '/^Mem:/{print $2}')
BUNGEE_MAX_MB=192
PAPER_MAX_MB=$(( TOTAL_MEM_MB - BUNGEE_MAX_MB - 768 ))
[ "$PAPER_MAX_MB" -gt 4096 ] && PAPER_MAX_MB=4096
[ "$PAPER_MAX_MB" -lt 1024 ] && PAPER_MAX_MB=1024
PAPER_MIN_MB=1024
CPU_CORES=$(nproc 2>/dev/null || echo 2)
GC_THREADS=$CPU_CORES
CONC_GC=$(( CPU_CORES / 2 ))
[ "$CONC_GC" -lt 1 ] && CONC_GC=1
NETTY_THREADS=$CPU_CORES
echo "============================================"
echo " Eaglercraft 1.8.8 β€” Vanilla"
echo " Mem: ${PAPER_MIN_MB}-${PAPER_MAX_MB}MB | CPUs: ${CPU_CORES}"
echo " Setup UI: port 7860 (only if no world)"
echo "============================================"
PAPER_JVM_FLAGS=(
-Xmx${PAPER_MAX_MB}M -Xms${PAPER_MIN_MB}M
-XX:+UseG1GC
-XX:+ParallelRefProcEnabled
-XX:MaxGCPauseMillis=15
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:G1NewSizePercent=40
-XX:G1MaxNewSizePercent=50
-XX:G1HeapRegionSize=16M
-XX:G1ReservePercent=20
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=50
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
-XX:+OptimizeStringConcat
-XX:+UseCompressedOops
-XX:+AlwaysPreTouch
-XX:ParallelGCThreads=${GC_THREADS}
-XX:ConcGCThreads=${CONC_GC}
-XX:MaxMetaspaceSize=256M
-XX:CompressedClassSpaceSize=128M
-XX:ReservedCodeCacheSize=128M
-XX:-UseCodeCacheFlushing
-Xss1m
-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
-Dspigot.disable-firewall-check=true
-Djava.net.preferIPv4Stack=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
-Xss1m
-XX:ParallelGCThreads=${GC_THREADS}
-XX:ConcGCThreads=${CONC_GC}
-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}
-Djava.net.preferIPv4Stack=true
)
PAPER_PID=""
BUNGEE_PID=""
WEB_PID=""
NGINX_PID=""
echo "127.0.0.1 checkip.amazonaws.com" >> /etc/hosts
echo "127.0.0.1 api.ipify.org" >> /etc/hosts
echo "127.0.0.1 icanhazip.com" >> /etc/hosts
echo "127.0.0.1 portcheck.twizmwazin.com" >> /etc/hosts
cleanup() {
echo ""
echo "[SHUTDOWN] Saving world to HuggingFace..."
python3 /opt/server/sync_world.py upload 2>/dev/null || true
echo "[SHUTDOWN] Stopping WindSpigot..."
if [ -p "$PAPER_PIPE" ]; then
echo "stop" >&3 2>/dev/null || true
fi
sleep 5
[ -n "$PAPER_PID" ] && kill "$PAPER_PID" 2>/dev/null || true
[ -n "$BUNGEE_PID" ] && kill "$BUNGEE_PID" 2>/dev/null || true
[ -n "$WEB_PID" ] && kill "$WEB_PID" 2>/dev/null || true
[ -n "$NGINX_PID" ] && kill "$NGINX_PID" 2>/dev/null || true
exec 3>&- 2>/dev/null || true
wait 2>/dev/null || true
}
trap cleanup SIGTERM SIGINT EXIT
echo "[0/7] Starting nginx (port 7860) and web panel (port 5000)..."
nginx -g "daemon off;" >> /tmp/nginx_error.log 2>&1 &
NGINX_PID=$!
python3 /opt/server/web.py >> /tmp/web.log 2>&1 &
WEB_PID=$!
echo " nginx PID=$NGINX_PID web PID=$WEB_PID"
sleep 1
echo "[1/7] Downloading world from HuggingFace..."
python3 /opt/server/sync_world.py download 2>&1
WORLD_OK=$?
if [ "$WORLD_OK" -ne 0 ] || [ ! -d "$WORLD_DIR" ]; then
echo ""
echo "╔══════════════════════════════════════════╗"
echo "β•‘ NO WORLD FOUND β€” Setup UI is active β•‘"
echo "β•‘ Open port 7860 and upload world.zip β•‘"
echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"
touch /tmp/setup_mode
wait "$WEB_PID"
exit 0
fi
rm -f /tmp/setup_mode
echo "[2/7] Generating server icon..."
if command -v convert &>/dev/null; then
if [ -f /etc/ImageMagick-6/policy.xml ]; then
sed -i 's/rights="none" pattern="PNG"/rights="read|write" pattern="PNG"/' \
/etc/ImageMagick-6/policy.xml
fi
convert -size 64x64 xc:"#1a1a2e" \
\( -size 64x64 gradient:"#1a1a2e"-"#16213e" \) -composite \
-fill "#fffde7" -draw "circle 52,10 58,10" \
-fill "#16213e" -draw "circle 55,8 61,8" \
-fill "#ffffff" \
-draw "point 10,5" -draw "point 25,3" \
-draw "point 40,8" -draw "point 15,15" \
-draw "point 35,12" -draw "point 5,20" \
-fill "#4caf50" -draw "rectangle 0,48 63,63" \
-fill none -stroke "#000000" -strokewidth 1 \
-draw "rectangle 0,0 63,63" \
PNG:"$BACKEND_DIR/server-icon.png" \
&& cp "$BACKEND_DIR/server-icon.png" "$BUNGEE_DIR/server-icon.png" \
&& echo " Icon generated." \
|| echo " Warning: icon generation failed."
else
echo " Warning: ImageMagick not found."
fi
echo "[3/7] Writing backend config..."
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=50
view-distance=8
motd=\u00A7aEaglercraft 1.8.8
pvp=true
allow-flight=false
level-name=world
gamemode=2
default-player-permission-level=visitor
difficulty=1
use-native-transport=true
network-compression-threshold=-1
max-tick-time=-1
generate-structures=true
allow-nether=true
spawn-animals=true
spawn-monsters=true
prevent-proxy-connections=false
enable-query=false
enable-rcon=false
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
check-firewall: false
commands:
tab-complete: 0
log: false
world-settings:
default:
verbose: false
view-distance: 8
mob-spawn-range: 4
ticks-per:
hopper-transfer: 8
hopper-check: 1
arrow-despawn-rate: 300
item-despawn-rate: 6000
EOF
cat > bukkit.yml << 'EOF'
settings:
connection-throttle: -1
allow-end: true
spawn-npcs: true
spawn-animals: true
spawn-monsters: true
chunk-gc:
period-in-ticks: 600
ticks-per:
animal-spawns: 400
monster-spawns: 1
autosave: 6000
spawn:
location:
world: world
x: 0.0
y: 64.0
z: 0.0
yaw: 0.0
pitch: 0.0
EOF
python3 -c "
import uuid, json
players = [
{'name': 'Xx_N01i_xX', 'level': 4},
{'name': 'EXCUSE_ME_SIR', 'level': 4}
]
ops = []
for p in players:
# Offline-mode UUID = UUID v3 of 'OfflinePlayer:<name>'
offline_uuid = uuid.uuid3(uuid.NAMESPACE_URL, 'OfflinePlayer:' + p['name'])
# Minecraft uses UUID v3 but with a specific namespace
# Actually the correct method:
import hashlib
h = hashlib.md5(('OfflinePlayer:' + p['name']).encode('utf-8')).digest()
b = bytearray(h)
b[6] = (b[6] & 0x0f) | 0x30 # version 3
b[8] = (b[8] & 0x3f) | 0x80 # variant 1
u = uuid.UUID(bytes=bytes(b))
ops.append({
'uuid': str(u),
'name': p['name'],
'level': p['level'],
'bypassesPlayerLimit': False
})
print(f\" {p['name']} -> {u}\")
with open('ops.json', 'w') as f:
json.dump(ops, f, indent=2)
print(' ops.json written!')
" 2>&1
echo "[4/7] Writing BungeeCord config..."
cd "$BUNGEE_DIR"
cat > config.yml << 'EOF'
server_connect_timeout: 5000
player_limit: 50
permissions:
default:
- bungeecord.command.server
- bungeecord.command.list
timeout: 30000
online_mode: false
throttle: 4000
throttle_limit: 3
servers:
main:
motd: '&aEaglercraft 1.8.8'
address: 127.0.0.1:25565
restricted: false
listeners:
- query_port: 25567
motd: '&aEaglercraft 1.8.8'
tab_list: GLOBAL_PING
query_enabled: false
proxy_protocol: false
priorities:
- main
bind_local_address: true
host: 127.0.0.1:25567
max_players: 50
force_default_server: true
ip_forward: true
connection_throttle: -1
stats: none
log_commands: false
log_pings: false
network_compression_threshold: -1
EOF
echo "[5/7] Generating EaglerXServer config..."
rm -rf \
"$BUNGEE_DIR/plugins/EaglercraftXServer" \
"$BUNGEE_DIR/plugins/EaglercraftXBungee" \
"$BUNGEE_DIR/plugins/EaglerXServer"
> /tmp/bungee-gen.log
timeout 20s $JAVA17 "${BUNGEE_JVM_FLAGS[@]}" \
-cp "sqlite-jdbc.jar:BungeeCord.jar" net.md_5.bungee.Bootstrap \
>> /tmp/bungee-gen.log 2>&1 || true
pkill -f 'net.md_5.bungee.Bootstrap' 2>/dev/null || true
sleep 3
EAGLER_CFG="$(find "$BUNGEE_DIR/plugins" -maxdepth 2 -name listeners.yml | head -n1 || true)"
if [ -z "$EAGLER_CFG" ]; then
echo " ERROR: EaglerXServer did not generate listeners.yml"
echo " Last 120 lines of bungee-gen.log:"
tail -120 /tmp/bungee-gen.log
echo " Files under plugins/:"
find "$BUNGEE_DIR/plugins" -maxdepth 2 -type f | sort
exit 1
fi
sed -i 's|inject_address:.*|inject_address: "127.0.0.1:25567"|' "$EAGLER_CFG" || true
echo " Using Eagler config: $EAGLER_CFG"
echo "[6/7] Starting WindSpigot (backend)..."
cd "$BACKEND_DIR"
rm -f "$PAPER_PIPE"
mkfifo "$PAPER_PIPE"
> /tmp/paper.log
$JAVA11 "${PAPER_JVM_FLAGS[@]}" \
-jar server.jar nogui \
< "$PAPER_PIPE" >> /tmp/paper.log 2>&1 &
PAPER_PID=$!
sleep 1
exec 3>"$PAPER_PIPE"
echo " WindSpigot PID: $PAPER_PID"
echo " Waiting for WindSpigot to be ready (up to 120s)..."
READY=0
for i in $(seq 1 120); do
if grep -q "Done" /tmp/paper.log 2>/dev/null; then
READY=1
echo " WindSpigot READY! (${i}s)"
break
fi
if ! kill -0 "$PAPER_PID" 2>/dev/null; then
echo " ERROR: WindSpigot died early"
tail -60 /tmp/paper.log
exit 1
fi
[ $(( i % 20 )) -eq 0 ] && echo " Still waiting... (${i}s)" && tail -5 /tmp/paper.log
sleep 1
done
if [ "$READY" -eq 0 ]; then
echo " WARNING: WindSpigot did not print Done within 120s"
tail -20 /tmp/paper.log
fi
sleep 2
echo "[7/7] Starting BungeeCord + EaglerXServer..."
cd "$BUNGEE_DIR"
> /tmp/bungee.log
$JAVA17 "${BUNGEE_JVM_FLAGS[@]}" \
-cp "sqlite-jdbc.jar:BungeeCord.jar" net.md_5.bungee.Bootstrap \
>> /tmp/bungee.log 2>&1 &
BUNGEE_PID=$!
echo " BungeeCord PID: $BUNGEE_PID"
echo " Waiting for Bungee/Eagler listener on 25567 (up to 30s)..."
WS_READY=0
for i in $(seq 1 30); do
if nc -z 127.0.0.1 25567 2>/dev/null; then
WS_READY=1
echo " Eagler listener READY! (${i}s)"
break
fi
if ! kill -0 "$BUNGEE_PID" 2>/dev/null; then
echo " ERROR: BungeeCord died early"
tail -80 /tmp/bungee.log
exit 1
fi
sleep 1
done
if [ "$WS_READY" -eq 0 ]; then
echo " ERROR: 25567 never opened"
echo " Last 120 lines of bungee.log:"
tail -120 /tmp/bungee.log
exit 1
fi
echo ""
echo "============================================"
echo " SERVER STATUS"
echo "============================================"
echo " WindSpigot :25565 $(nc -z 127.0.0.1 25565 2>/dev/null && echo 'βœ“ ONLINE' || echo 'βœ— OFFLINE')"
echo " Eagler/Bungee:25567 $(nc -z 127.0.0.1 25567 2>/dev/null && echo 'βœ“ ONLINE' || echo 'βœ— OFFLINE')"
echo " Nginx :7860 $(nc -z 127.0.0.1 7860 2>/dev/null && echo 'βœ“ ONLINE' || echo 'βœ— OFFLINE')"
echo " Flask :5000 $(nc -z 127.0.0.1 5000 2>/dev/null && echo 'βœ“ ONLINE' || echo 'βœ— OFFLINE')"
echo "============================================"
echo " JOIN URL: wss://YOUR-ACTUAL-SPACE.hf.space/ws/"
echo "============================================"
echo ""
LAST_LOG_LINE=0
LAST_SAVE_TIME=$(date +%s)
SAVE_INTERVAL=300
LOOP_COUNT=0
while true; do
LOOP_COUNT=$(( LOOP_COUNT + 1 ))
sleep 10
if [ $(( LOOP_COUNT % 6 )) -eq 0 ]; then
CUR=$(wc -l < /tmp/paper.log 2>/dev/null || echo 0)
if [ "$CUR" -gt "$LAST_LOG_LINE" ]; then
ERR=$(tail -n +"$(( LAST_LOG_LINE + 1 ))" /tmp/paper.log | grep -c "ERROR\|SEVERE" 2>/dev/null || echo 0)
[ "$ERR" -gt 0 ] && echo "[$(date '+%H:%M:%S')] $ERR new error(s) in paper.log"
LAST_LOG_LINE=$CUR
fi
fi
NOW=$(date +%s)
if [ $(( NOW - LAST_SAVE_TIME )) -ge "$SAVE_INTERVAL" ]; then
echo "[$(date '+%H:%M:%S')] Autosaving world to HuggingFace..."
echo "save-all" >&3 2>/dev/null || true
sleep 3
python3 /opt/server/sync_world.py upload 2>&1 &
LAST_SAVE_TIME=$NOW
fi
done