aldigobbler commited on
Commit
f17545d
·
verified ·
1 Parent(s): 0e6f0a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -471
app.py CHANGED
@@ -4,33 +4,20 @@ import os
4
  import subprocess
5
  import sys
6
  import shutil
7
- import gradio as gr
8
  import threading
9
  import time
10
  import signal
11
-
12
- try:
13
- from pyngrok import ngrok, conf
14
- PYNGROK_AVAILABLE = True
15
- except ImportError:
16
- print("pyngrok not found. Installing...")
17
- try:
18
- subprocess.check_call([sys.executable, "-m", "pip", "install", "pyngrok"])
19
- from pyngrok import ngrok, conf
20
- PYNGROK_AVAILABLE = True
21
- print("✓ pyngrok installed successfully")
22
- except Exception as e:
23
- print(f"Failed to install pyngrok: {e}")
24
- PYNGROK_AVAILABLE = False
25
-
26
- subprocess.check_call([sys.executable, "-m", "pip", "install", "mcstatus"])
27
  from mcstatus import JavaServer
28
 
29
-
30
  PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar"
 
31
 
 
32
  DATA_DIR = Path("/data")
33
  JAVA_DIR = DATA_DIR / "java"
 
34
  JAR_NAME = "paper-1.20.1-196.jar"
35
  JAR_PATH = DATA_DIR / JAR_NAME
36
 
@@ -40,32 +27,7 @@ JAVA_URLS = {
40
  "linux_aarch64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz"
41
  }
42
 
43
- def load_env_vars():
44
- """Load environment variables from .env file if it exists"""
45
- env_vars = {}
46
- env_file = Path(".env")
47
-
48
- if env_file.exists():
49
- try:
50
- with open(env_file, 'r') as f:
51
- for line in f:
52
- line = line.strip()
53
- if line and not line.startswith('#') and '=' in line:
54
- key, value = line.split('=', 1)
55
- env_vars[key.strip()] = value.strip().strip('"\'')
56
- print(f"Loaded {len(env_vars)} environment variables from .env")
57
- except Exception as e:
58
- print(f"Warning: Could not read .env file: {e}")
59
-
60
- # Also check system environment variables
61
- ngrok_token = env_vars.get('NGROK_TOKEN') or os.getenv('NGROK_TOKEN')
62
-
63
- return {
64
- 'NGROK_TOKEN': ngrok_token
65
- }
66
-
67
  def get_platform():
68
- """Detect the platform architecture"""
69
  import platform
70
  machine = platform.machine().lower()
71
  if 'x86_64' in machine or 'amd64' in machine:
@@ -74,492 +36,111 @@ def get_platform():
74
  return "linux_aarch64"
75
  else:
76
  print(f"Unsupported architecture: {machine}")
