import urllib.request from pathlib import Path import os import subprocess import sys import shutil PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar" NGROK_URL = "https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz" DATA_DIR = Path("/data") JAVA_DIR = DATA_DIR / "java" NGROK_DIR = DATA_DIR / "ngrok" JAR_NAME = "paper-1.20.1-196.jar" JAR_PATH = DATA_DIR / JAR_NAME # Java download URLs (OpenJDK 17) JAVA_URLS = { "linux_x64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz", "linux_aarch64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz" } def load_env_vars(): """Load environment variables from .env file if it exists""" env_vars = {} env_file = Path(".env") if env_file.exists(): try: with open(env_file, 'r') as f: for line in f: line = line.strip() if line and not line.startswith('#') and '=' in line: key, value = line.split('=', 1) env_vars[key.strip()] = value.strip().strip('"\'') print(f"Loaded {len(env_vars)} environment variables from .env") except Exception as e: print(f"Warning: Could not read .env file: {e}") # Also check system environment variables ngrok_token = env_vars.get('NGROK_TOKEN') or os.getenv('NGROK_TOKEN') return { 'NGROK_TOKEN': ngrok_token } def check_ngrok(): """Check if ngrok is available and return the path""" # Check system PATH first ngrok_path = shutil.which("ngrok") if ngrok_path: try: result = subprocess.run([ngrok_path, "version"], capture_output=True, text=True) print(f"Found ngrok: {ngrok_path}") print(f"Version: {result.stdout.strip()}") return ngrok_path except: pass # Check our custom ngrok installation custom_ngrok = NGROK_DIR / "ngrok" if custom_ngrok.exists(): try: result = subprocess.run([str(custom_ngrok), "version"], capture_output=True, text=True) print(f"Found custom ngrok: {custom_ngrok}") print(f"Version: {result.stdout.strip()}") return str(custom_ngrok) except: pass return None def install_ngrok(): """Download and install ngrok""" print("Installing ngrok...") # Create ngrok directory NGROK_DIR.mkdir(parents=True, exist_ok=True) # Download ngrok ngrok_archive = DATA_DIR / "ngrok.tgz" if not download_file(NGROK_URL, ngrok_archive): return False # Extract ngrok print("Extracting ngrok...") try: import tarfile with tarfile.open(ngrok_archive, 'r:gz') as tar: tar.extractall(NGROK_DIR) # Make ngrok executable ngrok_bin = NGROK_DIR / "ngrok" ngrok_bin.chmod(0o755) # Clean up ngrok_archive.unlink() print(f"✓ ngrok installed to: {ngrok_bin}") return True except Exception as e: print(f"Failed to extract ngrok: {e}") return False def setup_ngrok(): """Setup ngrok with authentication token""" env_vars = load_env_vars() ngrok_token = env_vars.get('NGROK_TOKEN') if not ngrok_token: print("Warning: NGROK_TOKEN not found in environment variables or .env file") print("Please set NGROK_TOKEN in your .env file or Hugging Face Space secrets") return False # Get ngrok path ngrok_path = check_ngrok() if not ngrok_path: print("ngrok not found. Installing...") if not install_ngrok(): print("Failed to install ngrok") return False ngrok_path = check_ngrok() if not ngrok_path: print("ngrok installation failed") return False # Configure ngrok with auth token print("Configuring ngrok with auth token...") try: result = subprocess.run([ ngrok_path, "config", "add-authtoken", ngrok_token ], capture_output=True, text=True) if result.returncode == 0: print("✓ ngrok configured successfully") return True else: print(f"Failed to configure ngrok: {result.stderr}") return False except Exception as e: print(f"Failed to configure ngrok: {e}") return False def start_ngrok_tunnel(port=25565): """Start ngrok tunnel for the Minecraft server""" ngrok_path = check_ngrok() if not ngrok_path: print("ngrok not found. Run setup first.") return None print(f"Starting ngrok tunnel for port {port}...") try: # Start ngrok in background process = subprocess.Popen([ ngrok_path, "tcp", str(port), "--log", "stdout" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # Give ngrok a moment to start import time time.sleep(3) # Try to get the tunnel URL from ngrok API try: import json import urllib.request # ngrok exposes a local API on port 4040 api_url = "http://localhost:4040/api/tunnels" with urllib.request.urlopen(api_url) as response: data = json.loads(response.read().decode()) if data.get('tunnels'): tunnel_url = data['tunnels'][0]['public_url'] print(f"✓ ngrok tunnel active: {tunnel_url}") print(f"Players can connect to: {tunnel_url.replace('tcp://', '')}") return process, tunnel_url else: print("No active tunnels found") return process, None except Exception as e: print(f"Could not retrieve tunnel URL: {e}") print("Check ngrok dashboard at http://localhost:4040") return process, None except Exception as e: print(f"Failed to start ngrok tunnel: {e}") return None, None def check_java(): """Check if Java is available and return the path""" # Check if java is already in PATH java_path = shutil.which("java") if java_path: try: result = subprocess.run([java_path, "-version"], capture_output=True, text=True) print(f"Found Java: {java_path}") print(result.stderr.split('\n')[0]) # Java version info is in stderr return java_path except: pass # Check our custom Java installation custom_java = JAVA_DIR / "bin" / "java" if custom_java.exists(): try: result = subprocess.run([str(custom_java), "-version"], capture_output=True, text=True) print(f"Found custom Java: {custom_java}") print(result.stderr.split('\n')[0]) # Java version info is in stderr return str(custom_java) except: pass return None """Detect the platform architecture""" import platform machine = platform.machine().lower() if 'x86_64' in machine or 'amd64' in machine: return "linux_x64" elif 'aarch64' in machine or 'arm64' in machine: return "linux_aarch64" else: print(f"Unsupported architecture: {machine}") return "linux_x64" # Default fallback def download_file(url, destination): print(f"Downloading {url}...") try: destination.parent.mkdir(parents=True, exist_ok=True) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } request = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(request) as response: with open(destination, 'wb') as f: total_size = int(response.headers.get('content-length', 0)) block_size = 8192 downloaded = 0 while True: buffer = response.read(block_size) if not buffer: break downloaded += len(buffer) f.write(buffer) if total_size: percent = min(100, (downloaded * 100) // total_size) print(f"\rProgress: {percent}% ({downloaded}/{total_size} bytes)", end="") print(f"\n✓ Downloaded: {destination}") return True except Exception as e: print(f"\n✗ Failed to download {url}: {e}") return False def install_java(): """Download and install Java if not available""" print("Installing Java...") platform_key = get_platform() java_url = JAVA_URLS.get(platform_key) if not java_url: print(f"No Java download available for platform: {platform_key}") return False # Download Java java_archive = DATA_DIR / "java.tar.gz" if not download_file(java_url, java_archive): return False # Extract Java print("Extracting Java...") try: import tarfile with tarfile.open(java_archive, 'r:gz') as tar: # Extract to temporary directory first temp_dir = DATA_DIR / "java_temp" temp_dir.mkdir(exist_ok=True) tar.extractall(temp_dir) # Find the extracted JDK directory (usually has a version number) extracted_dirs = [d for d in temp_dir.iterdir() if d.is_dir() and d.name.startswith('jdk')] if not extracted_dirs: print("Could not find extracted JDK directory") return False jdk_dir = extracted_dirs[0] # Move to final location if JAVA_DIR.exists(): shutil.rmtree(JAVA_DIR) shutil.move(str(jdk_dir), str(JAVA_DIR)) # Clean up shutil.rmtree(temp_dir) java_archive.unlink() print(f"✓ Java installed to: {JAVA_DIR}") # Make java executable java_bin = JAVA_DIR / "bin" / "java" java_bin.chmod(0o755) return True except Exception as e: print(f"Failed to extract Java: {e}") return False def setup_minecraft_server(): """Set up the Minecraft server in persistent storage""" # Ensure the data directory exists DATA_DIR.mkdir(parents=True, exist_ok=True) # Check and install Java if needed java_path = check_java() if not java_path: print("Java not found. Installing Java...") if not install_java(): print("Failed to install Java. Cannot proceed.") return False java_path = check_java() if not java_path: print("Java installation failed or not working.") return False # Download the server JAR if it doesn't exist if not JAR_PATH.exists(): print(f"Downloading Minecraft server to {JAR_PATH}") if not download_file(PAPER_JAR_URL, JAR_PATH): return False else: print(f"Server JAR already exists at {JAR_PATH}") # Create server.properties file if it doesn't exist server_properties_path = DATA_DIR / "server.properties" if not server_properties_path.exists(): print("Creating server.properties...") server_properties = """# Minecraft server properties server-port=25565 gamemode=survival difficulty=easy spawn-protection=16 max-players=20 online-mode=false white-list=false motd=Hugging Face Minecraft Server """ with open(server_properties_path, 'w') as f: f.write(server_properties) # Create eula.txt (required for server to start) eula_path = DATA_DIR / "eula.txt" if not eula_path.exists(): print("Creating eula.txt...") with open(eula_path, 'w') as f: f.write("eula=true\n") print("✓ Minecraft server setup complete!") print(f"Server files are stored in: {DATA_DIR}") print(f"Java path: {java_path}") return True def start_server(): """Start the Minecraft server""" if not JAR_PATH.exists(): print("Server JAR not found. Run setup first.") return False # Get Java path java_path = check_java() if not java_path: print("Java not found. Run setup first.") return False # Change to the data directory os.chdir(DATA_DIR) # Start the server cmd = [ java_path, "-Xmx2G", # Max heap size "-Xms1G", # Initial heap size "-jar", str(JAR_PATH), "--nogui" ] print(f"Starting Minecraft server with command: {' '.join(cmd)}") print(f"Working directory: {DATA_DIR}") try: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) # Print server output in real-time for line in process.stdout: print(line.strip()) except Exception as e: print(f"Failed to start server: {e}") return False if __name__ == "__main__": if setup_minecraft_server(): start_server() else: print("Setup failed. Cannot start server.")