aldigobbler commited on
Commit
417d689
·
1 Parent(s): b676880
Files changed (1) hide show
  1. app.py +313 -6
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 setup_minecraft_server():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- "java",
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
- start_server()
 
 
 
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.")