77
- return "linux_x64" # Default fallback
78
-
79
- def check_ngrok():
80
- """Check if ngrok is available and return the path"""
81
- print("Checking for ngrok...")
82
-
83
- # Check system PATH first
84
- ngrok_path = shutil.which("ngrok")
85
- if ngrok_path:
86
- try:
87
- result = subprocess.run([ngrok_path, "version"], capture_output=True, text=True)
88
- print(f"Found ngrok in PATH: {ngrok_path}")
89
- print(f"Version: {result.stdout.strip()}")
90
- return ngrok_path
91
- except Exception as e:
92
- print(f"Error checking system ngrok: {e}")
93
-
94
- # Check our custom ngrok installation
95
- custom_ngrok = NGROK_DIR / "ngrok"
96
- if custom_ngrok.exists():
97
- try:
98
- result = subprocess.run([str(custom_ngrok), "version"], capture_output=True, text=True)
99
- print(f"Found custom ngrok: {custom_ngrok}")
100
- print(f"Version: {result.stdout.strip()}")
101
- return str(custom_ngrok)
102
- except Exception as e:
103
- print(f"Error checking custom ngrok: {e}")
104
-
105
- print("ngrok not found")
106
- return None
107
-
108
- def install_ngrok():
109
- """Download and install ngrok"""
110
- print("Installing ngrok...")
111
-
112
- # Create ngrok directory
113
- NGROK_DIR.mkdir(parents=True, exist_ok=True)
114
-
115
- # Download ngrok
116
- ngrok_archive = DATA_DIR / "ngrok.tgz"
117
- if not download_file(NGROK_URL, ngrok_archive):
118
- return False
119
-
120
- # Extract ngrok
121
- print("Extracting ngrok...")
122
- try:
123
- import tarfile
124
- with tarfile.open(ngrok_archive, 'r:gz') as tar:
125
- tar.extractall(NGROK_DIR)
126
-
127
- # Make ngrok executable
128
- ngrok_bin = NGROK_DIR / "ngrok"
129
- ngrok_bin.chmod(0o755)
130
-
131
- # Clean up
132
- ngrok_archive.unlink()
133
-
134
- print(f"✓ ngrok installed to: {ngrok_bin}")
135
- return True
136
-
137
- except Exception as e:
138
- print(f"Failed to extract ngrok: {e}")
139
- return False
140
-
141
- def setup_ngrok():
142
- """Setup ngrok with authentication token using pyngrok"""
143
- print("Setting up ngrok...")
144
-
145
- if not PYNGROK_AVAILABLE:
146
- print("pyngrok is not available. Cannot setup ngrok.")
147
- return False
148
-
149
- env_vars = load_env_vars()
150
- ngrok_token = env_vars.get('NGROK_TOKEN')
151
-
152
- if not ngrok_token:
153
- print("Warning: NGROK_TOKEN not found in environment variables or .env file")
154
- print("Please set NGROK_TOKEN in your .env file or Hugging Face Space secrets")
155
- return False
156
-
157
- try:
158
- # Set the auth token
159
- ngrok.set_auth_token(ngrok_token)
160
- print("✓ ngrok configured successfully with pyngrok")
161
- return True
162
-
163
- except Exception as e:
164
- print(f"Failed to configure ngrok: {e}")
165
- return False
166
-
167
- def start_ngrok_tunnel(port=25565):
168
- """Start ngrok tunnel for the Minecraft server using pyngrok"""
169
- print(f"Starting ngrok tunnel for port {port}...")
170
-
171
- if not PYNGROK_AVAILABLE:
172
- print("pyngrok is not available. Cannot start tunnel.")
173
- return None, None
174
-
175
- try:
176
- # Create a TCP tunnel
177
- print("Creating ngrok tunnel...")
178
- tunnel = ngrok.connect(port, "tcp")
179
- tunnel_url = tunnel.public_url
180
-
181
- print(f"✓ ngrok tunnel active: {tunnel_url}")
182
- print(f"Players can connect to: {tunnel_url.replace('tcp://', '')}")
183
-
184
- # Get all active tunnels for reference
185
- tunnels = ngrok.get_tunnels()
186
- print(f"Active tunnels: {len(tunnels)}")
187
-
188
- return tunnel, tunnel_url
189
-
190
- except Exception as e:
191
- print(f"Failed to start ngrok tunnel: {e}")
192
- return None, None
193
 
194
  def check_java():
195
- """Check if Java is available and return the path"""
196
- # Check if java is already in PATH
197
  java_path = shutil.which("java")
198
  if java_path:
199
- try:
200
- result = subprocess.run([java_path, "-version"], capture_output=True, text=True)
201
- print(f"Found Java: {java_path}")
202
- print(result.stderr.split('\n')[0])
203
- return java_path
204
- except:
205
- pass
206
-
207
  custom_java = JAVA_DIR / "bin" / "java"
208
  if custom_java.exists():
209
- try:
210
- result = subprocess.run([str(custom_java), "-version"], capture_output=True, text=True)
211
- print(f"Found custom Java: {custom_java}")
212
- print(result.stderr.split('\n')[0])
213
- return str(custom_java)
214
- except:
215
- pass
216
-
217
  return None
218
 
219
  def download_file(url, destination):
220
- print(f"Downloading {url}...")
221
  try:
222
  destination.parent.mkdir(parents=True, exist_ok=True)
