Spaces:
Running
Running
Commit
·
417d689
1
Parent(s):
b676880
asdf
Browse files
app.py
CHANGED
@@ -3,12 +3,228 @@ from pathlib import Path
|
|
3 |
import os
|
4 |
import subprocess
|
5 |
import sys
|
|
|
6 |
|
7 |
PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar"
|
|
|
|
|
8 |
DATA_DIR = Path("/data")
|
|
|
|
|
9 |
JAR_NAME = "paper-1.20.1-196.jar"
|
10 |
JAR_PATH = DATA_DIR / JAR_NAME
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
def download_file(url, destination):
|
13 |
print(f"Downloading {url}...")
|
14 |
try:
|
@@ -43,9 +259,80 @@ def download_file(url, destination):
|
|
43 |
print(f"\n✗ Failed to download {url}: {e}")
|
44 |
return False
|
45 |
|
46 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
if not JAR_PATH.exists():
|
50 |
print(f"Downloading Minecraft server to {JAR_PATH}")
|
51 |
if not download_file(PAPER_JAR_URL, JAR_PATH):
|
@@ -53,8 +340,10 @@ def setup_minecraft_server():
|
|
53 |
else:
|
54 |
print(f"Server JAR already exists at {JAR_PATH}")
|
55 |
|
|
|
56 |
server_properties_path = DATA_DIR / "server.properties"
|
57 |
if not server_properties_path.exists():
|
|
|
58 |
server_properties = """# Minecraft server properties
|
59 |
server-port=25565
|
60 |
gamemode=survival
|
@@ -68,23 +357,38 @@ motd=Hugging Face Minecraft Server
|
|
68 |
with open(server_properties_path, 'w') as f:
|
69 |
f.write(server_properties)
|
70 |
|
|
|
71 |
eula_path = DATA_DIR / "eula.txt"
|
72 |
if not eula_path.exists():
|
|
|
73 |
with open(eula_path, 'w') as f:
|
74 |
f.write("eula=true\n")
|
|
|
|
|
|
|
|
|
75 |
return True
|
76 |
|
77 |
def start_server():
|
|
|
78 |
if not JAR_PATH.exists():
|
79 |
print("Server JAR not found. Run setup first.")
|
80 |
return False
|
81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
os.chdir(DATA_DIR)
|
83 |
|
|
|
84 |
cmd = [
|
85 |
-
|
86 |
-
"-Xmx2G",
|
87 |
-
"-Xms1G",
|
88 |
"-jar", str(JAR_PATH),
|
89 |
"--nogui"
|
90 |
]
|
@@ -101,6 +405,7 @@ def start_server():
|
|
101 |
bufsize=1
|
102 |
)
|
103 |
|
|
|
104 |
for line in process.stdout:
|
105 |
print(line.strip())
|
106 |
|
@@ -109,5 +414,7 @@ def start_server():
|
|
109 |
return False
|
110 |
|
111 |
if __name__ == "__main__":
|
112 |
-
setup_minecraft_server()
|
113 |
-
|
|
|
|
|
|
3 |
import os
|
4 |
import subprocess
|
5 |
import sys
|
6 |
+
import shutil
|
7 |
|
8 |
PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar"
|
9 |
+
NGROK_URL = "https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz"
|
10 |
+
|
11 |
DATA_DIR = Path("/data")
|
12 |
+
JAVA_DIR = DATA_DIR / "java"
|
13 |
+
NGROK_DIR = DATA_DIR / "ngrok"
|
14 |
JAR_NAME = "paper-1.20.1-196.jar"
|
15 |
JAR_PATH = DATA_DIR / JAR_NAME
|
16 |
|
17 |
+
# Java download URLs (OpenJDK 17)
|
18 |
+
JAVA_URLS = {
|
19 |
+
"linux_x64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz",
|
20 |
+
"linux_aarch64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz"
|
21 |
+
}
|
22 |
+
|
23 |
+
def load_env_vars():
|
24 |
+
"""Load environment variables from .env file if it exists"""
|
25 |
+
env_vars = {}
|
26 |
+
env_file = Path(".env")
|
27 |
+
|
28 |
+
if env_file.exists():
|
29 |
+
try:
|
30 |
+
with open(env_file, 'r') as f:
|
31 |
+
for line in f:
|
32 |
+
line = line.strip()
|
33 |
+
if line and not line.startswith('#') and '=' in line:
|
34 |
+
key, value = line.split('=', 1)
|
35 |
+
env_vars[key.strip()] = value.strip().strip('"\'')
|
36 |
+
print(f"Loaded {len(env_vars)} environment variables from .env")
|
37 |
+
except Exception as e:
|
38 |
+
print(f"Warning: Could not read .env file: {e}")
|
39 |
+
|
40 |
+
# Also check system environment variables
|
41 |
+
ngrok_token = env_vars.get('NGROK_TOKEN') or os.getenv('NGROK_TOKEN')
|
42 |
+
|
43 |
+
return {
|
44 |
+
'NGROK_TOKEN': ngrok_token
|
45 |
+
}
|
46 |
+
|
47 |
+
def check_ngrok():
|
48 |
+
"""Check if ngrok is available and return the path"""
|
49 |
+
# Check system PATH first
|
50 |
+
ngrok_path = shutil.which("ngrok")
|
51 |
+
if ngrok_path:
|
52 |
+
try:
|
53 |
+
result = subprocess.run([ngrok_path, "version"], capture_output=True, text=True)
|
54 |
+
print(f"Found ngrok: {ngrok_path}")
|
55 |
+
print(f"Version: {result.stdout.strip()}")
|
56 |
+
return ngrok_path
|
57 |
+
except:
|
58 |
+
pass
|
59 |
+
|
60 |
+
# Check our custom ngrok installation
|
61 |
+
custom_ngrok = NGROK_DIR / "ngrok"
|
62 |
+
if custom_ngrok.exists():
|
63 |
+
try:
|
64 |
+
result = subprocess.run([str(custom_ngrok), "version"], capture_output=True, text=True)
|
65 |
+
print(f"Found custom ngrok: {custom_ngrok}")
|
66 |
+
print(f"Version: {result.stdout.strip()}")
|
67 |
+
return str(custom_ngrok)
|
68 |
+
except:
|
69 |
+
pass
|
70 |
+
|
71 |
+
return None
|
72 |
+
|
73 |
+
def install_ngrok():
|
74 |
+
"""Download and install ngrok"""
|
75 |
+
print("Installing ngrok...")
|
76 |
+
|
77 |
+
# Create ngrok directory
|
78 |
+
NGROK_DIR.mkdir(parents=True, exist_ok=True)
|
79 |
+
|
80 |
+
# Download ngrok
|
81 |
+
ngrok_archive = DATA_DIR / "ngrok.tgz"
|
82 |
+
if not download_file(NGROK_URL, ngrok_archive):
|
83 |
+
return False
|
84 |
+
|
85 |
+
# Extract ngrok
|
86 |
+
print("Extracting ngrok...")
|
87 |
+
try:
|
88 |
+
import tarfile
|
89 |
+
with tarfile.open(ngrok_archive, 'r:gz') as tar:
|
90 |
+
tar.extractall(NGROK_DIR)
|
91 |
+
|
92 |
+
# Make ngrok executable
|
93 |
+
ngrok_bin = NGROK_DIR / "ngrok"
|
94 |
+
ngrok_bin.chmod(0o755)
|
95 |
+
|
96 |
+
# Clean up
|
97 |
+
ngrok_archive.unlink()
|
98 |
+
|
99 |
+
print(f"✓ ngrok installed to: {ngrok_bin}")
|
100 |
+
return True
|
101 |
+
|
102 |
+
except Exception as e:
|
103 |
+
print(f"Failed to extract ngrok: {e}")
|
104 |
+
return False
|
105 |
+
|
106 |
+
def setup_ngrok():
|
107 |
+
"""Setup ngrok with authentication token"""
|
108 |
+
env_vars = load_env_vars()
|
109 |
+
ngrok_token = env_vars.get('NGROK_TOKEN')
|
110 |
+
|
111 |
+
if not ngrok_token:
|
112 |
+
print("Warning: NGROK_TOKEN not found in environment variables or .env file")
|
113 |
+
print("Please set NGROK_TOKEN in your .env file or Hugging Face Space secrets")
|
114 |
+
return False
|
115 |
+
|
116 |
+
# Get ngrok path
|
117 |
+
ngrok_path = check_ngrok()
|
118 |
+
if not ngrok_path:
|
119 |
+
print("ngrok not found. Installing...")
|
120 |
+
if not install_ngrok():
|
121 |
+
print("Failed to install ngrok")
|
122 |
+
return False
|
123 |
+
ngrok_path = check_ngrok()
|
124 |
+
if not ngrok_path:
|
125 |
+
print("ngrok installation failed")
|
126 |
+
return False
|
127 |
+
|
128 |
+
# Configure ngrok with auth token
|
129 |
+
print("Configuring ngrok with auth token...")
|
130 |
+
try:
|
131 |
+
result = subprocess.run([
|
132 |
+
ngrok_path, "config", "add-authtoken", ngrok_token
|
133 |
+
], capture_output=True, text=True)
|
134 |
+
|
135 |
+
if result.returncode == 0:
|
136 |
+
print("✓ ngrok configured successfully")
|
137 |
+
return True
|
138 |
+
else:
|
139 |
+
print(f"Failed to configure ngrok: {result.stderr}")
|
140 |
+
return False
|
141 |
+
|
142 |
+
except Exception as e:
|
143 |
+
print(f"Failed to configure ngrok: {e}")
|
144 |
+
return False
|
145 |
+
|
146 |
+
def start_ngrok_tunnel(port=25565):
|
147 |
+
"""Start ngrok tunnel for the Minecraft server"""
|
148 |
+
ngrok_path = check_ngrok()
|
149 |
+
if not ngrok_path:
|
150 |
+
print("ngrok not found. Run setup first.")
|
151 |
+
return None
|
152 |
+
|
153 |
+
print(f"Starting ngrok tunnel for port {port}...")
|
154 |
+
try:
|
155 |
+
# Start ngrok in background
|
156 |
+
process = subprocess.Popen([
|
157 |
+
ngrok_path, "tcp", str(port), "--log", "stdout"
|
158 |
+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
159 |
+
|
160 |
+
# Give ngrok a moment to start
|
161 |
+
import time
|
162 |
+
time.sleep(3)
|
163 |
+
|
164 |
+
# Try to get the tunnel URL from ngrok API
|
165 |
+
try:
|
166 |
+
import json
|
167 |
+
import urllib.request
|
168 |
+
|
169 |
+
# ngrok exposes a local API on port 4040
|
170 |
+
api_url = "http://localhost:4040/api/tunnels"
|
171 |
+
with urllib.request.urlopen(api_url) as response:
|
172 |
+
data = json.loads(response.read().decode())
|
173 |
+
|
174 |
+
if data.get('tunnels'):
|
175 |
+
tunnel_url = data['tunnels'][0]['public_url']
|
176 |
+
print(f"✓ ngrok tunnel active: {tunnel_url}")
|
177 |
+
print(f"Players can connect to: {tunnel_url.replace('tcp://', '')}")
|
178 |
+
return process, tunnel_url
|
179 |
+
else:
|
180 |
+
print("No active tunnels found")
|
181 |
+
return process, None
|
182 |
+
|
183 |
+
except Exception as e:
|
184 |
+
print(f"Could not retrieve tunnel URL: {e}")
|
185 |
+
print("Check ngrok dashboard at http://localhost:4040")
|
186 |
+
return process, None
|
187 |
+
|
188 |
+
except Exception as e:
|
189 |
+
print(f"Failed to start ngrok tunnel: {e}")
|
190 |
+
return None, None
|
191 |
+
|
192 |
+
def check_java():
|
193 |
+
"""Check if Java is available and return the path"""
|
194 |
+
# Check if java is already in PATH
|
195 |
+
java_path = shutil.which("java")
|
196 |
+
if java_path:
|
197 |
+
try:
|
198 |
+
result = subprocess.run([java_path, "-version"], capture_output=True, text=True)
|
199 |
+
print(f"Found Java: {java_path}")
|
200 |
+
print(result.stderr.split('\n')[0]) # Java version info is in stderr
|
201 |
+
return java_path
|
202 |
+
except:
|
203 |
+
pass
|
204 |
+
|
205 |
+
# Check our custom Java installation
|
206 |
+
custom_java = JAVA_DIR / "bin" / "java"
|
207 |
+
if custom_java.exists():
|
208 |
+
try:
|
209 |
+
result = subprocess.run([str(custom_java), "-version"], capture_output=True, text=True)
|
210 |
+
print(f"Found custom Java: {custom_java}")
|
211 |
+
print(result.stderr.split('\n')[0]) # Java version info is in stderr
|
212 |
+
return str(custom_java)
|
213 |
+
except:
|
214 |
+
pass
|
215 |
+
|
216 |
+
return None
|
217 |
+
"""Detect the platform architecture"""
|
218 |
+
import platform
|
219 |
+
machine = platform.machine().lower()
|
220 |
+
if 'x86_64' in machine or 'amd64' in machine:
|
221 |
+
return "linux_x64"
|
222 |
+
elif 'aarch64' in machine or 'arm64' in machine:
|
223 |
+
return "linux_aarch64"
|
224 |
+
else:
|
225 |
+
print(f"Unsupported architecture: {machine}")
|
226 |
+
return "linux_x64" # Default fallback
|
227 |
+
|
228 |
def download_file(url, destination):
|
229 |
print(f"Downloading {url}...")
|
230 |
try:
|
|
|
259 |
print(f"\n✗ Failed to download {url}: {e}")
|
260 |
return False
|
261 |
|
262 |
+
def install_java():
|
263 |
+
"""Download and install Java if not available"""
|
264 |
+
print("Installing Java...")
|
265 |
+
|
266 |
+
platform_key = get_platform()
|
267 |
+
java_url = JAVA_URLS.get(platform_key)
|
268 |
+
|
269 |
+
if not java_url:
|
270 |
+
print(f"No Java download available for platform: {platform_key}")
|
271 |
+
return False
|
272 |
+
|
273 |
+
# Download Java
|
274 |
+
java_archive = DATA_DIR / "java.tar.gz"
|
275 |
+
if not download_file(java_url, java_archive):
|
276 |
+
return False
|
277 |
+
|
278 |
+
# Extract Java
|
279 |
+
print("Extracting Java...")
|
280 |
+
try:
|
281 |
+
import tarfile
|
282 |
+
with tarfile.open(java_archive, 'r:gz') as tar:
|
283 |
+
# Extract to temporary directory first
|
284 |
+
temp_dir = DATA_DIR / "java_temp"
|
285 |
+
temp_dir.mkdir(exist_ok=True)
|
286 |
+
tar.extractall(temp_dir)
|
287 |
+
|
288 |
+
# Find the extracted JDK directory (usually has a version number)
|
289 |
+
extracted_dirs = [d for d in temp_dir.iterdir() if d.is_dir() and d.name.startswith('jdk')]
|
290 |
+
if not extracted_dirs:
|
291 |
+
print("Could not find extracted JDK directory")
|
292 |
+
return False
|
293 |
+
|
294 |
+
jdk_dir = extracted_dirs[0]
|
295 |
+
|
296 |
+
# Move to final location
|
297 |
+
if JAVA_DIR.exists():
|
298 |
+
shutil.rmtree(JAVA_DIR)
|
299 |
+
shutil.move(str(jdk_dir), str(JAVA_DIR))
|
300 |
+
|
301 |
+
# Clean up
|
302 |
+
shutil.rmtree(temp_dir)
|
303 |
+
java_archive.unlink()
|
304 |
+
|
305 |
+
print(f"✓ Java installed to: {JAVA_DIR}")
|
306 |
+
|
307 |
+
# Make java executable
|
308 |
+
java_bin = JAVA_DIR / "bin" / "java"
|
309 |
+
java_bin.chmod(0o755)
|
310 |
+
|
311 |
+
return True
|
312 |
+
|
313 |
+
except Exception as e:
|
314 |
+
print(f"Failed to extract Java: {e}")
|
315 |
+
return False
|
316 |
+
|
317 |
+
def setup_minecraft_server():
|
318 |
+
"""Set up the Minecraft server in persistent storage"""
|
319 |
+
|
320 |
+
# Ensure the data directory exists
|
321 |
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
322 |
|
323 |
+
# Check and install Java if needed
|
324 |
+
java_path = check_java()
|
325 |
+
if not java_path:
|
326 |
+
print("Java not found. Installing Java...")
|
327 |
+
if not install_java():
|
328 |
+
print("Failed to install Java. Cannot proceed.")
|
329 |
+
return False
|
330 |
+
java_path = check_java()
|
331 |
+
if not java_path:
|
332 |
+
print("Java installation failed or not working.")
|
333 |
+
return False
|
334 |
+
|
335 |
+
# Download the server JAR if it doesn't exist
|
336 |
if not JAR_PATH.exists():
|
337 |
print(f"Downloading Minecraft server to {JAR_PATH}")
|
338 |
if not download_file(PAPER_JAR_URL, JAR_PATH):
|
|
|
340 |
else:
|
341 |
print(f"Server JAR already exists at {JAR_PATH}")
|
342 |
|
343 |
+
# Create server.properties file if it doesn't exist
|
344 |
server_properties_path = DATA_DIR / "server.properties"
|
345 |
if not server_properties_path.exists():
|
346 |
+
print("Creating server.properties...")
|
347 |
server_properties = """# Minecraft server properties
|
348 |
server-port=25565
|
349 |
gamemode=survival
|
|
|
357 |
with open(server_properties_path, 'w') as f:
|
358 |
f.write(server_properties)
|
359 |
|
360 |
+
# Create eula.txt (required for server to start)
|
361 |
eula_path = DATA_DIR / "eula.txt"
|
362 |
if not eula_path.exists():
|
363 |
+
print("Creating eula.txt...")
|
364 |
with open(eula_path, 'w') as f:
|
365 |
f.write("eula=true\n")
|
366 |
+
|
367 |
+
print("✓ Minecraft server setup complete!")
|
368 |
+
print(f"Server files are stored in: {DATA_DIR}")
|
369 |
+
print(f"Java path: {java_path}")
|
370 |
return True
|
371 |
|
372 |
def start_server():
|
373 |
+
"""Start the Minecraft server"""
|
374 |
if not JAR_PATH.exists():
|
375 |
print("Server JAR not found. Run setup first.")
|
376 |
return False
|
377 |
|
378 |
+
# Get Java path
|
379 |
+
java_path = check_java()
|
380 |
+
if not java_path:
|
381 |
+
print("Java not found. Run setup first.")
|
382 |
+
return False
|
383 |
+
|
384 |
+
# Change to the data directory
|
385 |
os.chdir(DATA_DIR)
|
386 |
|
387 |
+
# Start the server
|
388 |
cmd = [
|
389 |
+
java_path,
|
390 |
+
"-Xmx2G", # Max heap size
|
391 |
+
"-Xms1G", # Initial heap size
|
392 |
"-jar", str(JAR_PATH),
|
393 |
"--nogui"
|
394 |
]
|
|
|
405 |
bufsize=1
|
406 |
)
|
407 |
|
408 |
+
# Print server output in real-time
|
409 |
for line in process.stdout:
|
410 |
print(line.strip())
|
411 |
|
|
|
414 |
return False
|
415 |
|
416 |
if __name__ == "__main__":
|
417 |
+
if setup_minecraft_server():
|
418 |
+
start_server()
|
419 |
+
else:
|
420 |
+
print("Setup failed. Cannot start server.")
|