Spaces:
Paused
Paused
| 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() |