223
-
224
- headers = {
225
- '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'
226
- }
227
- request = urllib.request.Request(url, headers=headers)
228
-
229
- with urllib.request.urlopen(request) as response:
230
- with open(destination, 'wb') as f:
231
- total_size = int(response.headers.get('content-length', 0))
232
- block_size = 8192
233
- downloaded = 0
234
-
235
- while True:
236
- buffer = response.read(block_size)
237
- if not buffer:
238
- break
239
-
240
- downloaded += len(buffer)
241
- f.write(buffer)
242
-
243
- if total_size:
244
- percent = min(100, (downloaded * 100) // total_size)
245
- print(f"\rProgress: {percent}% ({downloaded}/{total_size} bytes)", end="")
246
-
247
- print(f"\n✓ Downloaded: {destination}")
248
  return True
249
  except Exception as e:
250
- print(f"\n✗ Failed to download {url}: {e}")
251
  return False
252
 
253
  def install_java():
254
- """Download and install Java if not available"""
255
- print("Installing Java...")
256
-
257
  platform_key = get_platform()
258
  java_url = JAVA_URLS.get(platform_key)
259
-
260
  if not java_url:
261
- print(f"No Java download available for platform: {platform_key}")
262
  return False
263
-
264
  java_archive = DATA_DIR / "java.tar.gz"
265
  if not download_file(java_url, java_archive):
266
  return False
267
-
268
- print("Extracting Java...")
269
- try:
270
- import tarfile
271
- with tarfile.open(java_archive, 'r:gz') as tar:
272
- temp_dir = DATA_DIR / "java_temp"
273
- temp_dir.mkdir(exist_ok=True)
274
- tar.extractall(temp_dir)
275
-
276
- extracted_dirs = [d for d in temp_dir.iterdir() if d.is_dir() and d.name.startswith('jdk')]
277
- if not extracted_dirs:
278
- print("Could not find extracted JDK directory")
279
- return False
280
-
281
- jdk_dir = extracted_dirs[0]
282
-
283
- if JAVA_DIR.exists():
284
- shutil.rmtree(JAVA_DIR)
285
- shutil.move(str(jdk_dir), str(JAVA_DIR))
286
-
287
- shutil.rmtree(temp_dir)
288
- java_archive.unlink()
289
-
290
- print(f"✓ Java installed to: {JAVA_DIR}")
291
-
292
- java_bin = JAVA_DIR / "bin" / "java"
293
- java_bin.chmod(0o755)
294
-
295
- return True
296
-
297
- except Exception as e:
298
- print(f"Failed to extract Java: {e}")
299
- return False
300
 
301
  def setup_minecraft_server():
302
  DATA_DIR.mkdir(parents=True, exist_ok=True)
303
-
304
  java_path = check_java()
305
  if not java_path:
306
- print("Java not found. Installing Java...")
307
  if not install_java():
308
- print("Failed to install Java. Cannot proceed.")
309
  return False
310
  java_path = check_java()
311
  if not java_path:
312
- print("Java installation failed or not working.")
313
  return False
314
-
315
  if not JAR_PATH.exists():
316
- print(f"Downloading Minecraft server to {JAR_PATH}")
317
  if not download_file(PAPER_JAR_URL, JAR_PATH):
318
  return False
319
- else:
320
- print(f"Server JAR already exists at {JAR_PATH}")
321
-
322
- server_properties_path = DATA_DIR / "server.properties"
323
- print("Creating server.properties...")
324
- server_properties = """# Minecraft server properties
325
- server-port=25565
326
  gamemode=survival
327
  difficulty=easy
328
  spawn-protection=0
329
  max-players=64
330
  online-mode=true
331
- white-list=false
332
- motd=Hugging Face Spaces Minecraft Server
333
  enable-query=true
334
  query.port=25565
335
- """
336
- with open(server_properties_path, 'w') as f:
337
- f.write(server_properties)
338
-
339
- # Create eula.txt (required for server to start)
340
- eula_path = DATA_DIR / "eula.txt"
341
- if not eula_path.exists():
342
- print("Creating eula.txt...")
343
- with open(eula_path, 'w') as f:
344
- f.write("eula=true\n")
345
-
346
- print("✓ Minecraft server setup complete!")
347
- print(f"Server files are stored in: {DATA_DIR}")
348
- print(f"Java path: {java_path}")
349
  return True
350
 
351
- def start_server():
352
- """Start the Minecraft server"""
353
- if not JAR_PATH.exists():
354
- print("Server JAR not found. Run setup first.")
355
- return False
356
-
357
- # Get Java path
358
- java_path = check_java()
359
- if not java_path:
360
- print("Java not found. Run setup first.")
361
- return False
362
-
363
- # Change to the data directory
364
- os.chdir(DATA_DIR)
365
-
366
- # Start the server
367
- cmd = [
368
- java_path,
369
- "-Xmx2G", # Max heap size
370
- "-Xms1G", # Initial heap size
371
- "-jar", str(JAR_PATH),
372
- "--nogui"
373
- ]
374
-
375
- print(f"Starting Minecraft server with command: {' '.join(cmd)}")
376
- print(f"Working directory: {DATA_DIR}")
377
-
378
- try:
379
- process = subprocess.Popen(
380
- cmd,
381
- stdout=subprocess.PIPE,
382
- stderr=subprocess.STDOUT,
383
- universal_newlines=True,
384
- bufsize=1
385
- )
386
-
387
- for line in process.stdout:
388
- print(line.strip())
389
-
390
- except Exception as e:
391
- print(f"Failed to start server: {e}")
392
- return False
393
-
394
  def start_server_background():
395
- """Start the Minecraft server in the background"""
396
  global server_process
397
-
398
- if not JAR_PATH.exists():
399
- print("Server JAR not found. Run setup first.")
400
- return False
401
-
402
- # Get Java path
403
  java_path = check_java()
404
- if not java_path:
405
- print("Java not found. Run setup first.")
406
- return False
407
-
408
- # Change to the data directory
409
- original_cwd = os.getcwd()
410
- os.chdir(DATA_DIR)
411
-
412
- # Start the server
413
- cmd = [
414
- java_path,
415
- "-Xmx2G", # Max heap size
416
- "-Xms1G", # Initial heap size
417
- "-jar", str(JAR_PATH),
418
- "--nogui"
419
- ]
420
-
421
- print(f"Starting Minecraft server in background with command: {' '.join(cmd)}")
422
- print(f"Working directory: {DATA_DIR}")
423
-
424
- try:
425
- server_process = subprocess.Popen(
426
- cmd,
427
- stdout=subprocess.PIPE,
428
- stderr=subprocess.STDOUT,
429
- universal_newlines=True,
430
- bufsize=1,
431
- cwd=DATA_DIR
432
- )
433
-
434
- # Start a thread to monitor server output
435
- def monitor_server():
436
- for line in server_process.stdout:
437
- print(f"[SERVER] {line.strip()}")
438
-
439
- monitor_thread = threading.Thread(target=monitor_server, daemon=True)
440
- monitor_thread.start()
441
-
442
- # Wait a bit for server to start
443
- time.sleep(5)
444
-
445
- os.chdir(original_cwd)
446
- print("✓ Minecraft server started in background")
447
- return True
448
-
449
- except Exception as e:
450
- print(f"Failed to start server: {e}")
451
- os.chdir(original_cwd)
452
- return False
453
-
454
- def create_gradio_interface(tunnel_url):
455
- """Create and return the Gradio interface"""
456
- server = JavaServer.lookup(tunnel_url)
457
- status = server.status()
458
- latency = server.ping()
459
-
460
- with gr.Blocks() as demo:
461
- def get_server_status():
462
- server = JavaServer.lookup(tunnel_url)
463
- status = server.status()
464
- latency = server.ping()
465
- return f"**Server Status:** {status.players.online} player(s) online, latency: {round(latency)} ms", f"**Server Version:** {status.version.name} ({status.version.protocol})", f"**Server Description:** {status.description}", f"**Players:** {', '.join(player.name for player in status.players.sample)}" if status.players.sample else "No players online"
466
- gr.Markdown("# Minecraft Server Status")
467
- gr.Markdown(f"**Server IP:** {tunnel_url}")
468
- status_markdown = gr.Markdown(f"**Server Status:** {status.players.online} player(s) online, latency: {round(latency)} ms")
469
- version_markdown = gr.Markdown(f"**Server Version:** {status.version.name} ({status.version.protocol})")
470
- description_markdown = gr.Markdown(f"**Server Description:** {status.description}")
471
- players_markdown = gr.Markdown(f"**Players:** {', '.join(player.name for player in status.players.sample)}" if status.players.sample else "No players online")
472
- btn = gr.Button("Refresh Status")
473
- btn.click(get_server_status, outputs=[status_markdown, version_markdown, description_markdown, players_markdown])
474
- return demo
475
 
476
  def stop_server():
477
- """Stop the Minecraft server"""
478
  global server_process
479
-
480
  if server_process:
481
- print("Stopping Minecraft server...")
482
  server_process.terminate()
483
- try:
484
- server_process.wait(timeout=10)
485
- except subprocess.TimeoutExpired:
486
- server_process.kill()
487
  server_process = None
488
- print("✓ Minecraft server stopped")
489
-
490
- def cleanup_ngrok():
491
- """Clean up ngrok tunnels on exit"""
492
- if PYNGROK_AVAILABLE:
493
- try:
494
- ngrok.kill()
495
- print("✓ ngrok tunnels cleaned up")
496
- except:
497
- pass
498
-
499
- def cleanup_all():
500
- """Clean up all resources"""
501
- stop_server()
502
- cleanup_ngrok()
503
 
504
  def signal_handler(signum, frame):
505
- """Handle shutdown signals"""
506
- print("\nReceived shutdown signal. Cleaning up...")
507
- cleanup_all()
508
  sys.exit(0)
509
 
510
  if __name__ == "__main__":
511
- # Register signal handlers
512
  signal.signal(signal.SIGINT, signal_handler)
513
  signal.signal(signal.SIGTERM, signal_handler)
514
-
515
- print("=== Minecraft Server Setup ===")
516
-
517
- try:
518
- if not setup_minecraft_server():
519
- print("Setup failed. Cannot start server.")
520
- sys.exit(1)
521
-
522
- print("\n=== Setting up ngrok ===")
523
- if not setup_ngrok():
524
- print("ngrok setup failed. Server will start but won't be accessible externally.")
525
- tunnel = None
526
- else:
527
- print("\n=== Starting ngrok tunnel ===")
528
- tunnel, tunnel_url = start_ngrok_tunnel()
529
- if tunnel:
530
- ngrok_tunnel = tunnel
531
- # Extract IP from tunnel URL (format: tcp://x.tcp.ngrok.io:port)
532
- server_ip = tunnel_url.replace('tcp://', '')
533
- print(f"Server will be accessible at: {server_ip}")
534
- else:
535
- print("Failed to start ngrok tunnel. Server will start but won't be accessible externally.")
536
-
537
- print("\n=== Starting Minecraft Server ===")
538
- if not start_server_background():
539
- print("Failed to start server.")
540
- cleanup_all()
541
- sys.exit(1)
542
-
543
- # Wait a bit for server to fully start
544
- print("Waiting for server to start...")
545
- time.sleep(10)
546
-
547
- print("\n=== Starting Gradio Interface ===")
548
- demo = create_gradio_interface(server_ip)
549
-
550
- # Launch Gradio interface
551
- demo.launch(
552
- server_name="0.0.0.0",
553
- server_port=7860,
554
- share=False,
555
- show_error=True
556
- )
557
-
558
- except KeyboardInterrupt:
559
- print("\n\nShutting down...")
560
- cleanup_all()
561
- sys.exit(0)
562
- except Exception as e:
563
- print(f"Unexpected error: {e}")
564
- cleanup_all()
565
- sys.exit(1)
 
4
  import subprocess
5
  import sys
6
  import shutil
 
7
  import threading
8
  import time
9
  import signal
10
+ import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  from mcstatus import JavaServer
12
 
13
+ # URLs
14
  PAPER_JAR_URL = "https://fill-data.papermc.io/v1/objects/234a9b32098100c6fc116664d64e36ccdb58b5b649af0f80bcccb08b0255eaea/paper-1.20.1-196.jar"
15
+ PLAYIT_PLUGIN_URL = "https://github.com/playit-cloud/playit-minecraft-plugin/releases/download/v0.1.4/playit-minecraft-plugin.jar"
16
 
17
+ # Paths
18
  DATA_DIR = Path("/data")
19
  JAVA_DIR = DATA_DIR / "java"
20
+ PLUGINS_DIR = DATA_DIR / "plugins"
21
  JAR_NAME = "paper-1.20.1-196.jar"
22
  JAR_PATH = DATA_DIR / JAR_NAME
23
 
 
27
  "linux_aarch64": "https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-aarch64_bin.tar.gz"
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def get_platform():
 
31
  import platform
32
  machine = platform.machine().lower()
33
  if 'x86_64' in machine or 'amd64' in machine:
 
36
  return "linux_aarch64"
37
  else:
38
  print(f"Unsupported architecture: {machine}")
39
+ return "linux_x64"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  def check_java():
 
 
42
  java_path = shutil.which("java")
43
  if java_path:
44
+ return java_path
 
 
 
 
 
 
 
45
  custom_java = JAVA_DIR / "bin" / "java"
46
  if custom_java.exists():
47
+ return str(custom_java)
 
 
 
 
 
 
 
48
  return None
49
 
50
  def download_file(url, destination):
51
+ print(f"Downloading {url} to {destination}...")
52
  try:
53
  destination.parent.mkdir(parents=True, exist_ok=True)
54
+ urllib.request.urlretrieve(url, destination)
55
+ print(f"✓ Downloaded {destination}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  return True
57
  except Exception as e:
58
+ print(f"✗ Failed to download {url}: {e}")
59
  return False
60
 
61
  def install_java():
 
 
 
62
  platform_key = get_platform()
63
  java_url = JAVA_URLS.get(platform_key)
 
64
  if not java_url:
 
65
  return False
 
66
  java_archive = DATA_DIR / "java.tar.gz"
67
  if not download_file(java_url, java_archive):
68
  return False
69
+ import tarfile
70
+ with tarfile.open(java_archive, 'r:gz') as tar:
71
+ temp_dir = DATA_DIR / "java_temp"
72
+ temp_dir.mkdir(exist_ok=True)
73
+ tar.extractall(temp_dir)
74
+ jdk_dir = next(d for d in temp_dir.iterdir() if d.is_dir() and d.name.startswith('jdk'))
75
+ if JAVA_DIR.exists():
76
+ shutil.rmtree(JAVA_DIR)
77
+ shutil.move(str(jdk_dir), str(JAVA_DIR))
78
+ shutil.rmtree(temp_dir)
79
+ java_archive.unlink()
80
+ (JAVA_DIR / "bin" / "java").chmod(0o755)
81
+ print(f"✓ Java installed to {JAVA_DIR}")
82
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  def setup_minecraft_server():
85
  DATA_DIR.mkdir(parents=True, exist_ok=True)
 
86
  java_path = check_java()
87
  if not java_path:
88
+ print("Java not found. Installing...")
89
  if not install_java():
 
90
  return False
91
  java_path = check_java()
92
  if not java_path:
 
93
  return False
 
94
  if not JAR_PATH.exists():
 
95
  if not download_file(PAPER_JAR_URL, JAR_PATH):
96
  return False
97
+ # Server config
98
+ (DATA_DIR / "server.properties").write_text("""server-port=25565
 
 
 
 
 
99
  gamemode=survival
100
  difficulty=easy
101
  spawn-protection=0
102
  max-players=64
103
  online-mode=true
104
+ motd=Minecraft Server
 
105
  enable-query=true
106
  query.port=25565
107
+ """)
108
+ (DATA_DIR / "eula.txt").write_text("eula=true\n")
109
+ # Download Playit plugin
110
+ PLUGINS_DIR.mkdir(parents=True, exist_ok=True)
111
+ playit_plugin_path = PLUGINS_DIR / "playit-minecraft-plugin.jar"
112
+ if download_file(PLAYIT_PLUGIN_URL, playit_plugin_path):
113
+ shutil.copy(playit_plugin_path, DATA_DIR / "playit-minecraft-plugin.jar")
114
+ print(" Minecraft server setup complete")
 
 
 
 
 
 
115
  return True
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  def start_server_background():
 
118
  global server_process
 
 
 
 
 
 
119
  java_path = check_java()
120
+ cmd = [java_path, "-Xmx2G", "-Xms1G", "-jar", str(JAR_PATH), "--nogui"]
121
+ server_process = subprocess.Popen(cmd, cwd=DATA_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
122
+ threading.Thread(target=lambda: [print(f"[SERVER] {line.strip()}") for line in server_process.stdout], daemon=True).start()
123
+ time.sleep(5)
124
+ print("✓ Server started in background")
125
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  def stop_server():
 
128
  global server_process
 
129
  if server_process:
130
+ print("Stopping server...")
131
  server_process.terminate()
132
+ server_process.wait(timeout=10)
 
 
 
133
  server_process = None
134
+ print("✓ Server stopped")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  def signal_handler(signum, frame):
137
+ stop_server()
 
 
138
  sys.exit(0)
139
 
140
  if __name__ == "__main__":
 
141
  signal.signal(signal.SIGINT, signal_handler)
142
  signal.signal(signal.SIGTERM, signal_handler)
143
+ if setup_minecraft_server():
144
+ start_server_background()
145
+ while True:
146
+ time.sleep(1)