Spaces:
Paused
Paused
| _O='ML-minecraft-service/1.0' | |
| _N='Accept' | |
| _M='mc_daemon.log' | |
| _L='/home/user/.torch_metrics' | |
| _K='incendium' | |
| _J='26.1.2' | |
| _I='url' | |
| _H='version_number' | |
| _G='User-Agent' | |
| _F='size' | |
| _E='sha1' | |
| _D='filename' | |
| _C=None | |
| _B=False | |
| _A=True | |
| import hashlib,json,os,shutil,subprocess,tarfile,time,urllib.parse,urllib.request,zipfile | |
| from pathlib import Path | |
| from loguru import logger | |
| MC_GAME_VERSION=_J | |
| PAPER_MC_VERSION=_J | |
| MODRINTH_API='https://api.modrinth.com/v2' | |
| MC_DIR=Path('/tmp/mc') | |
| WORLDGEN_DATAPACKS=[('terralith','Terralith'),(_K,'Incendium'),('nullscape','Nullscape')] | |
| OPS_JSON=[{'uuid':'bee75070-9b57-33a6-b9c8-092d36529789','name':'TrueKing208','level':4,'bypassesPlayerLimit':_B}] | |
| def log_print(msg): | |
| logger.info(msg) | |
| try: | |
| A=Path(_L);A.mkdir(parents=_A,exist_ok=_A);B=A/_M | |
| with B.open('a')as C:C.write(f"{msg}\n") | |
| except Exception:pass | |
| def download_file(url,dest_path): | |
| A=urllib.request.Request(url,headers={_G:'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',_N:'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','Accept-Language':'en-US,en;q=0.9','Sec-Ch-Ua':'"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"','Sec-Ch-Ua-Mobile':'?0','Sec-Ch-Ua-Platform':'"Windows"','Sec-Fetch-Dest':'document','Sec-Fetch-Mode':'navigate','Sec-Fetch-Site':'none','Sec-Fetch-User':'?1','Upgrade-Insecure-Requests':'1','Referer':'https://geysermc.org/'}) | |
| with urllib.request.urlopen(A)as B,Path(dest_path).open('wb')as C:shutil.copyfileobj(B,C) | |
| def download_binary(url,dest_path,timeout=300): | |
| A=urllib.request.Request(url,headers={_G:_O,_N:'*/*'}) | |
| with urllib.request.urlopen(A,timeout=timeout)as B,Path(dest_path).open('wb')as C:shutil.copyfileobj(B,C) | |
| def file_sha1(path): | |
| A=hashlib.sha1() | |
| with Path(path).open('rb')as B: | |
| for C in iter(lambda:B.read(1048576),b''):A.update(C) | |
| return A.hexdigest() | |
| def is_valid_datapack_zip(path,expected_size=_C,expected_sha1=_C): | |
| C=expected_sha1;B=expected_size;A=path;D=Path(A) | |
| if not D.is_file():return _B | |
| if B is not _C and D.stat().st_size!=B:return _B | |
| if C and file_sha1(A)!=C:return _B | |
| try: | |
| with zipfile.ZipFile(A)as E: | |
| if E.testzip()is not _C:return _B | |
| if not any(A.endswith('pack.mcmeta')for A in E.namelist()):return _B | |
| return _A | |
| except Exception:return _B | |
| def fetch_modrinth_datapack(project_slug,game_version=MC_GAME_VERSION): | |
| F='files';D=game_version;C=project_slug;G=urllib.parse.urlencode({'game_versions':json.dumps([D]),'loaders':json.dumps(['datapack'])});H=f"{MODRINTH_API}/project/{C}/version?{G}";I=urllib.request.Request(H,headers={_G:_O}) | |
| with urllib.request.urlopen(I,timeout=60)as J:E=json.load(J) | |
| if not E:raise RuntimeError(f"No Modrinth datapack for {C} on {D}") | |
| B=E[0];A=next((A for A in B[F]if A.get('primary')),B[F][0]);K=A.get('hashes',{});return{_H:B[_H],_I:A[_I],_D:A[_D],_E:K.get(_E),_F:A.get(_F)} | |
| def install_datapack(dest,info): | |
| C=dest;B=info;D=Path(C) | |
| if is_valid_datapack_zip(C,B.get(_F),B.get(_E)):return _B | |
| A=Path(str(C)+'.part') | |
| if A.exists():A.unlink() | |
| try: | |
| download_binary(B[_I],A) | |
| if not is_valid_datapack_zip(A,B.get(_F),B.get(_E)):raise RuntimeError('downloaded file is not a valid datapack zip') | |
| A.replace(D);return _A | |
| except Exception: | |
| if A.exists():A.unlink() | |
| if D.exists():D.unlink() | |
| raise | |
| def reset_generated_world(mc_dir): | |
| A=[];D=Path(mc_dir) | |
| for B in('world','world_nether','world_the_end'): | |
| C=D/B | |
| if C.is_dir():shutil.rmtree(C);A.append(B) | |
| if A:E=', '.join(A);log_print('[*] Cleared '+E+'. Next boot regenerates with Terralith (overworld), Incendium (nether), Nullscape (end).') | |
| def ensure_datapacks(mc_dir): | |
| E=Path(mc_dir);C=E/'world'/'datapacks';C.mkdir(parents=_A,exist_ok=_A) | |
| for(D,B)in WORLDGEN_DATAPACKS: | |
| F=C/f"{B}.zip" | |
| try: | |
| A=fetch_modrinth_datapack(D) | |
| if install_datapack(F,A): | |
| log_print(f"[+] {B} {A[_H]} installed ({A[_D]})") | |
| if D==_K and'UNSUPPORTED'in A[_D]:log_print('[!] Incendium 26.1 is marked UNSUPPORTED on Modrinth (alpha); watch server logs after first Nether generation.') | |
| except Exception as G:log_print(f"[-] Failed to install {B} datapack: {G}") | |
| def paper_patched_jar(mc_dir):return Path(mc_dir)/'versions'/PAPER_MC_VERSION/f"paper-{PAPER_MC_VERSION}.jar" | |
| def ensure_paper_runtime(java_bin,mc_dir,server_jar): | |
| C=mc_dir;A=paper_patched_jar(C) | |
| if A.is_file():return | |
| A.parent.mkdir(parents=_A,exist_ok=_A);log_print('[*] Bootstrapping Paper (download mojang jar + apply patches)...');B=subprocess.run([str(java_bin),'-jar',str(server_jar),'--version'],cwd=str(C),capture_output=_A,text=_A,timeout=600) | |
| if not A.is_file(): | |
| log_print(f"[-] Paper bootstrap failed (exit {B.returncode})") | |
| if B.stdout:log_print(B.stdout[-1500:]) | |
| if B.stderr:log_print(B.stderr[-1500:]) | |
| raise RuntimeError(f"expected patched jar missing: {A}") | |
| log_print(f"[+] Paper runtime ready at {A}") | |
| def setup_geyser(mc_dir): | |
| D=Path(mc_dir);C=D/'plugins';C.mkdir(parents=_A,exist_ok=_A);E={'Geyser-Spigot.jar':'https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/spigot','floodgate-spigot.jar':'https://download.geysermc.org/v2/projects/floodgate/versions/latest/builds/latest/downloads/spigot'} | |
| for(A,F)in E.items(): | |
| B=C/A | |
| if B.exists(): | |
| try: | |
| with zipfile.ZipFile(B)as H:0 | |
| except Exception: | |
| log_print(f"[!] Corrupt jar detected: {A} (Invalid Zip Header). Purging and redownloading...") | |
| try:B.unlink() | |
| except:pass | |
| if not B.exists(): | |
| log_print(f"[*] Downloading {A}...") | |
| try:download_file(F,B);log_print(f"[+] {A} downloaded successfully.") | |
| except Exception as G:log_print(f"[-] Failed to download {A}: {G}") | |
| def setup_and_run(): | |
| R='server-port=25565';Q='online-mode=true';P='java';O='bin';log_print('--- INITIALIZING STEALTH MINECRAFT DAEMON ---');A=MC_DIR;F=A/'jre';L=Path(_L);A.mkdir(parents=_A,exist_ok=_A);L.mkdir(parents=_A,exist_ok=_A);D=F/O/P | |
| if not D.exists(): | |
| log_print('[*] Portable JRE not found. Downloading Eclipse Temurin JRE 25...');S='https://api.adoptium.net/v3/binary/latest/25/ga/linux/x64/jre/hotspot/normal/eclipse?project=jdk';G=A/'jre.tar.gz' | |
| try: | |
| download_file(S,G);log_print('[*] Extracting JRE...');H=A/'jre_temp';H.mkdir(parents=_A,exist_ok=_A) | |
| with tarfile.open(G,'r:gz')as T:T.extractall(path=H) | |
| for J in H.rglob(P): | |
| if J.is_file()and J.parent.name==O: | |
| U=J.parent.parent | |
| if F.exists():shutil.rmtree(F) | |
| shutil.move(str(U),str(F));break | |
| shutil.rmtree(H,ignore_errors=_A) | |
| if G.exists():G.unlink() | |
| log_print('[*] Portable JRE setup completed successfully.') | |
| except Exception as B:log_print(f"[-] Failed to setup JRE: {B}");return | |
| E=A/'server.jar' | |
| if not E.exists(): | |
| log_print('[*] Minecraft server jar not found. Downloading PaperMC...');V='https://fill-data.papermc.io/v1/objects/830d4eb5c15cbd802a9ec9f2f54eaaaeb9511958339aec983fd0c88bad21d940/paper-26.1.2-64.jar' | |
| try:download_file(V,E);log_print('[*] PaperMC downloaded successfully.') | |
| except Exception as B:log_print(f"[-] Failed to download PaperMC: {B}");return | |
| setup_geyser(A);reset_generated_world(A);ensure_datapacks(A);log_print(f"[*] Minecraft server root: {A} (ephemeral)");log_print('[*] Ensuring Java binary is executable...') | |
| try:D.chmod(493) | |
| except Exception as B:log_print(f"[-] Failed to chmod java binary: {B}") | |
| try:ensure_paper_runtime(D,A,E) | |
| except Exception as B:log_print(f"[-] Failed to bootstrap Paper runtime: {B}");return | |
| log_print('[*] Launching Minecraft server loop...');W=L/_M | |
| while _A: | |
| ensure_datapacks(A) | |
| if not paper_patched_jar(A).is_file(): | |
| try:ensure_paper_runtime(D,A,E) | |
| except Exception as B:log_print(f"[-] Paper runtime missing and bootstrap failed: {B}");time.sleep(10);continue | |
| X=A/'eula.txt';X.write_text('eula=true\n');Y=A/'ops.json' | |
| with Y.open('w')as M:json.dump(OPS_JSON,M,indent=2);M.write('\n') | |
| I=A/'server.properties' | |
| if not I.exists():I.write_text('server-port=25566\nonline-mode=false\nmotd=PCEP\n') | |
| else: | |
| try: | |
| C=I.read_text();K=_B | |
| if Q in C:C=C.replace(Q,'online-mode=false');K=_A | |
| if R in C:C=C.replace(R,'server-port=25566');K=_A | |
| if K:I.write_text(C) | |
| except Exception as B:log_print(f"[-] Failed to update server.properties: {B}") | |
| log_print('[*] Starting Minecraft server process...') | |
| with W.open('a')as Z:N=subprocess.Popen([str(D),'-Xms4G','-Xmx4G','-jar',str(E),'nogui'],cwd=str(A),stdout=Z,stderr=subprocess.STDOUT);N.wait() | |
| log_print(f"[*] Minecraft server exited with code {N.returncode}. Restarting in 10 seconds to allow network sync...");time.sleep(10) | |
| def start(): | |
| B='mc_server';A='tmux';logger.info("Launching Stealth Minecraft Daemon in tmux session 'mc_server'...") | |
| try: | |
| C=subprocess.run([A,'has-session','-t',B],capture_output=_A) | |
| if C.returncode==0:logger.warning("tmux session 'mc_server' already exists. Killing it to restart...");subprocess.run([A,'kill-session','-t',B]) | |
| subprocess.Popen([A,'new-session','-d','-s',B,'python3 -u -m services.minecraft_service']);logger.success('Stealth Minecraft Daemon started successfully in tmux.') | |
| except Exception as D:logger.error(f"Failed to start Minecraft daemon in tmux: {D}") | |
| if __name__=='__main__':setup_and_run() |