├── config.json ├── docker ├── .dockerignore ├── Dockerfile ├── .gitignore ├── package.json ├── tsconfig.json ├── index.html └── server.ts ├── start.bat ├── start.sh ├── requirements.txt ├── README.md └── autoeagler.py /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "", 3 | "codespaces": false 4 | } 5 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !index.html 3 | !package.json 4 | !server.ts 5 | !tsconfig.json 6 | !yarn.lock 7 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun:alpine 2 | RUN apk add --no-cache openjdk8-jre 3 | WORKDIR /app 4 | ADD . /app 5 | ENV PORT=80 6 | CMD ["/usr/local/bin/bun", "server.ts"] -------------------------------------------------------------------------------- /docker/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !.dockerignore 3 | !.gitignore 4 | !Dockerfile 5 | !index.html 6 | !package.json 7 | !server.ts 8 | !tsconfig.json 9 | !yarn.lock 10 | 11 | !eula.txt 12 | -------------------------------------------------------------------------------- /docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autoeagler-webserver", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "clean": "rm -rf bungee spigot", 7 | "dev": "bun --watch server.ts", 8 | "docker": "docker buildx build . -t thgh/autoeagler-webserver --platform=linux/amd64,linux/arm64/v8 --push", 9 | "drun": "docker run --rm -it -e PORT=24025 -p 24025:24025 thgh/autoeagler-webserver" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@types/bun": "^1.0.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | title "Checking software ..." 3 | 4 | where python3 >nul 2>nul 5 | if %errorlevel%==1 ( 6 | @echo Python not found, please install Python 3 from https://python.org. 7 | pause 8 | exit 0 9 | ) 10 | 11 | where java >nul 2>nul 12 | if %errorlevel%==1 ( 13 | @echo Java not found, please install Java 8 from https://java.com 14 | pause 15 | exit 0 16 | ) 17 | 18 | python3 -m pip install -r requirements.txt 19 | 20 | title "Running ..." 21 | 22 | python3 autoeagler.py 23 | 24 | echo Script exited. 25 | 26 | timeout 5 -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Checking software ..." 4 | 5 | if ! command -v python3 >/dev/null 2>&1; then 6 | echo "Python not found, please install Python 3 from https://python.org." 7 | read -p "Press any key to continue..." 8 | exit 0 9 | fi 10 | 11 | if ! command -v java >/dev/null 2>&1; then 12 | echo "Java not found, please install Java 8 from https://java.com" 13 | read -p "Press any key to continue..." 14 | exit 0 15 | fi 16 | 17 | pip3 install -r requirements.txt 18 | 19 | echo "Running ..." 20 | 21 | python3 autoeagler.py 22 | 23 | echo "Script exited." 24 | 25 | sleep 5 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile 6 | # 7 | certifi==2023.11.17 8 | # via requests 9 | charset-normalizer==3.3.2 10 | # via requests 11 | idna==3.6 12 | # via requests 13 | psutil==5.9.6 14 | # via -r requirements.in 15 | pygetwindow==0.0.9 16 | # via -r requirements.in 17 | pyngrok==7.0.3 18 | # via -r requirements.in 19 | pyrect==0.2.0 20 | # via pygetwindow 21 | pyyaml==6.0.1 22 | # via pyngrok 23 | requests==2.31.0 24 | # via -r requirements.in 25 | urllib3==2.1.0 26 | # via requests 27 | -------------------------------------------------------------------------------- /docker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // enable latest features 4 | "lib": ["ESNext"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", // support JSX 9 | "allowJs": true, // allow importing `.js` from `.ts` 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | 24 | // Some stricter flags 25 | "useUnknownInCatchVariables": true, 26 | "noPropertyAccessFromIndexSignature": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ARCHIVED] 2 | Websites dissapeared making the installer useless. 3 | Singleplayer is released on 1.8.8 making the idea useless. 4 | I officially declare this project as dead. 5 | Feel free to use the code for your own project. 6 | 7 | # AutoEagler 8 | A tool to automatically set up a localhost eaglerXbungee, aiming to provide an alternative to Eaglercraft's singleplayer 9 | 10 | ## Progress: 11 | ✅ Local + NGROK tunneling 12 | 13 | ✅ NGROK customisation 14 | 15 | ✅ Server customisation (gamemode, seed, etc) 16 | 17 | ✅ Multiple versions (1.3_beta + 1.5.2 + 1.8.8) 18 | 19 | 🟠 Docker deploy 20 | 21 | ### Installation (manual) 22 | 23 | 1. Download and install the latest [Python](https://python.org) release 24 | 3. Install [Java 8](https://java.com/download/) 25 | 4. Download [the latest release](https://github.com/wxnnvs/AutoEagler/releases/latest) to a dedicated folder 26 | 4. Unzip the zip file 27 | 5. Run `start.bat` in CMD 28 | 6. Open up option `1` 29 | 7. Let it run for a while, it might pop up some windows, you're done when the main menu is back 30 | 8. Done! 31 | 32 | ## Usage: 33 | 34 | ### Local server 35 | 36 | 1. Run `python3 autoeagler.py` or `start.bat` in CMD 37 | 2. Open up option `2` 38 | 3. Let it run for a minute, it might pop up some windows 39 | 4. Join on `ws://localhost:8081` using an offline download 40 | 5. Press `[Enter]` to return to the menu 41 | 6. Open up option `5` to shut it down and close the program 42 | 43 | ### Public server (ngrok) 44 | 45 | 1. Run `python3 autoeagler.py` or `start.bat` in CMD 46 | 2. Open up option `3` 47 | 3. Let it run for a minute, it might pop up some windows 48 | 4. Join on `wss://.ngrok.io` using any client (link will be showed) 49 | 5. Press `[Enter]` to return to the menu 50 | 6. Open up option `5` to shut it down and close the program 51 | 52 | ### Development server 53 | 54 | 1. Install [Docker](https://www.docker.com/products/docker-desktop) and [Bun](https://bun.sh) 55 | 2. Run `cd docker` to enter the docker folder 56 | 3. Run `bun install` to install the dependencies 57 | 4. Run `bun dev` to start the server 58 | 5. Open your browser and go to `http://localhost:6543` 59 | 60 | ### Development server on Windows 61 | 62 | 1. Install [Docker](https://www.docker.com/products/docker-desktop) 63 | 2. Run `cd docker` to enter the docker folder 64 | 3. Run the image `docker run --rm -it -e PORT=6543 -p 6543:6543 -v .:/app thgh/autoeagler-webserver bun dev` 65 | 4. Open your browser and go to `http://localhost:6543` 66 | 67 | ### Publish to Docker Hub 68 | 69 | 1. Run `docker buildx build . -t thgh/autoeagler-webserver --platform=linux/amd64,linux/arm64/v8 --push` to build and push the docker image 70 | 2. Run `bun drun` to test if the docker image is working 71 | 72 | # For Mojang: 73 | 74 | This tool does **NOT** include any of the source code from Minecraft, MCP, or any other illegal/copyrighted resources, nor any info on how to get it. 75 | -------------------------------------------------------------------------------- /docker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AutoEagler Webserver 5 | 6 | 7 | 146 | 150 | 151 | 152 |
161 | AutoEagler Webserver 162 |
163 |
164 |
165 |
166 | Spigot 167 | 191 | 198 | 205 | 212 | 219 | 220 |
221 |
222 |

