print("STARTED") # Imports import gradio as gr import spaces, requests, os, time, threading, subprocess, shutil, tarfile, urllib.request, json from huggingface_hub import hf_hub_download, upload_file # ======================== # PATH SETUP (FIXED) # ======================== ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.join(ROOT_DIR, "minecraft_server") WORLD_PATH = os.path.join(BASE_DIR, "world") NETHER_PATH = os.path.join(BASE_DIR, "world_nether") END_PATH = os.path.join(BASE_DIR, "world_the_end") print("[DEBUG] ROOT:", ROOT_DIR) print("[DEBUG] BASE:", BASE_DIR) # ======================== # ENV VARIABLES # ======================== ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "") HF_TOKEN = os.environ.get("HF_TOKEN", "") PLAYIT_AUTH_KEY = os.environ.get("PLAYIT_AUTH_KEY", "") HF_REPO_ID = "Dalleon/McSaves" WORLD_ID = "world_default" # ======================== # GLOBALS # ======================== SERVER_PROCESS = None TCP_PROCESS = None PUBLIC_TCP = None SERVER_ONLINE = False SERVER_OUTPUT_BUFFER = [] # ======================== # JAVA INSTALL (fallback) # ======================== def install_java21_local(): jdk_url = "https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz" download_path = os.path.join(BASE_DIR, "jdk21.tar.gz") print("[JAVA] Downloading...") urllib.request.urlretrieve(jdk_url, download_path) extract_dir = os.path.join(BASE_DIR, "jdk21") os.makedirs(extract_dir, exist_ok=True) with tarfile.open(download_path, "r:gz") as tar: tar.extractall(path=extract_dir) for item in os.listdir(extract_dir): if item.startswith("jdk-21"): java_home = os.path.join(extract_dir, item) os.environ["JAVA_HOME"] = java_home os.environ["PATH"] = os.path.join(java_home, "bin") + ":" + os.environ["PATH"] print("[JAVA] Installed:", java_home) return # ======================== # DOWNLOAD PAPER # ======================== def download_paper(): jar_path = os.path.join(BASE_DIR, "server.jar") if os.path.exists(jar_path): return print("[PAPER] Downloading...") url = "https://api.papermc.io/v2/projects/paper/versions/1.20.6/builds/latest/downloads/paper-1.20.6-latest.jar" urllib.request.urlretrieve(url, jar_path) # ======================== # SERVER OUTPUT READER # ======================== def read_server_output(): global SERVER_PROCESS while True: if SERVER_PROCESS and SERVER_PROCESS.stdout: line = SERVER_PROCESS.stdout.readline() if line: print("[MC]", line.strip()) SERVER_OUTPUT_BUFFER.append(line) time.sleep(0.1) # ======================== # SAVE WORLD # ======================== def save_world(): global SERVER_PROCESS print("[SAVE] Saving world...") if SERVER_PROCESS: SERVER_PROCESS.stdin.write("save-off\n") SERVER_PROCESS.stdin.flush() SERVER_PROCESS.stdin.write("save-all\n") SERVER_PROCESS.stdin.flush() time.sleep(10) SERVER_PROCESS.stdin.write("save-on\n") SERVER_PROCESS.stdin.flush() tar_path = os.path.join(BASE_DIR, "world.tar.gz") with tarfile.open(tar_path, "w:gz") as tar: if os.path.exists(WORLD_PATH): tar.add(WORLD_PATH, arcname="world") if os.path.exists(NETHER_PATH): tar.add(NETHER_PATH, arcname="world_nether") if os.path.exists(END_PATH): tar.add(END_PATH, arcname="world_the_end") upload_file( path_or_fileobj=tar_path, path_in_repo="world.tar.gz", repo_id=HF_REPO_ID, token=HF_TOKEN, repo_type="model" ) print("[SAVE] Done.") # ======================== # LOAD WORLD # ======================== def load_world(): print("[LOAD] Loading world...") try: file_path = hf_hub_download( repo_id=HF_REPO_ID, filename="world.tar.gz", token=HF_TOKEN ) except: print("[LOAD] No backup found.") return if os.path.exists(WORLD_PATH): shutil.rmtree(WORLD_PATH) with tarfile.open(file_path, "r:gz") as tar: tar.extractall(BASE_DIR) print("[LOAD] Done.") # ======================== # PLAYIT TUNNEL (FIXED - NON-BLOCKING) # ======================== def start_tcp(): global PUBLIC_TCP, SERVER_ONLINE, TCP_PROCESS playit_path = os.path.join(BASE_DIR, "playit") if not os.path.exists(playit_path): print("[PLAYIT] Downloading...") url = "https://github.com/playit-cloud/playit-agent/releases/latest/download/playit-linux-amd64" urllib.request.urlretrieve(url, playit_path) os.chmod(playit_path, 0o755) if not PLAYIT_AUTH_KEY: print("[PLAYIT] Missing auth key") return # ✅ FIX: fully detached process (NO PIPE, NO READLINE, NO FREEZE) TCP_PROCESS = subprocess.Popen( [playit_path, "--secret", PLAYIT_AUTH_KEY], cwd=BASE_DIR, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True ) print("[PLAYIT] Agent started safely in background") # small delay so it can initialize without blocking HF time.sleep(5) # ❗ IMPORTANT: no blocking log parsing anymore PUBLIC_TCP = "Check Playit dashboard for address" SERVER_ONLINE = True print("[PLAYIT] Ready (no blocking, HF-safe)") # ======================== # START SERVER # ======================== def start_server(): global SERVER_PROCESS load_world() if shutil.which("java") is None: install_java21_local() if shutil.which("java") is None: print("[ERROR] Java not found") return download_paper() with open(os.path.join(BASE_DIR, "eula.txt"), "w") as f: f.write("eula=true") SERVER_PROCESS = subprocess.Popen( [ shutil.which("java"), "-Xms512M", "-Xmx1G", "-jar", "server.jar", "--nogui" ], cwd=BASE_DIR, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) threading.Thread(target=read_server_output, daemon=True).start() time.sleep(5) start_tcp() # ======================== # COMMAND EXEC # ======================== def execute_command(password, cmd): if password != ADMIN_PASSWORD: return "Wrong password" if not SERVER_PROCESS: return "Server not running" SERVER_PROCESS.stdin.write(cmd + "\n") SERVER_PROCESS.stdin.flush() return "Executed" # ======================== # GET ADDRESS # ======================== def get_address(): if not SERVER_ONLINE: return "Server starting..." return PUBLIC_TCP or "No address yet" # ======================== # INIT # ======================== threading.Thread(target=start_server, daemon=True).start() # ======================== # UI # ======================== with gr.Blocks() as app: gr.Markdown("## ⛏️ Minecraft Server") address = gr.Textbox(label="Server Address") btn = gr.Button("Get Address") btn.click(fn=get_address, outputs=address) app.launch()