mc-server / app.py
aldigobbler's picture
asdf
417d689
raw
history blame
13.8 kB
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.")