| ; | |
| 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: #1e1e1e; color: #d4d4d4; | |
| font-family: 'Consolas', monospace; | |
| display: flex; flex-direction: column; | |
| height: 100vh; padding: 16px; gap: 12px; | |
| } | |
| h2 { color: #569cd6; font-size: 1.1rem; } | |
| #logs { | |
| flex: 1; overflow-y: auto; white-space: pre-wrap; word-break: break-all; | |
| background: #0d0d0d; padding: 12px; | |
| border: 1px solid #333; border-radius: 4px; | |
| font-size: 0.82rem; line-height: 1.45; | |
| } | |
| form { display: flex; gap: 8px; } | |
| input[type=text] { | |
| flex: 1; padding: 9px 12px; | |
| background: #2d2d2d; color: #fff; | |
| border: 1px solid #444; border-radius: 4px; font-family: inherit; | |
| } | |
| button { | |
| padding: 9px 20px; background: #0e639c; color: #fff; | |
| border: none; border-radius: 4px; cursor: pointer; font-family: inherit; | |
| } | |
| button:hover { background: #1177bb; } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>▶ Eaglercraft Live Console</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}`); | |
| }); | |
Xet Storage Details
- Size:
- 6.12 kB
- Xet hash:
- d992d36e79a1c66f2949a0bfd4068251744b654f3551e3baef53a9be2e65eade
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.