| #!/bin/bash |
| set -e |
|
|
| |
| |
| |
| if [ -d "/data" ]; then |
| echo "[System] Persistent /data bucket found. Taking ownership..." |
| sudo chown -R minecraft:minecraft /data 2>/dev/null || true |
| sudo chmod -R 775 /data 2>/dev/null || true |
| cd /data |
| else |
| echo "[Warning] No /data bucket found. World will reset on restart!" |
| mkdir -p /app/server |
| cd /app/server |
| fi |
|
|
| WORKDIR=$(pwd) |
| echo "[System] Working directory: $WORKDIR" |
|
|
| |
| |
| |
| echo "[System] Cleaning up old jars..." |
| |
| rm -f plugins/Via*.jar |
| rm -f plugins/EaglerX*.jar |
| rm -f server.jar |
|
|
| echo "[System] Clearing Paperclip cache..." |
| rm -rf cache |
|
|
| mkdir -p logs |
| mkdir -p plugins/EaglercraftXServer |
|
|
| |
| |
| |
| echo "[System] Installing WebSocket proxy dependencies..." |
| npm install http-proxy --save 2>&1 | tail -1 |
|
|
| |
| |
| |
| echo "[System] Downloading Paper 1.12.2..." |
| curl -fsSL \ |
| -A "Mozilla/5.0" \ |
| -o server.jar \ |
| "https://api.papermc.io/v2/projects/paper/versions/1.12.2/builds/1620/downloads/paper-1.12.2-1620.jar" |
|
|
| if [ ! -s server.jar ]; then |
| echo "[Error] server.jar download failed or is empty. Aborting." |
| exit 1 |
| fi |
| echo "[System] server.jar downloaded successfully ($(du -sh server.jar | cut -f1))." |
|
|
| |
| |
| |
| echo "[System] Downloading EaglerXServer & EaglerXRewind..." |
| curl -fsSL -A "Mozilla/5.0" \ |
| -o plugins/EaglerXServer.jar \ |
| "https://github.com/lax1dude/eaglerxserver/releases/download/v1.0.8/EaglerXServer.jar" |
|
|
| curl -fsSL -A "Mozilla/5.0" \ |
| -o plugins/EaglerXRewind.jar \ |
| "https://github.com/lax1dude/eaglerxserver/releases/download/v1.0.8/EaglerXRewind.jar" |
|
|
| echo "[System] Downloading ViaVersion suite..." |
| curl -fsSL -A "Mozilla/5.0" \ |
| -o plugins/ViaVersion.jar \ |
| "https://github.com/ViaVersion/ViaVersion/releases/download/5.8.1/ViaVersion-5.8.1.jar" |
|
|
| curl -fsSL -A "Mozilla/5.0" \ |
| -o plugins/ViaBackwards.jar \ |
| "https://cdn.modrinth.com/data/NpvuJQoq/versions/8YdMHUIl/ViaBackwards-5.9.0-SNAPSHOT.jar" |
|
|
| curl -fsSL -A "Mozilla/5.0" \ |
| -o plugins/ViaRewind.jar \ |
| "https://cdn.modrinth.com/data/TbHIxhx5/versions/r3AZ5yTY/ViaRewind-4.1.0-SNAPSHOT.jar" |
|
|
| |
| echo "[System] Verifying plugin downloads..." |
| for jar in \ |
| plugins/EaglerXServer.jar \ |
| plugins/EaglerXRewind.jar \ |
| plugins/ViaVersion.jar \ |
| plugins/ViaBackwards.jar \ |
| plugins/ViaRewind.jar |
| do |
| if [ ! -s "$jar" ]; then |
| echo "[Error] $jar is missing or empty! Aborting." |
| exit 1 |
| fi |
| echo " [OK] $jar ($(du -sh "$jar" | cut -f1))" |
| done |
|
|
| |
| |
| |
| echo "[System] Writing eula.txt and server.properties..." |
| echo "eula=true" > eula.txt |
|
|
| cat > server.properties << 'PROPS' |
| server-port=25565 |
| online-mode=false |
| motd=Eaglercraft Server |
| max-players=60 |
| PROPS |
|
|
| |
| |
| |
| echo "[System] Writing EaglercraftXServer listener config..." |
| cat > plugins/EaglercraftXServer/listeners.yml << 'LISTENEOF' |
| listeners: |
| - address: 127.0.0.1:25566 |
| motd: "&dEaglercraftX\n&ePowered by EaglerXServer" |
| max_players: 60 |
| online_mode: false |
| forward_ip: true |
| LISTENEOF |
|
|
| |
| |
| |
| echo "[System] Writing webconsole.js..." |
| cat > webconsole.js << 'NODEEOF' |
| 'use strict'; |
|
|
| const http = require('http'); |
| const fs = require('fs'); |
| const path = require('path'); |
| const { spawn, exec } = require('child_process'); |
| const httpProxy = require('http-proxy'); |
|
|
| const LOG_FILE = path.join(__dirname, 'logs', 'latest.log'); |
| const MC_PORT = 25566; // EaglerXServer internal WS port |
| const WEB_PORT = 7860; // Exposed port |
|
|
| // ββ Banner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| console.log('========================================='); |
| console.log(' Eaglercraft Server + Web Console '); |
| console.log('========================================='); |
|
|
| // ββ Start Minecraft βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| const mcProcess = spawn('java', [ |
| '-Xms2G', '-Xmx2G', |
| '-XX:+UseG1GC', |
| '-jar', 'server.jar', 'nogui' |
| ], { cwd: __dirname }); |
|
|
| const logStream = fs.createWriteStream(LOG_FILE, { flags: 'w' }); |
|
|
| mcProcess.stdout.pipe(logStream, { end: false }); |
| mcProcess.stderr.pipe(logStream, { end: false }); |
| mcProcess.stdout.pipe(process.stdout); |
| mcProcess.stderr.pipe(process.stderr); |
|
|
| mcProcess.on('exit', (code) => { |
| console.log(`[MC] Server process exited with code ${code}`); |
| logStream.end(); |
| }); |
|
|
| // ββ WebSocket Proxy βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| const proxy = httpProxy.createProxyServer({ |
| target: `ws://127.0.0.1:${MC_PORT}`, |
| ws: true |
| }); |
|
|
| proxy.on('error', (err, req, socket) => { |
| console.warn('[Proxy] Routing error (server may still be booting):', err.message); |
| if (socket && socket.writable) socket.destroy(); |
| }); |
|
|
| // ββ HTML Page βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| const PAGE_HTML = `<!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Eaglercraft Console</title> |
| <style> |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| body { |
| background: |
| font-family: 'Consolas', monospace; |
| display: flex; flex-direction: column; |
| height: 100vh; padding: 16px; gap: 12px; |
| } |
| h2 { color: |
| |
| flex: 1; overflow-y: auto; white-space: pre-wrap; word-break: break-all; |
| background: |
| border: 1px solid |
| font-size: 0.82rem; line-height: 1.45; |
| } |
| form { display: flex; gap: 8px; } |
| input[type=text] { |
| flex: 1; padding: 9px 12px; |
| background: |
| border: 1px solid |
| } |
| button { |
| padding: 9px 20px; background: |
| border: none; border-radius: 4px; cursor: pointer; font-family: inherit; |
| } |
| button:hover { background: |
| </style> |
| </head> |
| <body> |
| <h2>& |
| <div id="logs">Connecting to server logβ¦</div> |
| <form action="/command" method="POST" target="_blank" |
| onsubmit="setTimeout(()=>this.reset(),10)"> |
| <input type="text" name="cmd" |
| placeholder="e.g. op YourName | say Hello!" |
| autocomplete="off" autofocus> |
| <button type="submit">Send</button> |
| </form> |
| <script> |
| const logs = document.getElementById('logs'); |
| let last = ''; |
| async function poll() { |
| try { |
| const t = await fetch('/logs').then(r => r.text()); |
| if (t !== last) { |
| last = t; |
| const atBottom = |
| logs.scrollHeight - logs.clientHeight <= logs.scrollTop + 60; |
| logs.textContent = t; |
| if (atBottom) logs.scrollTop = logs.scrollHeight; |
| } |
| } catch(_) {} |
| setTimeout(poll, 1000); |
| } |
| poll(); |
| </script> |
| </body> |
| </html>`; |
|
|
| // ββ HTTP Server βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| const server = http.createServer((req, res) => { |
|
|
| // ββ GET / β console page |
| if (req.method === 'GET' && req.url === '/') { |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); |
| return res.end(PAGE_HTML); |
| } |
|
|
| // ββ GET /logs β last 200 lines of log |
| if (req.method === 'GET' && req.url === '/logs') { |
| exec(`tail -n 200 "${LOG_FILE}"`, (err, stdout) => { |
| res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); |
| res.end(stdout || '(Waiting for server outputβ¦)'); |
| }); |
| return; |
| } |
|
|
| // ββ POST /command β write to MC stdin |
| if (req.method === 'POST' && req.url === '/command') { |
| let body = ''; |
| req.on('data', chunk => { body += chunk; }); |
| req.on('end', () => { |
| const cmd = new URLSearchParams(body).get('cmd') || ''; |
| const safe = cmd.trim().replace(/[\r\n]+/g, ''); // no newline injection |
| if (safe && mcProcess.stdin.writable) { |
| mcProcess.stdin.write(safe + '\n'); |
| console.log(`[Console] Command sent: ${safe}`); |
| } |
| res.writeHead(200, { 'Content-Type': 'text/plain' }); |
| res.end('ok'); |
| }); |
| return; |
| } |
|
|
| res.writeHead(404); |
| res.end('Not found'); |
| }); |
|
|
| // ββ Upgrade β proxy WebSocket to Eaglercraft |
| server.on('upgrade', (req, socket, head) => { |
| console.log(`[Proxy] WS upgrade from ${socket.remoteAddress} β MC:${MC_PORT}`); |
| proxy.ws(req, socket, head); |
| }); |
|
|
| server.listen(WEB_PORT, '0.0.0.0', () => { |
| console.log(`[WebConsole] Listening on http://0.0.0.0:${WEB_PORT}`); |
| }); |
| NODEEOF |
|
|
| # βββββββββββββββββββββββββββββββββββββββββββββ |
| # 9. Launch |
| # βββββββββββββββββββββββββββββββββββββββββββββ |
| echo "[System] Handing off to Node.js master controller..." |
| exec node webconsole.js |