223 |           
224 |
225 |
226 | 227 | 228 |
229 |
230 |
231 |
232 | Bungee 233 | 240 | 247 |
248 |
249 |

250 |           

251 |         
252 |
253 | 254 | 255 |
256 |
257 |
258 | 374 | 375 | 376 | -------------------------------------------------------------------------------- /docker/server.ts: -------------------------------------------------------------------------------- 1 | import { $, type ServerWebSocket, type Subprocess } from 'bun' 2 | import fs from 'fs'; 3 | import util from 'util'; 4 | 5 | /** These sockets will receive any changes to the server state */ 6 | const sockets = new Set>() 7 | /** Port for webserver */ 8 | const PORT = parseInt(process.env['PORT']!) || 6543 9 | /** Public URL for webserver, if proxied through Docker for example */ 10 | const PUBLIC_URL = process.env['PUBLIC_URL'] || 'http://localhost:' + PORT 11 | 12 | /** Read and write file */ 13 | const readFile = util.promisify(fs.readFile); 14 | const writeFile = util.promisify(fs.writeFile); 15 | 16 | /** overwrite config */ 17 | async function overwriteConfig(file_path: string, target: string, replacement: string) { 18 | const data = await readFile(file_path, 'utf-8'); 19 | const lines = data.split('\n'); 20 | const updatedLines = lines.map(line => line.trim() === target ? replacement : line); 21 | const updatedData = updatedLines.join('\n'); 22 | await writeFile(file_path, updatedData, 'utf-8'); 23 | } 24 | 25 | Bun.serve({ 26 | port: PORT, 27 | async fetch(req, server) { 28 | if (server.upgrade(req)) return 29 | 30 | // Application frontend 31 | const url = new URL(req.url) 32 | if (!url.pathname.startsWith('/api')) 33 | return new Response(Bun.file('./index.html')) 34 | 35 | if (url.pathname.startsWith('/api/set')) return set(await req.json()) 36 | 37 | const cmd = url.searchParams.get('cmd') 38 | const sub = (url.searchParams.get('sub') as 'spigot' | 'bungee') || 'spigot' 39 | 40 | // Custom commands 41 | if (cmd === 'reset') return reset() 42 | if (cmd === 'start') return start(sub) 43 | if (cmd === 'stop') return stop(sub) 44 | 45 | // Any other command 46 | if (cmd) return command(cmd, sub) 47 | 48 | console.log('🔴 Not found:', url.pathname) 49 | 50 | return Response.json({ message: 'Not found' }, { status: 404 }) 51 | }, 52 | websocket: { 53 | open(ws) { 54 | // Sync the initial state 55 | ws.sendText( 56 | JSON.stringify( 57 | Object.fromEntries( 58 | Object.entries(state).filter(([k]) => interesting.includes(k)) 59 | ) 60 | ) 61 | ) 62 | 63 | // Sync further changes 64 | sockets.add(ws) 65 | 66 | // The first client initializes the server 67 | // if (state.spigotStatus === 'init' && state.spigotURL) { 68 | // console.log('📦 Initialize spigot') 69 | // spawn('spigot') 70 | // } 71 | // if (state.bungeeStatus === 'init' && state.bungeeURL) { 72 | // console.log('📦 Initialize bungee') 73 | // spawn('bungee') 74 | // } 75 | }, 76 | // this is called when a message is received 77 | async message(ws, message) { 78 | console.log('📦 ' + `Received ${message}`) 79 | }, 80 | }, 81 | }) 82 | console.log('📦 EaglerCraft control panel is listening on', PUBLIC_URL) 83 | 84 | /** Server state */ 85 | const _state: { 86 | bungee?: Subprocess<'pipe', 'pipe', 'pipe'> 87 | bungeeLog: string 88 | bungeeStatus: 'init' | 'starting' | 'started' | 'stopping' | 'stopped' 89 | bungeeURL: string 90 | bungeeCommand: string[] 91 | spigot?: Subprocess<'pipe', 'pipe', 'pipe'> 92 | spigotLog: string 93 | spigotStatus: 'init' | 'starting' | 'started' | 'stopping' | 'stopped' 94 | spigotURL: string 95 | spigotCommand: string[] 96 | } = { 97 | bungeeLog: '', 98 | bungeeStatus: 'init', 99 | bungeeURL: 100 | 'https://api.papermc.io/v2/projects/waterfall/versions/1.20/builds/556/downloads/waterfall-1.20-556.jar', 101 | bungeeCommand: ['/usr/bin/java', '-Xms64M', '-Xmx64M', '-jar', 'bungee.jar'], 102 | spigotLog: '', 103 | spigotStatus: 'init', 104 | spigotURL: 105 | 'https://cdn.getbukkit.org/spigot/spigot-1.8.8-R0.1-SNAPSHOT-latest.jar', 106 | spigotCommand: ['/usr/bin/java', '-Xms2G', '-Xmx2G', '-jar', 'spigot.jar'], 107 | } 108 | 109 | /** Properties of server state that are interesting for clients */ 110 | const interesting = [ 111 | 'bungeeCommand', 112 | 'bungeeLog', 113 | 'bungeeStatus', 114 | 'bungeeURL', 115 | 'spigotCommand', 116 | 'spigotLog', 117 | 'spigotStatus', 118 | 'spigotURL', 119 | ] 120 | 121 | /** Sync any changes to the server state to all clients */ 122 | const state = new Proxy(_state, { 123 | set(target, prop, value) { 124 | if (interesting.includes(prop as string)) { 125 | sockets.forEach((ws) => ws.sendText(JSON.stringify({ [prop]: value }))) 126 | } 127 | // @ts-expect-error 128 | target[prop] = value 129 | return true 130 | }, 131 | }) 132 | 133 | /** Properties of server state that are interesting for clients */ 134 | const settable = [ 135 | 'bungeeCommand', 136 | 'bungeeLog', 137 | 'bungeeURL', 138 | 'spigotCommand', 139 | 'spigotLog', 140 | 'spigotURL', 141 | ] 142 | 143 | /** Update server state */ 144 | async function set(data: Partial) { 145 | // ignore if data contains unsettable properties 146 | if (Object.keys(data).some((k) => !settable.includes(k))) { 147 | return Response.json({ message: 'Invalid properties' }, { status: 400 }) 148 | } 149 | 150 | Object.assign(state, data) 151 | return Response.json({ message: 'ok' }) 152 | } 153 | 154 | /** 155 | * Send a command to a process 156 | * Defaults to spigot 157 | */ 158 | async function command(cmd: string, sub: 'spigot' | 'bungee' = 'spigot') { 159 | if (state[`${sub}Status`] !== 'started') { 160 | state[`${sub}Log`] += '🔴 Ignored ' + sub + ': ' + cmd + '\n' 161 | return Response.json({ message: sub + ' is not started' }) 162 | } 163 | 164 | state[`${sub}Log`] += '> ' + cmd + '\n' 165 | 166 | state[sub]?.stdin.write(cmd + '\n') 167 | 168 | return Response.json({ message: 'ok' }) 169 | } 170 | 171 | async function start(sub: 'spigot' | 'bungee') { 172 | state[`${sub}Log`] += '> start\n' 173 | spawn(sub) 174 | 175 | return Response.json({ message: 'starting' }) 176 | } 177 | 178 | async function spawn(sub: 'spigot' | 'bungee') { 179 | if (state[`${sub}Status`] === 'starting') 180 | return console.error('🔴 Already starting', sub) 181 | if (state[`${sub}Status`] === 'started') 182 | return console.error('🔴 Already started', sub) 183 | if (state[`${sub}Status`] === 'stopping') 184 | return console.error('🔴 Wait for stopping', sub) 185 | 186 | const url = state[`${sub}URL`] 187 | if (!url) return console.error('🔴 No URL for', sub) 188 | 189 | state[`${sub}Status`] = 'starting' 190 | 191 | // Prepare folder and change config files 192 | await $`mkdir -p ${sub}`.quiet() 193 | 194 | if (sub === 'spigot') { 195 | await $`echo "eula=true" > ${sub}/eula.txt`.quiet() 196 | await overwriteConfig(`${sub}/server.properties`, "online-mode=true", "online-mode=false"); 197 | await overwriteConfig(`${sub}/spigot.yml`, "bungeecord: false", "bungeecord: true"); 198 | } 199 | 200 | if (sub === 'bungee') { 201 | await overwriteConfig(`${sub}/config.yml`, "online_mode: true", "online_mode: false"); 202 | await overwriteConfig(`${sub}/config.yml`, "ip_forward: false", "ip_forward: true"); 203 | await overwriteConfig(`${sub}/plugins/EaglercraftXBungee/authservice.yml`, "enable_authentication_system: true", "enable_authentication_system: false"); 204 | await overwriteConfig(`${sub}/plugins/EaglercraftXBungee/settings.yml`, "server_name: 'EaglercraftXBungee Server'", "server_name: 'AutoEagler Server'"); 205 | await overwriteConfig(`${sub}/plugins/EaglercraftXBungee/listeners.yml`, "&6An EaglercraftX server", "&6An AutoEagler server"); 206 | } 207 | 208 | // Generate a unique filename for the jar 209 | const jar = sub + '-' + Bun.hash(url).toString(36) + '.jar' 210 | // Download the jar if it's not already downloaded 211 | const downloaded = await $`wget -nv -c ${url} -O ${sub}/${jar}`.quiet() 212 | 213 | // Show download confirmation 214 | const stderr = downloaded.stderr.toString() 215 | if (stderr.includes('] -> "' + sub + '/' + sub + '-')) { 216 | console.log('🟢 Downloaded', sub, url) 217 | } else if (!stderr) { 218 | console.log('🟢 Already downloaded', sub, url) 219 | } else { 220 | console.log('🔴 Tried to download', sub, url) 221 | console.log(downloaded.stderr.toString()) 222 | console.log(downloaded.stdout.toString()) 223 | } 224 | 225 | // Spawn the subprocess 226 | const command = state[`${sub}Command`] 227 | const subprocess = Bun.spawn(command.slice(0, -1).concat([jar]), { 228 | stdin: 'pipe', 229 | stdout: 'pipe', 230 | stderr: 'pipe', 231 | cwd: process.cwd() + '/' + sub, 232 | }) 233 | state[sub] = subprocess 234 | 235 | handleLines( 236 | sub, 237 | subprocess.stdout, 238 | sub === 'spigot' ? interpretSpigot : interpretBungee 239 | ) 240 | handleLines( 241 | sub, 242 | subprocess.stderr, 243 | sub === 'spigot' ? interpretSpigot : interpretBungee 244 | ) 245 | } 246 | 247 | async function handleLines( 248 | sub: 'spigot' | 'bungee', 249 | stream: ReadableStream, 250 | callback: (line: string) => void 251 | ) { 252 | const reader = stream.getReader() 253 | try { 254 | while (true) { 255 | const { done, value } = await reader.read() 256 | if (done) break 257 | 258 | const colored = new TextDecoder().decode(value) 259 | // Show output in the console 260 | // process.stdout.write('📦 ' + colored) 261 | 262 | // Try to interpret the output 263 | const text = removeShellColors(colored) 264 | text.split('\n').forEach(callback) 265 | 266 | // Sync log to the frontend 267 | state[`${sub}Log`] += text 268 | } 269 | console.error(`🔴 ${sub} stdout stopped`) 270 | } catch (error) { 271 | console.error(`🔴 ${sub} stdout error`, error) 272 | } 273 | state[`${sub}Status`] = 'stopped' 274 | } 275 | 276 | function interpretSpigot(text: string) { 277 | if (text.includes('\n')) return text.split('\n').forEach(interpretSpigot) 278 | if (!text.trim()) return 279 | 280 | // ! For help, type "help" or "?" 281 | if (text.includes('For help, type "help" or "?"')) 282 | state.spigotStatus = 'started' 283 | if (text.includes('] Stopping server')) state.spigotStatus = 'stopped' 284 | if (text.includes(': Stopping server')) state.spigotStatus = 'stopped' 285 | } 286 | 287 | function interpretBungee(text: string) { 288 | if (text.includes('\n')) return text.split('\n').forEach(interpretBungee) 289 | if (!text.trim()) return 290 | 291 | if (text.includes('Listening on')) state.bungeeStatus = 'started' 292 | if (text.includes('Closing listener')) state.bungeeStatus = 'stopped' 293 | } 294 | 295 | async function stop(sub: 'spigot' | 'bungee') { 296 | if (state[`${sub}Status`] === 'init') 297 | return Response.json({ message: 'Server is not started' }) 298 | if (state[`${sub}Status`] === 'stopping') 299 | return Response.json({ message: 'Already stopping' }) 300 | if (state[`${sub}Status`] === 'stopped') 301 | return Response.json({ message: 'Already stopped' }) 302 | if (state[`${sub}Status`] === 'starting') 303 | return Response.json({ message: 'Wait for starting' }) 304 | 305 | if (sub === 'spigot') command('stop', sub) 306 | else state[sub]?.kill() 307 | 308 | return Response.json({ message: 'Stopped' }) 309 | } 310 | 311 | async function reset() { 312 | // if (state.spigotStatus !== 'stopped') await command('stop') 313 | 314 | // await new Promise((resolve) => setTimeout(resolve, 1000)) 315 | // state.spigotStatus = 'stopping' 316 | 317 | // // send SIGHUP to the process 318 | // state.spigot?.kill(1) 319 | 320 | // console.log('stopeed', state.spigot?.exitCode, await state.spigot?.exited) 321 | // state.spigotStatus = 'stopped' 322 | 323 | // setTimeout(() => { 324 | // console.log('📦 reset') 325 | // process.exit(0) 326 | // }, 100) 327 | return Response.json({ message: 'Reset' }) 328 | } 329 | 330 | function removeShellColors(input: string): string { 331 | return input.replace(/\x1B\[\d+m/g, '') // Regular expression to match ANSI escape codes for colors 332 | } 333 | 334 | // before exit 335 | process.once('beforeExit', (code) => { 336 | console.log('📦 Process beforeExit event with code: ', code) 337 | state.spigot?.kill() 338 | state.bungee?.kill() 339 | }) 340 | 341 | // Listen for termination signals allows Ctrl+C in docker run 342 | process.on('SIGINT', () => { 343 | console.log('📦 Received SIGINT') 344 | state.spigot?.kill() 345 | state.bungee?.kill() 346 | setTimeout(() => { 347 | process.exit(0) 348 | }, 1000) 349 | }) 350 | process.on('SIGTERM', () => { 351 | console.log('📦 Received SIGTERM') 352 | process.exit(0) 353 | }) 354 | -------------------------------------------------------------------------------- /autoeagler.py: -------------------------------------------------------------------------------- 1 | import os 2 | from wsgiref.simple_server import server_version 3 | import requests 4 | import shutil 5 | import time 6 | import subprocess 7 | import os 8 | import logging 9 | from datetime import datetime 10 | import json 11 | 12 | from contextlib import redirect_stdout, redirect_stderr 13 | from pyngrok import conf, ngrok 14 | import psutil 15 | import platform 16 | if platform.system() == 'Windows': 17 | import pygetwindow as gw 18 | 19 | 20 | #create log files 21 | log_dir = "./logs" 22 | os.makedirs(log_dir, exist_ok=True) 23 | log_file_path = os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log.txt") 24 | 25 | logging.basicConfig( 26 | level=logging.INFO, 27 | format="%(asctime)s [%(levelname)s] %(message)s", 28 | handlers=[ 29 | logging.FileHandler(log_file_path), 30 | ] 31 | ) 32 | logging.info("Autoeagler v2.3.4 logs") 33 | logging.info("starting Autoeagler...") 34 | 35 | 36 | 37 | # URLs and file locations 38 | latest_bungee_1_8_8 = "https://api.papermc.io/v2/projects/waterfall/versions/1.20/builds/556/downloads/waterfall-1.20-556.jar" 39 | bungee_location_1_8_8 = "Bungee-1.8.8/BungeeCord.jar" 40 | latest_eaglerx_1_8_8 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-1.8/raw/branch/main/gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar" 41 | eaglerx_location_1_8_8 = "./Bungee-1.8.8/plugins/eaglerXbungee.jar" 42 | latest_spigot_1_8_8 = "https://cdn.getbukkit.org/spigot/spigot-1.8.8-R0.1-SNAPSHOT-latest.jar" 43 | spigot_location_1_8_8 = "Server-1.8.8/Spigot.jar" 44 | # NOTE: eaglerbungee 1.5.2 is NOT a bungeecord plugin. its regular BungeeCord except it accepts WebSockets instead of raw TCP connections 45 | latest_eaglerbungee_1_5_2 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_1.5_Bungee/bungee-dist.jar" 46 | eaglerbungee_location_1_5_2 = "Bungee-1.5.2/BungeeCord.jar" 47 | # these files are required for eaglerbungee 1.5.2 to function(specially config.yml) 48 | file1_1_5_2 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_1.5_Bungee/bans.txt" 49 | file1_location_1_5_2 = "Bungee-1.5.2/bans.txt" 50 | file2_1_5_2 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_1.5_Bungee/config.yml" 51 | file2_location_1_5_2 = "Bungee-1.5.2/config.yml" 52 | file3_1_5_2 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_1.5_Bungee/erver-icon.png" 53 | file3_location_1_5_2 = "Bungee-1.5.2/server-icon.png" 54 | latest_spigot_1_5_2 = "https://cdn.getbukkit.org/spigot/spigot-1.5.2-R1.1-SNAPSHOT.jar" 55 | spigot_location_1_5_2 = "server-1.5.2/Spigot.jar" 56 | #1.3 beta 57 | latest_bukkit_1_3 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_b1.3_Bukkit/eaglercraft-bukkit.jar" 58 | bukkit_location_1_3 = "server-beta-1.3/eaglercraft-bukkit.jar" 59 | file1_1_3 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_b1.3_Bukkit/eagler.yml" 60 | file1_location_1_3 = "server-beta-1.3/eagler.yml" 61 | file2_1_3 = "https://git.eaglercraft.rip/eaglercraft/eaglercraft-builds/raw/branch/main/Eaglercraft_b1.3_Bukkit/server.properties" 62 | file2_location_1_3 = "server-beta-1.3/server.properties" 63 | 64 | token = "" 65 | 66 | def close_terminal_window(window_name): 67 | for proc in psutil.process_iter(['pid', 'name', 'cmdline']): 68 | if window_name in proc.info['cmdline']: 69 | try: 70 | proc.terminate() 71 | print(f"Window '{window_name}' terminated successfully.") 72 | except psutil.NoSuchProcess: 73 | print(f"Failed to terminate window '{window_name}'. Process not found.") 74 | def download_file(url, location): 75 | logging.info(f"downloaded a file from {url} to {location}") 76 | response = requests.get(url) 77 | with open(location, 'wb') as file: 78 | file.write(response.content) 79 | 80 | def replace_in_file(file_path, search, replace): 81 | with open(file_path, 'r') as file: 82 | file_data = file.read() 83 | file_data = file_data.replace(search, replace) 84 | with open(file_path, 'w') as file: 85 | file.write(file_data) 86 | 87 | def set_authtoken(token): 88 | # Load the JSON data from the file 89 | with open('config.json', 'r') as file: 90 | config_data = json.load(file) 91 | 92 | config_data['token'] = token 93 | 94 | # Save the updated JSON data back to the file 95 | with open('config.json', 'w') as file: 96 | json.dump(config_data, file, indent=4) 97 | 98 | def get_authtoken(): 99 | global token 100 | # Load the JSON data from the file 101 | with open('config.json', 'r') as file: 102 | config_data = json.load(file) 103 | 104 | # Read the value of the "token" field into a variable 105 | token = config_data.get('token', None) 106 | 107 | def remove_everything(): 108 | clear_screen() 109 | sure = input("All progress made in-game will be lost\nAre you sure you want to remove all files? (Y/N)\n>> ") 110 | if sure.lower() == "y": 111 | shutil.rmtree("./Bungee-1.8.8", ignore_errors=True) 112 | shutil.rmtree("./Server-1.8.8", ignore_errors=True) 113 | shutil.rmtree("./Bungee-1.5.2", ignore_errors=True) 114 | shutil.rmtree("./Server-1.5.2", ignore_errors=True) 115 | shutil.rmtree("./Server-beta-1.3", ignore_errors=True) 116 | if os.path.exists(bungee_location_1_8_8): 117 | os.remove(bungee_location_1_8_8) 118 | if os.path.exists(eaglerx_location_1_8_8): 119 | os.remove(eaglerx_location_1_8_8) 120 | if os.path.exists(spigot_location_1_8_8): 121 | os.remove(spigot_location_1_8_8) 122 | print("All server files have been deleted!") 123 | logging.info("removed everything D:") 124 | else: 125 | pass 126 | 127 | def clear_screen(): 128 | os.system('cls' if os.name == 'nt' else 'clear') 129 | 130 | def run_command_in_new_terminal(command): 131 | if os.name == 'nt': 132 | subprocess.Popen(['start', 'cmd', '/c', command], shell=True) 133 | elif os.name == 'posix': 134 | try: 135 | subprocess.Popen(['xdg-open', 'x-terminal-emulator', '--', 'bash', '-c', command]) 136 | except FileNotFoundError: 137 | print("No suitable terminal emulator found. Please run the command manually.") 138 | else: 139 | print("Unsupported operating system for running commands in a new terminal.") 140 | 141 | 142 | def run_servers(server_version): 143 | 144 | clear_screen() 145 | 146 | if server_version == "0" : 147 | logging.info("starting bungeecord 1.8.8 ...") 148 | # Change directory to the location of BungeeCord.jar 149 | os.chdir(os.path.dirname(bungee_location_1_8_8)) 150 | run_command_in_new_terminal(f'title bungee 1.8.8 & java -Xms64M -Xmx64M -jar {os.path.basename(bungee_location_1_8_8)}') 151 | os.chdir(os.path.dirname("../")) 152 | logging.info("starting spigot 1.8.8 ...") 153 | # Change directory to the location of Spigot.jar 154 | os.chdir(os.path.dirname(spigot_location_1_8_8)) 155 | run_command_in_new_terminal(f'title spigot 1.8.8 & java -Xms2G -Xmx2G -jar {os.path.basename(spigot_location_1_8_8)}') 156 | os.chdir(os.path.dirname("../")) 157 | if server_version == "1" : 158 | logging.info("starting bungeecord 1.5.2 ...") 159 | # Change directory to the location of BungeeCord.jar 160 | os.chdir(os.path.dirname(eaglerbungee_location_1_5_2)) 161 | run_command_in_new_terminal(f'title bungee 1.5.2 & java -Xmx32M -Xms32M -jar {os.path.basename(eaglerbungee_location_1_5_2)}') 162 | os.chdir(os.path.dirname("../")) 163 | logging.info("starting spigot 1.5.2 ...") 164 | # Change directory to the location of Spigot.jar 165 | os.chdir(os.path.dirname(spigot_location_1_5_2)) 166 | run_command_in_new_terminal(f'title spigot 1.5.2 & java -Xms2G -Xmx2G -jar {os.path.basename(spigot_location_1_5_2)}') 167 | os.chdir(os.path.dirname("../")) 168 | if server_version == "2" : 169 | # Change directory to the location of bukkit.jar 170 | os.chdir(os.path.dirname(bukkit_location_1_3)) 171 | run_command_in_new_terminal(f'title eaglerbukkit beta 1.3 & java -Xms2G -Xmx2G -jar {os.path.basename(bukkit_location_1_3)}') 172 | os.chdir(os.path.dirname("../")) 173 | 174 | print("Servers starting ...") 175 | 176 | def stop_servers(): 177 | print("Stopping servers...") 178 | logging.info("stopping servers...") 179 | 180 | 181 | if platform.system() == 'Windows': 182 | for window in gw.getAllTitles(): 183 | if 'bungee' in window.lower() or 'spigot' in window.lower() or 'eaglerbukkit' in window.lower(): 184 | try: 185 | gw.getWindowsWithTitle(window)[0].close() 186 | print(f"Closed {window}") 187 | except Exception as e: 188 | print(f"Failed to close {window}. Error: {e}") 189 | elif platform.system() == 'Linux': 190 | close_terminal_window('bungee') 191 | close_terminal_window('spigot') 192 | if server_version == "2": 193 | close_terminal_window('eaglerbukkit') 194 | logging.info("servers stopped") 195 | print("servers stopped") 196 | 197 | def ngrok_start(server_version): 198 | global http_tunnel 199 | 200 | clear_screen() 201 | 202 | #ask for region to use 203 | ligma = input("What region would u like to use for your server? \nap -> Asia/Pacific (Singapore)\nau -> Australia (Sydney)\neu -> Europe (Frankfurt)\nin -> India (Mumbai)\njp -> Japan (Tokyo)\nsa -> South America (São Paulo)\nus -> United States (Ohio)\nus-cal-1 -> United States (California)\n>> ") 204 | conf.get_default().region = ligma 205 | 206 | run_servers(server_version) 207 | 208 | #open tunnel to NGROK 209 | conf.get_default().monitor_thread = False 210 | with redirect_stdout(None) and redirect_stderr(None): 211 | http_tunnel = ngrok.connect(8081, 'http', bind_tls=True) 212 | logging.info(f"Server running at {http_tunnel.public_url.replace('https', 'wss')}") 213 | print(f"Server running at {http_tunnel.public_url.replace('https', 'wss')}") 214 | 215 | def main(): 216 | global token 217 | get_authtoken() 218 | while True: 219 | os.system('cls' if os.name == 'nt' else 'clear') 220 | print("1) Set up AutoEagler") 221 | print("2) Run locally") 222 | print("3) Run with NGROK") 223 | print("4) Wipe everything") 224 | print("5) Exit") 225 | print("6) start web dashboard (soon)") 226 | 227 | choice = input(">> ") 228 | 229 | if choice == '1': 230 | logging.info('user chose "1) Set up AutoEagler"') 231 | clear_screen() 232 | #ask for NGROK authtoken 233 | token = input("Please input your NGROK authtoken.\nRetrieve it at https://dashboard.ngrok.com/get-started/your-authtoken\n>> ") 234 | conf.get_default().auth_token = token 235 | set_authtoken(token) 236 | 237 | clear_screen() 238 | 239 | #Ask for version 240 | version = str(input("What version of eaglercraft would you like to make your server for?\n0 -> 1.8.8(u19)\n1 -> 1.5.2\n2 -> beta 1.3\n>> ")) 241 | logging.info(f"User chose {version}") 242 | 243 | clear_screen() 244 | 245 | #Ask for gamemode 246 | if not version == "2": # bc you cant change your gamemode on beta 1.3 :skull: 247 | gamemode = str(input("What gamemode would you like to use?\n0 -> Survival\n1 -> Creative\n2 -> Adventure\n3 -> Spectator\n>> ")) 248 | hardcore = "false" 249 | if gamemode == "0": 250 | logging.info("user chose survival mode") 251 | if input("Do you want to enable hardcore? (Y/N)\n>> ").lower() == "y": 252 | hardcore = "true" 253 | logging.info("user chose hardcore mode after choosing survival") 254 | if gamemode == "1": 255 | logging.info("user chose creative mode") 256 | 257 | if gamemode == "2": 258 | logging.info("user chose adventure mode") 259 | 260 | if gamemode == "3": 261 | logging.info("user chose spectator mode") 262 | 263 | clear_screen() 264 | 265 | #Ask for seed 266 | if not version == "2" : # bc you cant change the seed on beta 1.3 :skull: 267 | seed = str(input("What seed would you like to use?\n(Leave empty for a random seed)\n>> ")) 268 | 269 | clear_screen() 270 | if version == "0": 271 | if not os.path.exists(bungee_location_1_8_8): 272 | os.makedirs(os.path.dirname(bungee_location_1_8_8), exist_ok=True) 273 | download_file(latest_bungee_1_8_8, bungee_location_1_8_8) 274 | print(f"BungeeCord.jar downloaded to {bungee_location_1_8_8}") 275 | 276 | if not os.path.exists(eaglerx_location_1_8_8): 277 | os.makedirs(os.path.dirname(eaglerx_location_1_8_8), exist_ok=True) 278 | download_file(latest_eaglerx_1_8_8, eaglerx_location_1_8_8) 279 | print(f"Eaglerxbungee.jar downloaded to {eaglerx_location_1_8_8}") 280 | 281 | if not os.path.exists(spigot_location_1_8_8): 282 | os.makedirs(os.path.dirname(spigot_location_1_8_8), exist_ok=True) 283 | download_file(latest_spigot_1_8_8, spigot_location_1_8_8) 284 | print(f"Spigot.jar downloaded to {spigot_location_1_8_8}") 285 | if version == "1": 286 | if not os.path.exists(eaglerbungee_location_1_5_2): 287 | os.makedirs(os.path.dirname(eaglerbungee_location_1_5_2), exist_ok=True) 288 | download_file(latest_eaglerbungee_1_5_2, eaglerbungee_location_1_5_2) 289 | print(f"BungeeCord.jar downloaded to {eaglerbungee_location_1_5_2}") 290 | 291 | if not os.path.exists(file1_location_1_5_2): 292 | os.makedirs(os.path.dirname(file1_location_1_5_2), exist_ok=True) 293 | download_file(file1_1_5_2, file1_location_1_5_2) 294 | print(f"bans.txt downloaded to {file1_location_1_5_2}") 295 | 296 | if not os.path.exists(file2_location_1_5_2): 297 | os.makedirs(os.path.dirname(file2_location_1_5_2), exist_ok=True) 298 | download_file(file2_1_5_2, file2_location_1_5_2) 299 | print(f"config.yml downloaded to {file2_location_1_5_2}") 300 | 301 | if not os.path.exists(file3_location_1_5_2): 302 | os.makedirs(os.path.dirname(file3_location_1_5_2), exist_ok=True) 303 | download_file(file3_1_5_2, file3_location_1_5_2) 304 | print(f"server-icon.png downloaded to {file3_location_1_5_2}") 305 | 306 | if not os.path.exists(spigot_location_1_5_2): 307 | os.makedirs(os.path.dirname(spigot_location_1_5_2), exist_ok=True) 308 | download_file(latest_spigot_1_5_2, spigot_location_1_5_2) 309 | print(f"Spigot.jar downloaded to {spigot_location_1_5_2}") 310 | if version == "2": 311 | if not os.path.exists(bukkit_location_1_3): 312 | os.makedirs(os.path.dirname(bukkit_location_1_3), exist_ok=True) 313 | download_file(latest_bukkit_1_3, bukkit_location_1_3) 314 | print(f"bukkit.jar downloaded to {bukkit_location_1_3}") 315 | 316 | if not os.path.exists(file1_location_1_3): 317 | os.makedirs(os.path.dirname(file1_location_1_3), exist_ok=True) 318 | download_file(file1_1_3, file1_location_1_3) 319 | print(f"eagler.yml downloaded to {file1_location_1_3}") 320 | 321 | if not os.path.exists(file2_location_1_3): 322 | os.makedirs(os.path.dirname(file2_location_1_3), exist_ok=True) 323 | download_file(file2_1_3, file2_location_1_3) 324 | print(f"server.properties downloaded to {file2_location_1_3}") 325 | 326 | print("Initial run..") 327 | 328 | run_servers(version) # Run the servers after downloading 329 | time.sleep(15) 330 | stop_servers() 331 | 332 | 333 | if version == "0" : 334 | print("Enabling EULA in spigot...") 335 | logging.info("Enabling EULA in spigot...") 336 | replace_in_file("Server-1.8.8/eula.txt", "false", "true") # Change eula to true 337 | run_servers(version) 338 | time.sleep(15) 339 | stop_servers() 340 | 341 | print("Generating config files...") 342 | logging.info("Generating config files...") 343 | if version == "0" : 344 | print("Modifiying 1.8.8 config files...") 345 | # Replace content in configuration files 346 | replace_in_file("Server-1.8.8/server.properties", "online-mode=true", "online-mode=false") 347 | replace_in_file("Server-1.8.8/spigot.yml", "bungeecord: false", "bungeecord: true") 348 | replace_in_file("Bungee-1.8.8/plugins/EaglercraftXBungee/authservice.yml", "enable_authentication_system: true", "enable_authentication_system: false") 349 | replace_in_file("Bungee-1.8.8/plugins/EaglercraftXBungee/settings.yml", "server_name: 'EaglercraftXBungee Server'", "server_name: 'AutoEagler Server'") 350 | replace_in_file("Bungee-1.8.8/plugins/EaglercraftXBungee/listeners.yml", "&6An EaglercraftX server", "&6An AutoEagler server") 351 | 352 | replace_in_file("Bungee-1.8.8/config.yml", "online_mode: true", "online_mode: false") 353 | replace_in_file("Bungee-1.8.8/config.yml", "ip_forward: false", "ip_forward: true") 354 | 355 | #Custom settings 356 | replace_in_file("Server-1.8.8/server.properties", "gamemode=0", "gamemode="+gamemode) 357 | replace_in_file("Server-1.8.8/server.properties", "hardcore=false", "hardcore="+hardcore) 358 | 359 | if not seed == "": 360 | replace_in_file("Server-1.8.8/server.properties", "seed=", "seed="+seed) 361 | if version == "1" : 362 | logging.info("Modifiying 1.5.2 config files...") 363 | # Replace content in configuration files 364 | replace_in_file("Server-1.5.2/server.properties", "online-mode=true", "online-mode=false") 365 | replace_in_file("Server-1.5.2/spigot.yml", "bungeecord: false", "bungeecord: true") 366 | replace_in_file("Bungee-1.5.2/config.yml", "server_name: EaglercraftBungee Server", "server_name: AutoEagler Server") 367 | replace_in_file("Bungee-1.5.2/config.yml", "&6An Eaglercraft server", "&6An AutoEagler server") 368 | replace_in_file("Bungee-1.5.2/config.yml", "forward_ip: false", "forward_ip: true") 369 | replace_in_file("Bungee-1.5.2/config.yml", "host: 0.0.0.0:25565", "host: 0.0.0.0:8081") 370 | replace_in_file("Bungee-1.5.2/config.yml", "address: localhost:25569", "address: localhost:25565") 371 | 372 | #Custom settings 373 | replace_in_file("Server-1.5.2/server.properties", "gamemode=0", "gamemode="+gamemode) 374 | replace_in_file("Server-1.5.2/server.properties", "hardcore=false", "hardcore="+hardcore) 375 | 376 | if not seed == "": 377 | replace_in_file("Server-1.5.2/server.properties", "seed=", "seed="+seed) 378 | if version == "2" : 379 | logging.info("Modifiying beta 1.3 config files...") 380 | # Replace content in configuration files 381 | replace_in_file("Server-beta-1.3/eagler.yml", "only_allow_registered_users_to_login: true", "only_allow_registered_users_to_login: false") 382 | replace_in_file("Server-beta-1.3/eagler.yml", "allow_self_registration: false", "allow_self_registration: true") 383 | replace_in_file("Server-beta-1.3/eagler.yml", "allow_self_registration_without_expiration: false", "allow_self_registration_without_expiration: true") 384 | replace_in_file("Server-beta-1.3/server.properties", "max-players=20", "max-players=69420") 385 | replace_in_file("Server-beta-1.3/server.properties", "websocket-address=0.0.0.0\:25565", "websocket-address=0.0.0.0\:8081") 386 | 387 | clear_screen() 388 | print("You're done setting up AutoEagler\nRun the servers by using option 2 and close them using 5") 389 | logging.info("Finished setting up Autoeagler") 390 | time.sleep(3) 391 | 392 | elif choice == '2': 393 | clear_screen() 394 | logging.info('user chose "2) Run locally"') 395 | version = str(input("What version of eaglercraft would you like to run?\nNOTE: you can only run the versions you set up!\n0 -> 1.8.8(u19)\n1 -> 1.5.2\n2 -> beta 1.3\n>> ")) 396 | run_servers(version) 397 | if not version == "2": 398 | print("Server running at ws://localhost:8081") 399 | input("Press [Enter] to return to the menu (servers stay up)") 400 | else : 401 | print("Server running at 127.0.0.1:8081\nUse /register-password when you join the server to set your password!") 402 | input("Press [Enter] to return to the menu (servers stay up)") 403 | 404 | elif choice == '3': 405 | clear_screen() 406 | logging.info('user chose "3) Run with NGROK"') 407 | conf.get_default().auth_token = token 408 | version = str(input("What version of eaglercraft would you like to run?\nNOTE: you can only run the versions you set up!\n0 -> 1.8.8(u19)\n1 -> 1.5.2\n2 -> beta 1.3\n>> ")) 409 | ngrok_start(version) 410 | if not version == "2": 411 | input("Press [Enter] to return to the menu (servers stay up)") 412 | else: 413 | print("Use /register-password when you join the server to set your password!") 414 | input("Press [Enter] to return to the menu (servers stay up)") 415 | 416 | 417 | elif choice == '4': 418 | logging.info('user chose "4) Wipe everything"') 419 | remove_everything() 420 | print("Everything has been removed.") 421 | 422 | elif choice == '5': 423 | logging.info('user chose "5) Exit"') 424 | conf.get_default().auth_token = token 425 | stop_servers() 426 | logging.info("exiting Autoeagler...") 427 | with redirect_stdout(None) and redirect_stderr(None): 428 | tunnels = ngrok.get_tunnels() 429 | if not tunnels: 430 | break 431 | else: 432 | ngrok.disconnect(http_tunnel.public_url) 433 | break 434 | 435 | if __name__ == "__main__": 436 | main() 437 | --------------------------------------------------------------------------------