srinetwork commited on
Commit
334a01d
·
verified ·
1 Parent(s): 35d1afa

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +500 -275
Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
  FROM openjdk:21-jdk-slim
2
 
3
- # Install required packages (ADDED: procps for pgrep, psmisc for fuser)
4
  RUN apt-get update && apt-get install -y \
5
  python3 \
6
  python3-pip \
@@ -10,12 +10,12 @@ RUN apt-get update && apt-get install -y \
10
  psmisc \
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
- # Install Python WebSocket and Flask
14
  RUN pip3 install flask flask-sock psutil --break-system-packages
15
 
16
  WORKDIR /app
17
 
18
- # Create startup script with web terminal - FIXED VERSION
19
  RUN cat > start.sh << 'EOF'
20
  #!/bin/bash
21
  set -e
@@ -28,7 +28,6 @@ echo "⏰ $(date)"
28
  echo "=========================================="
29
 
30
  # Kill any existing processes on port 7860
31
- echo "🔧 Cleaning up port 7860..."
32
  fuser -k 7860/tcp 2>/dev/null || true
33
  sleep 2
34
 
@@ -36,7 +35,7 @@ sleep 2
36
  if [ -n "$GITHUB_TOKEN" ]; then
37
  echo "📥 Cloning Minecraft server from GitHub..."
38
  rm -rf mcserver
39
- git clone https://$GITHUB_TOKEN@github.com/Walukapah/mcserver.git mcserver
40
  cd mcserver
41
  echo "✅ Repository cloned successfully"
42
  else
@@ -78,7 +77,7 @@ echo "✅ Auto-backup enabled (every 5 minutes)"
78
  echo ""
79
 
80
  # ==========================================
81
- # WEB TERMINAL SERVER
82
  # ==========================================
83
  cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
84
  import os
@@ -155,268 +154,499 @@ def start_minecraft():
155
 
156
  HTML_TEMPLATE = '''
157
  <!DOCTYPE html>
158
- <html>
159
  <head>
160
- <title>Minecraft Server - Web Terminal</title>
161
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
 
 
162
  <style>
163
- * { box-sizing: border-box; }
 
164
  body {
165
- margin: 0;
166
- padding: 0;
167
- background: #0d1117;
168
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
169
- color: #c9d1d9;
170
  }
171
- .header {
172
- background: #161b22;
173
- border-bottom: 1px solid #30363d;
174
- padding: 16px 24px;
 
 
175
  display: flex;
176
- justify-content: space-between;
177
  align-items: center;
 
178
  }
179
- .header h1 {
180
- margin: 0;
181
- font-size: 18px;
182
- font-weight: 600;
 
183
  display: flex;
184
  align-items: center;
185
- gap: 10px;
186
- color: #f0f6fc;
 
 
 
 
 
187
  }
188
- .status-area {
 
 
 
 
 
 
189
  display: flex;
190
  align-items: center;
191
- gap: 20px;
192
- font-size: 13px;
193
- }
194
- .stat-box {
195
- background: #21262d;
196
- padding: 6px 12px;
197
- border-radius: 6px;
198
- border: 1px solid #30363d;
199
- }
200
- .status-badge {
201
- background: #238636;
202
- color: white;
203
- padding: 6px 14px;
204
- border-radius: 20px;
205
- font-size: 12px;
206
- font-weight: 600;
207
- text-transform: uppercase;
208
- }
209
- .status-badge.offline {
210
- background: #da3633;
211
  }
212
- .main-container {
213
- display: flex;
214
- height: calc(100vh - 70px);
 
 
 
 
 
 
215
  }
216
- .sidebar {
217
- width: 240px;
218
- background: #161b22;
219
- border-right: 1px solid #30363d;
220
- padding: 20px;
 
 
 
 
221
  display: flex;
222
  flex-direction: column;
223
- gap: 12px;
224
- }
225
- .sidebar h3 {
226
- margin: 0 0 10px 0;
227
- font-size: 12px;
228
- text-transform: uppercase;
229
- color: #8b949e;
230
- letter-spacing: 0.5px;
231
  }
232
- .cmd-btn {
233
- background: #21262d;
234
- border: 1px solid #30363d;
235
- color: #c9d1d9;
236
- padding: 10px 14px;
237
- border-radius: 6px;
238
- cursor: pointer;
239
- font-size: 13px;
240
- text-align: left;
241
- transition: all 0.2s;
242
  display: flex;
 
243
  align-items: center;
244
- gap: 8px;
245
- }
246
- .cmd-btn:hover {
247
- background: #30363d;
248
- border-color: #8b949e;
249
- }
250
- .cmd-btn.danger {
251
- background: #da3633;
252
- border-color: #f85149;
253
- color: white;
254
- }
255
- .cmd-btn.danger:hover {
256
- background: #f85149;
257
  }
258
- .terminal-wrapper {
259
- flex: 1;
260
- padding: 20px;
 
 
261
  display: flex;
262
- flex-direction: column;
 
263
  }
264
- .terminal-header {
265
- background: #21262d;
266
- padding: 10px 16px;
267
- border-radius: 8px 8px 0 0;
268
- border: 1px solid #30363d;
269
- border-bottom: none;
270
- font-size: 12px;
271
- color: #8b949e;
 
 
 
 
272
  display: flex;
273
  align-items: center;
274
- gap: 8px;
 
275
  }
 
 
 
 
 
 
 
 
 
 
276
  .terminal-container {
277
  flex: 1;
278
  background: #0d1117;
279
- border-radius: 0 0 8px 8px;
280
- border: 1px solid #30363d;
281
  overflow: hidden;
282
  position: relative;
283
  }
284
- #terminal {
 
285
  width: 100%;
286
  height: 100%;
287
- padding: 10px;
 
 
 
 
 
 
 
288
  }
289
- .input-area {
 
 
 
 
 
 
 
 
 
 
 
290
  display: flex;
291
  gap: 10px;
292
- margin-top: 15px;
293
  }
294
- #cmdInput {
 
295
  flex: 1;
296
- background: #21262d;
 
 
297
  border: 1px solid #30363d;
298
- color: #f0f6fc;
299
- padding: 12px 16px;
300
  border-radius: 6px;
301
- font-size: 14px;
302
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
303
  }
304
- #cmdInput:focus {
 
 
 
 
 
 
 
 
 
 
 
305
  outline: none;
306
- border-color: #58a6ff;
307
  }
 
 
 
308
  .send-btn {
309
  background: #238636;
310
- border: none;
311
  color: white;
 
312
  padding: 12px 24px;
313
  border-radius: 6px;
314
  cursor: pointer;
315
- font-weight: 600;
316
- font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  }
318
- .send-btn:hover {
319
- background: #2ea043;
 
 
 
 
 
 
 
 
 
320
  }
321
- .connection-status {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  position: fixed;
323
- top: 80px;
324
- right: 20px;
325
- padding: 8px 16px;
326
- border-radius: 6px;
327
  font-size: 12px;
328
- font-weight: 600;
 
 
 
 
329
  transition: all 0.3s;
330
  }
331
- .connection-status.connected {
332
- background: #238636;
333
- color: white;
334
- }
335
- .connection-status.disconnected {
336
- background: #da3633;
337
- color: white;
338
- }
339
  </style>
340
  </head>
341
  <body>
342
- <div class="header">
343
- <h1>
344
- <span>⛏️</span>
345
- Minecraft Server Console
346
- </h1>
347
- <div class="status-area">
348
- <div class="stat-box">CPU: <span id="cpu">--</span>%</div>
349
- <div class="stat-box">RAM: <span id="memory">--</span>%</div>
350
- <span class="status-badge" id="status">Starting...</span>
351
  </div>
352
- </div>
353
-
354
- <div class="connection-status disconnected" id="connStatus">Disconnected</div>
 
 
355
 
 
356
  <div class="main-container">
357
- <div class="sidebar">
358
- <h3>⚡ Quick Commands</h3>
359
- <button class="cmd-btn" onclick="sendCmd('help')">📖 Help</button>
360
- <button class="cmd-btn" onclick="sendCmd('list')">👥 Player List</button>
361
- <button class="cmd-btn" onclick="sendCmd('save-all')">💾 Save World</button>
362
- <button class="cmd-btn" onclick="sendCmd('say Hello from Web Console!')">💬 Broadcast</button>
363
- <button class="cmd-btn" onclick="sendCmd('time set day')">☀️ Set Day</button>
364
- <button class="cmd-btn" onclick="sendCmd('weather clear')">🌤️ Clear Weather</button>
365
- <div style="flex: 1;"></div>
366
- <button class="cmd-btn danger" onclick="sendCmd('stop')">🛑 Stop Server</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  </div>
368
 
369
- <div class="terminal-wrapper">
370
- <div class="terminal-header">
371
- <span style="color: #f85149;">●</span>
372
- <span>server.jar</span>
373
- <span style="margin-left: auto; color: #8b949e;">Live Console</span>
 
 
 
 
374
  </div>
375
- <div class="terminal-container">
376
- <div id="terminal"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  </div>
378
- <div class="input-area">
379
- <input type="text" id="cmdInput" placeholder="Type command here (e.g., 'give @a diamond 64')..." autocomplete="off">
380
- <button class="send-btn" onclick="sendInput()">Send</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  </div>
382
  </div>
383
  </div>
384
 
385
- <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
386
- <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
387
  <script>
388
- const term = new Terminal({
389
- fontSize: 13,
390
- fontFamily: '"JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace',
391
- cursorBlink: true,
392
- cursorStyle: 'block',
393
- theme: {
394
- background: '#0d1117',
395
- foreground: '#c9d1d9',
396
- cursor: '#58a6ff',
397
- selection: '#264f78',
398
- black: '#484f58',
399
- red: '#ff7b72',
400
- green: '#7ee787',
401
- yellow: '#e3b341',
402
- blue: '#79c0ff',
403
- magenta: '#d2a8ff',
404
- cyan: '#56d4dd',
405
- white: '#f0f6fc'
406
- },
407
- scrollback: 50000,
408
- cols: 100,
409
- rows: 30
410
- });
411
-
412
- const fitAddon = new FitAddon.FitAddon();
413
- term.loadAddon(fitAddon);
414
- term.open(document.getElementById('terminal'));
415
- fitAddon.fit();
416
 
417
  let ws = null;
418
  let reconnectAttempts = 0;
419
- const maxReconnectAttempts = 10;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
  function connect() {
422
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -425,43 +655,35 @@ HTML_TEMPLATE = '''
425
  ws.onopen = () => {
426
  console.log('WebSocket connected');
427
  reconnectAttempts = 0;
428
- updateConnectionStatus(true);
429
- term.writeln('\\r\\n');
430
- term.writeln('\\x1b[32m╔══════════════════════════════════════════════════════════════╗\\x1b[0m');
431
- term.writeln('\\x1b[32m║\\x1b[0m \\x1b[1;36mMinecraft Server Web Console\\x1b[0m \\x1b[32m║\\x1b[0m');
432
- term.writeln('\\x1b[32m╚══════════════════════════════════════════════════════════════╝\\x1b[0m');
433
- term.writeln('');
434
- term.writeln('\\x1b[33mType commands below or use quick buttons on the left...\\x1b[0m');
435
- term.writeln('');
436
  };
437
 
438
  ws.onmessage = (event) => {
439
  const data = JSON.parse(event.data);
 
440
  if (data.type === 'output') {
441
- term.write(data.data);
442
  } else if (data.type === 'stats') {
443
- document.getElementById('cpu').textContent = data.cpu.toFixed(1);
444
- document.getElementById('memory').textContent = data.memory.toFixed(1);
445
- const statusEl = document.getElementById('status');
446
- if (data.running) {
447
- statusEl.textContent = 'Online';
448
- statusEl.classList.remove('offline');
449
- } else {
450
- statusEl.textContent = 'Offline';
451
- statusEl.classList.add('offline');
452
  }
453
  }
454
  };
455
 
456
  ws.onclose = () => {
457
- console.log('WebSocket disconnected');
458
- updateConnectionStatus(false);
 
459
  if (reconnectAttempts < maxReconnectAttempts) {
460
  reconnectAttempts++;
461
- term.writeln('\\r\\n\\x1b[33m[Connection lost. Reconnecting in 3s... (' + reconnectAttempts + '/' + maxReconnectAttempts + ')]\\x1b[0m');
462
  setTimeout(connect, 3000);
463
- } else {
464
- term.writeln('\\r\\n\\x1b[31m[Connection failed. Please refresh the page.]\\x1b[0m');
465
  }
466
  };
467
 
@@ -470,51 +692,43 @@ HTML_TEMPLATE = '''
470
  };
471
  }
472
 
473
- function updateConnectionStatus(connected) {
474
- const statusEl = document.getElementById('connStatus');
475
- if (connected) {
476
- statusEl.textContent = 'Connected';
477
- statusEl.classList.remove('disconnected');
478
- statusEl.classList.add('connected');
479
- } else {
480
- statusEl.textContent = 'Disconnected';
481
- statusEl.classList.remove('connected');
482
- statusEl.classList.add('disconnected');
483
- }
484
- }
485
-
486
  connect();
487
 
488
  function sendInput() {
489
- const input = document.getElementById('cmdInput');
490
- const cmd = input.value.trim();
491
  if (cmd && ws && ws.readyState === WebSocket.OPEN) {
492
  ws.send(JSON.stringify({type: 'command', data: cmd}));
493
- term.writeln('');
494
- term.writeln('\\x1b[36m> ' + cmd + '\\x1b[0m');
495
- input.value = '';
496
  }
497
  }
498
 
499
  function sendCmd(cmd) {
500
  if (ws && ws.readyState === WebSocket.OPEN) {
501
  ws.send(JSON.stringify({type: 'command', data: cmd}));
502
- term.writeln('');
503
- term.writeln('\\x1b[36m[Quick] ' + cmd + '\\x1b[0m');
504
  }
505
  }
506
 
507
- document.getElementById('cmdInput').addEventListener('keypress', (e) => {
508
- if (e.key === 'Enter') {
509
- sendInput();
510
- }
511
  });
512
 
513
- window.addEventListener('resize', () => {
514
- fitAddon.fit();
515
- });
 
 
 
 
 
 
 
 
 
516
 
517
- document.getElementById('cmdInput').focus();
 
518
  </script>
519
  </body>
520
  </html>
@@ -528,8 +742,7 @@ def index():
528
  def health():
529
  return {
530
  'status': 'running',
531
- 'minecraft': mc_process is not None and mc_process.poll() is None,
532
- 'timestamp': time.time()
533
  }
534
 
535
  @sock.route('/ws')
@@ -538,22 +751,48 @@ def websocket(ws):
538
 
539
  # Send recent history
540
  history = []
541
- while not output_queue.empty() and len(history) < 50:
 
542
  try:
543
  line = output_queue.get_nowait()
544
- history.append(line)
545
  except:
546
  break
547
 
548
- for line in history:
549
  output_queue.put(line)
 
550
 
551
- for line in history[-100:]:
552
  try:
553
  ws.send(json.dumps({'type': 'output', 'data': line}))
554
  except:
555
  break
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  def send_output():
558
  while True:
559
  try:
@@ -563,29 +802,17 @@ def websocket(ws):
563
  except:
564
  break
565
  except queue.Empty:
566
- try:
567
- cpu = psutil.cpu_percent(interval=0.1)
568
- memory = psutil.virtual_memory().percent
569
- running = mc_process is not None and mc_process.poll() is None
570
- ws.send(json.dumps({
571
- 'type': 'stats',
572
- 'cpu': cpu,
573
- 'memory': memory,
574
- 'running': running
575
- }))
576
- except Exception as e:
577
- print(f"[WS] Stats error: {e}", flush=True)
578
- break
579
- except Exception as e:
580
- print(f"[WS] Output error: {e}", flush=True)
581
  break
582
 
583
  output_thread = threading.Thread(target=send_output, daemon=True)
584
  output_thread.start()
585
 
 
586
  while True:
587
  try:
588
- data = ws.receive(timeout=1)
589
  if data:
590
  msg = json.loads(data)
591
  if msg.get('type') == 'command':
@@ -594,7 +821,7 @@ def websocket(ws):
594
  print(f"[WS] Command: {cmd}", flush=True)
595
  command_queue.put(cmd)
596
  except Exception as e:
597
- if "Connection closed" in str(e) or "timed out" in str(e).lower():
598
  break
599
  continue
600
 
@@ -618,14 +845,12 @@ signal.signal(signal.SIGTERM, cleanup)
618
  signal.signal(signal.SIGINT, cleanup)
619
 
620
  if __name__ == '__main__':
621
- print("[MAIN] Starting Minecraft...", flush=True)
 
 
622
  mc_thread = threading.Thread(target=start_minecraft, daemon=True)
623
  mc_thread.start()
624
 
625
- print("[MAIN] Waiting for Minecraft to start...", flush=True)
626
- server_ready.wait(timeout=60)
627
-
628
- print("[MAIN] Starting web server on port 7860...", flush=True)
629
  from werkzeug.serving import run_simple
630
  run_simple('0.0.0.0', 7860, app, threaded=True, use_reloader=False)
631
  PYTHON_SCRIPT
@@ -648,7 +873,7 @@ echo "✅ Minecraft Server with Web Terminal is LIVE!"
648
  echo "🌐 Access at: https://your-space.hf.space"
649
  echo "=========================================="
650
 
651
- # Keep container alive and monitor processes
652
  while true; do
653
  sleep 30
654
 
 
1
  FROM openjdk:21-jdk-slim
2
 
3
+ # Install required packages
4
  RUN apt-get update && apt-get install -y \
5
  python3 \
6
  python3-pip \
 
10
  psmisc \
11
  && rm -rf /var/lib/apt/lists/*
12
 
13
+ # Install Python dependencies
14
  RUN pip3 install flask flask-sock psutil --break-system-packages
15
 
16
  WORKDIR /app
17
 
18
+ # Create startup script
19
  RUN cat > start.sh << 'EOF'
20
  #!/bin/bash
21
  set -e
 
28
  echo "=========================================="
29
 
30
  # Kill any existing processes on port 7860
 
31
  fuser -k 7860/tcp 2>/dev/null || true
32
  sleep 2
33
 
 
35
  if [ -n "$GITHUB_TOKEN" ]; then
36
  echo "📥 Cloning Minecraft server from GitHub..."
37
  rm -rf mcserver
38
+ git clone https://$GITHUB_TOKEN@github.com/Walukapah/bungemcserver.git mcserver
39
  cd mcserver
40
  echo "✅ Repository cloned successfully"
41
  else
 
77
  echo ""
78
 
79
  # ==========================================
80
+ # WEB TERMINAL SERVER (Pterodactyl Style)
81
  # ==========================================
82
  cat > /app/web_terminal.py << 'PYTHON_SCRIPT'
83
  import os
 
154
 
155
  HTML_TEMPLATE = '''
156
  <!DOCTYPE html>
157
+ <html lang="en">
158
  <head>
159
+ <meta charset="UTF-8">
160
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
161
+ <title>Minecraft Server - Web Console</title>
162
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
163
  <style>
164
+ * { margin: 0; padding: 0; box-sizing: border-box; }
165
+
166
  body {
167
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
168
+ background: #1a1d21;
169
+ color: #e0e0e0;
170
+ height: 100vh;
171
+ overflow: hidden;
172
  }
173
+
174
+ /* Top Navigation */
175
+ .top-nav {
176
+ background: #252a30;
177
+ border-bottom: 1px solid #363b42;
178
+ padding: 0 20px;
179
  display: flex;
 
180
  align-items: center;
181
+ height: 50px;
182
  }
183
+
184
+ .nav-tabs { display: flex; gap: 0; height: 100%; }
185
+
186
+ .nav-tab {
187
+ padding: 0 20px;
188
  display: flex;
189
  align-items: center;
190
+ gap: 8px;
191
+ color: #9ca3af;
192
+ text-decoration: none;
193
+ font-size: 14px;
194
+ border-bottom: 2px solid transparent;
195
+ transition: all 0.2s;
196
+ cursor: pointer;
197
  }
198
+
199
+ .nav-tab:hover { color: #e0e0e0; background: rgba(255,255,255,0.05); }
200
+ .nav-tab.active { color: #fff; border-bottom-color: #3b82f6; background: rgba(59, 130, 246, 0.1); }
201
+ .nav-tab i { font-size: 14px; }
202
+
203
+ .nav-right {
204
+ margin-left: auto;
205
  display: flex;
206
  align-items: center;
207
+ gap: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }
209
+
210
+ .nav-btn {
211
+ background: transparent;
212
+ border: none;
213
+ color: #9ca3af;
214
+ padding: 8px;
215
+ cursor: pointer;
216
+ border-radius: 4px;
217
+ transition: all 0.2s;
218
  }
219
+
220
+ .nav-btn:hover { color: #fff; background: rgba(255,255,255,0.1); }
221
+
222
+ /* Main Layout */
223
+ .main-container { display: flex; height: calc(100vh - 50px); }
224
+
225
+ /* Console Area */
226
+ .console-section {
227
+ flex: 1;
228
  display: flex;
229
  flex-direction: column;
230
+ background: #0d1117;
 
 
 
 
 
 
 
231
  }
232
+
233
+ .console-header {
234
+ background: #161b22;
235
+ padding: 15px 20px;
236
+ border-bottom: 1px solid #30363d;
 
 
 
 
 
237
  display: flex;
238
+ justify-content: space-between;
239
  align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  }
241
+
242
+ .server-title {
243
+ font-size: 20px;
244
+ font-weight: 600;
245
+ color: #f0f6fc;
246
  display: flex;
247
+ align-items: center;
248
+ gap: 10px;
249
  }
250
+
251
+ .server-title i { color: #3b82f6; }
252
+
253
+ .control-buttons { display: flex; gap: 8px; }
254
+
255
+ .btn {
256
+ padding: 8px 16px;
257
+ border: none;
258
+ border-radius: 4px;
259
+ font-size: 13px;
260
+ font-weight: 500;
261
+ cursor: pointer;
262
  display: flex;
263
  align-items: center;
264
+ gap: 6px;
265
+ transition: all 0.2s;
266
  }
267
+
268
+ .btn-primary { background: #2563eb; color: white; }
269
+ .btn-primary:hover { background: #1d4ed8; }
270
+ .btn-secondary { background: #374151; color: #e0e0e0; }
271
+ .btn-secondary:hover { background: #4b5563; }
272
+ .btn-danger { background: #dc2626; color: white; }
273
+ .btn-danger:hover { background: #b91c1c; }
274
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
275
+
276
+ /* Terminal */
277
  .terminal-container {
278
  flex: 1;
279
  background: #0d1117;
280
+ padding: 0;
 
281
  overflow: hidden;
282
  position: relative;
283
  }
284
+
285
+ .terminal {
286
  width: 100%;
287
  height: 100%;
288
+ padding: 15px;
289
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
290
+ font-size: 13px;
291
+ line-height: 1.5;
292
+ color: #e6edf3;
293
+ overflow-y: auto;
294
+ white-space: pre-wrap;
295
+ word-wrap: break-word;
296
  }
297
+
298
+ .terminal-line { margin: 2px 0; padding: 1px 0; }
299
+ .terminal-line.info { color: #58a6ff; }
300
+ .terminal-line.warn { color: #d29922; }
301
+ .terminal-line.error { color: #f85149; }
302
+ .terminal-line.success { color: #3fb950; }
303
+
304
+ /* Command Input Area */
305
+ .command-area {
306
+ background: #161b22;
307
+ border-top: 1px solid #30363d;
308
+ padding: 12px 20px;
309
  display: flex;
310
  gap: 10px;
311
+ align-items: center;
312
  }
313
+
314
+ .command-input-wrapper {
315
  flex: 1;
316
+ display: flex;
317
+ align-items: center;
318
+ background: #0d1117;
319
  border: 1px solid #30363d;
 
 
320
  border-radius: 6px;
321
+ padding: 0 15px;
322
+ transition: border-color 0.2s;
323
  }
324
+
325
+ .command-input-wrapper:focus-within { border-color: #58a6ff; }
326
+ .command-input-wrapper i { color: #6e7681; margin-right: 10px; }
327
+
328
+ #commandInput {
329
+ flex: 1;
330
+ background: transparent;
331
+ border: none;
332
+ color: #e6edf3;
333
+ padding: 12px 0;
334
+ font-size: 14px;
335
+ font-family: 'SFMono-Regular', Consolas, monospace;
336
  outline: none;
 
337
  }
338
+
339
+ #commandInput::placeholder { color: #6e7681; }
340
+
341
  .send-btn {
342
  background: #238636;
 
343
  color: white;
344
+ border: none;
345
  padding: 12px 24px;
346
  border-radius: 6px;
347
  cursor: pointer;
348
+ font-weight: 500;
349
+ display: flex;
350
+ align-items: center;
351
+ gap: 6px;
352
+ transition: background 0.2s;
353
+ }
354
+
355
+ .send-btn:hover { background: #2ea043; }
356
+ .send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
357
+
358
+ /* Sidebar */
359
+ .sidebar {
360
+ width: 300px;
361
+ background: #1f2428;
362
+ border-left: 1px solid #363b42;
363
+ padding: 20px;
364
+ overflow-y: auto;
365
  }
366
+
367
+ .stat-card {
368
+ background: #252a30;
369
+ border: 1px solid #363b42;
370
+ border-radius: 8px;
371
+ padding: 16px;
372
+ margin-bottom: 12px;
373
+ display: flex;
374
+ align-items: center;
375
+ gap: 15px;
376
+ transition: transform 0.2s, box-shadow 0.2s;
377
  }
378
+
379
+ .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
380
+
381
+ .stat-icon {
382
+ width: 45px;
383
+ height: 45px;
384
+ border-radius: 8px;
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ font-size: 20px;
389
+ }
390
+
391
+ .stat-icon.blue { background: rgba(59, 130, 246, 0.2); color: #3b82f6; }
392
+ .stat-icon.green { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
393
+ .stat-icon.red { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
394
+ .stat-icon.yellow { background: rgba(234, 179, 8, 0.2); color: #eab308; }
395
+ .stat-icon.purple { background: rgba(168, 85, 247, 0.2); color: #a855f7; }
396
+ .stat-icon.cyan { background: rgba(6, 182, 212, 0.2); color: #06b6d4; }
397
+
398
+ .stat-info { flex: 1; }
399
+ .stat-label { font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
400
+ .stat-value { font-size: 18px; font-weight: 600; color: #f0f6fc; }
401
+ .stat-sub { font-size: 11px; color: #6b7280; margin-top: 2px; }
402
+
403
+ /* Status Badge */
404
+ .status-indicator {
405
+ display: inline-flex;
406
+ align-items: center;
407
+ gap: 6px;
408
+ padding: 4px 10px;
409
+ border-radius: 20px;
410
+ font-size: 11px;
411
+ font-weight: 600;
412
+ text-transform: uppercase;
413
+ }
414
+
415
+ .status-online { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
416
+ .status-offline { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
417
+ .status-starting { background: rgba(234, 179, 8, 0.2); color: #eab308; }
418
+
419
+ .pulse { width: 8px; height: 8px; border-radius: 50%; background: currentColor; animation: pulse 2s infinite; }
420
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
421
+
422
+ /* Scrollbar */
423
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
424
+ ::-webkit-scrollbar-track { background: #1a1d21; }
425
+ ::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
426
+ ::-webkit-scrollbar-thumb:hover { background: #6b7280; }
427
+
428
+ /* Connection Status */
429
+ .connection-bar {
430
  position: fixed;
431
+ top: 50px;
432
+ left: 0;
433
+ right: 300px;
434
+ padding: 8px 20px;
435
  font-size: 12px;
436
+ font-weight: 500;
437
+ display: flex;
438
+ align-items: center;
439
+ gap: 8px;
440
+ z-index: 100;
441
  transition: all 0.3s;
442
  }
443
+
444
+ .connection-bar.connected { background: rgba(34, 197, 94, 0.15); color: #22c55e; border-bottom: 1px solid rgba(34, 197, 94, 0.3); }
445
+ .connection-bar.disconnected { background: rgba(239, 68, 68, 0.15); color: #ef4444; border-bottom: 1px solid rgba(239, 68, 68, 0.3); }
446
+ .connection-bar.connecting { background: rgba(234, 179, 8, 0.15); color: #eab308; border-bottom: 1px solid rgba(234, 179, 8, 0.3); }
 
 
 
 
447
  </style>
448
  </head>
449
  <body>
450
+ <!-- Top Navigation -->
451
+ <nav class="top-nav">
452
+ <div class="nav-tabs">
453
+ <a class="nav-tab active" onclick="switchTab('console', this)">
454
+ <i class="fas fa-terminal"></i> Console
455
+ </a>
456
+ <a class="nav-tab" onclick="switchTab('files', this)">
457
+ <i class="fas fa-folder"></i> Files
458
+ </a>
459
  </div>
460
+ <div class="nav-right">
461
+ <button class="nav-btn" title="Settings"><i class="fas fa-cog"></i></button>
462
+ <button class="nav-btn" title="Full Screen" onclick="toggleFullscreen()"><i class="fas fa-expand"></i></button>
463
+ </div>
464
+ </nav>
465
 
466
+ <!-- Main Container -->
467
  <div class="main-container">
468
+ <!-- Console Section -->
469
+ <div class="console-section" id="consoleSection">
470
+ <div class="connection-bar connecting" id="connectionBar">
471
+ <i class="fas fa-circle-notch fa-spin"></i>
472
+ <span>Connecting to server...</span>
473
+ </div>
474
+
475
+ <div class="console-header">
476
+ <div class="server-title">
477
+ <i class="fas fa-server"></i>
478
+ <span>Minecraft Server</span>
479
+ <span class="status-indicator status-offline" id="serverStatus">
480
+ <span class="pulse"></span> Offline
481
+ </span>
482
+ </div>
483
+ <div class="control-buttons">
484
+ <button class="btn btn-primary" id="startBtn" onclick="sendCmd('start')" disabled>
485
+ <i class="fas fa-play"></i> Start
486
+ </button>
487
+ <button class="btn btn-secondary" id="restartBtn" onclick="sendCmd('restart')" disabled>
488
+ <i class="fas fa-redo"></i> Restart
489
+ </button>
490
+ <button class="btn btn-danger" id="stopBtn" onclick="sendCmd('stop')" disabled>
491
+ <i class="fas fa-stop"></i> Stop
492
+ </button>
493
+ </div>
494
+ </div>
495
+
496
+ <div class="terminal-container">
497
+ <div class="terminal" id="terminal"></div>
498
+ </div>
499
+
500
+ <div class="command-area">
501
+ <div class="command-input-wrapper">
502
+ <i class="fas fa-angle-right"></i>
503
+ <input type="text" id="commandInput" placeholder="Type a command..." autocomplete="off" disabled>
504
+ </div>
505
+ <button class="send-btn" id="sendBtn" onclick="sendInput()" disabled>
506
+ <i class="fas fa-paper-plane"></i> Send
507
+ </button>
508
+ </div>
509
  </div>
510
 
511
+ <!-- Sidebar -->
512
+ <div class="sidebar">
513
+ <div class="stat-card">
514
+ <div class="stat-icon blue"><i class="fas fa-network-wired"></i></div>
515
+ <div class="stat-info">
516
+ <div class="stat-label">Address</div>
517
+ <div class="stat-value" id="serverAddress">--</div>
518
+ <div class="stat-sub">Port 25565</div>
519
+ </div>
520
  </div>
521
+
522
+ <div class="stat-card">
523
+ <div class="stat-icon red"><i class="fas fa-clock"></i></div>
524
+ <div class="stat-info">
525
+ <div class="stat-label">Uptime</div>
526
+ <div class="stat-value" id="uptime">Offline</div>
527
+ <div class="stat-sub" id="uptimeSub">Not running</div>
528
+ </div>
529
+ </div>
530
+
531
+ <div class="stat-card">
532
+ <div class="stat-icon yellow"><i class="fas fa-microchip"></i></div>
533
+ <div class="stat-info">
534
+ <div class="stat-label">CPU Load</div>
535
+ <div class="stat-value" id="cpuLoad">--%</div>
536
+ <div class="stat-sub" id="cpuSub">System average</div>
537
+ </div>
538
+ </div>
539
+
540
+ <div class="stat-card">
541
+ <div class="stat-icon purple"><i class="fas fa-memory"></i></div>
542
+ <div class="stat-info">
543
+ <div class="stat-label">Memory</div>
544
+ <div class="stat-value" id="memoryUsage">--%</div>
545
+ <div class="stat-sub" id="memorySub">-- / -- GB</div>
546
+ </div>
547
  </div>
548
+
549
+ <div class="stat-card">
550
+ <div class="stat-icon cyan"><i class="fas fa-hdd"></i></div>
551
+ <div class="stat-info">
552
+ <div class="stat-label">Disk</div>
553
+ <div class="stat-value" id="diskUsage">-- MB</div>
554
+ <div class="stat-sub">Server files</div>
555
+ </div>
556
+ </div>
557
+
558
+ <div class="stat-card">
559
+ <div class="stat-icon green"><i class="fas fa-users"></i></div>
560
+ <div class="stat-info">
561
+ <div class="stat-label">Players</div>
562
+ <div class="stat-value" id="playerCount">--</div>
563
+ <div class="stat-sub">Online players</div>
564
+ </div>
565
  </div>
566
  </div>
567
  </div>
568
 
 
 
569
  <script>
570
+ const terminal = document.getElementById('terminal');
571
+ const commandInput = document.getElementById('commandInput');
572
+ const sendBtn = document.getElementById('sendBtn');
573
+ const startBtn = document.getElementById('startBtn');
574
+ const restartBtn = document.getElementById('restartBtn');
575
+ const stopBtn = document.getElementById('stopBtn');
576
+ const connectionBar = document.getElementById('connectionBar');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
  let ws = null;
579
  let reconnectAttempts = 0;
580
+ let maxReconnectAttempts = 10;
581
+ let serverStartTime = null;
582
+ let isServerRunning = false;
583
+
584
+ function updateConnectionStatus(status, message) {
585
+ connectionBar.className = 'connection-bar ' + status;
586
+ const icons = {
587
+ 'connected': 'fa-check-circle',
588
+ 'disconnected': 'fa-exclamation-triangle',
589
+ 'connecting': 'fa-circle-notch fa-spin'
590
+ };
591
+ connectionBar.innerHTML = '<i class="fas ' + icons[status] + '"></i><span>' + message + '</span>';
592
+ }
593
+
594
+ function updateServerStatus(running) {
595
+ isServerRunning = running;
596
+ const statusEl = document.getElementById('serverStatus');
597
+
598
+ if (running) {
599
+ statusEl.className = 'status-indicator status-online';
600
+ statusEl.innerHTML = '<span class="pulse"></span>Online';
601
+ startBtn.disabled = true;
602
+ restartBtn.disabled = false;
603
+ stopBtn.disabled = false;
604
+ commandInput.disabled = false;
605
+ sendBtn.disabled = false;
606
+ if (!serverStartTime) serverStartTime = Date.now();
607
+ } else {
608
+ statusEl.className = 'status-indicator status-offline';
609
+ statusEl.innerHTML = '<span class="pulse"></span>Offline';
610
+ startBtn.disabled = false;
611
+ restartBtn.disabled = true;
612
+ stopBtn.disabled = true;
613
+ commandInput.disabled = true;
614
+ sendBtn.disabled = true;
615
+ serverStartTime = null;
616
+ }
617
+ }
618
+
619
+ function addTerminalLine(text, type = '') {
620
+ const line = document.createElement('div');
621
+ line.className = 'terminal-line ' + type;
622
+ line.textContent = text;
623
+ terminal.appendChild(line);
624
+ terminal.scrollTop = terminal.scrollHeight;
625
+ }
626
+
627
+ function formatUptime(ms) {
628
+ const seconds = Math.floor(ms / 1000);
629
+ const minutes = Math.floor(seconds / 60);
630
+ const hours = Math.floor(minutes / 60);
631
+ const days = Math.floor(hours / 24);
632
+ if (days > 0) return days + 'd ' + (hours % 24) + 'h';
633
+ if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm';
634
+ if (minutes > 0) return minutes + 'm ' + (seconds % 60) + 's';
635
+ return seconds + 's';
636
+ }
637
+
638
+ function updateUptime() {
639
+ if (serverStartTime && isServerRunning) {
640
+ const uptime = Date.now() - serverStartTime;
641
+ document.getElementById('uptime').textContent = formatUptime(uptime);
642
+ document.getElementById('uptimeSub').textContent = 'Running';
643
+ } else {
644
+ document.getElementById('uptime').textContent = 'Offline';
645
+ document.getElementById('uptimeSub').textContent = 'Not running';
646
+ }
647
+ }
648
+
649
+ setInterval(updateUptime, 1000);
650
 
651
  function connect() {
652
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
 
655
  ws.onopen = () => {
656
  console.log('WebSocket connected');
657
  reconnectAttempts = 0;
658
+ updateConnectionStatus('connected', 'Connected to server');
659
+ setTimeout(() => { connectionBar.style.opacity = '0'; }, 2000);
 
 
 
 
 
 
660
  };
661
 
662
  ws.onmessage = (event) => {
663
  const data = JSON.parse(event.data);
664
+
665
  if (data.type === 'output') {
666
+ addTerminalLine(data.data.trim());
667
  } else if (data.type === 'stats') {
668
+ document.getElementById('cpuLoad').textContent = data.cpu.toFixed(1) + '%';
669
+ document.getElementById('memoryUsage').textContent = data.memory.toFixed(1) + '%';
670
+ const memTotal = (data.memoryTotal / 1024 / 1024 / 1024).toFixed(2);
671
+ const memUsed = ((data.memoryTotal * data.memory / 100) / 1024 / 1024 / 1024).toFixed(2);
672
+ document.getElementById('memorySub').textContent = memUsed + ' / ' + memTotal + ' GB';
673
+ updateServerStatus(data.running);
674
+ if (data.players !== undefined) {
675
+ document.getElementById('playerCount').textContent = data.players;
 
676
  }
677
  }
678
  };
679
 
680
  ws.onclose = () => {
681
+ updateConnectionStatus('disconnected', 'Connection lost. Reconnecting...');
682
+ connectionBar.style.opacity = '1';
683
+ updateServerStatus(false);
684
  if (reconnectAttempts < maxReconnectAttempts) {
685
  reconnectAttempts++;
 
686
  setTimeout(connect, 3000);
 
 
687
  }
688
  };
689
 
 
692
  };
693
  }
694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
695
  connect();
696
 
697
  function sendInput() {
698
+ const cmd = commandInput.value.trim();
 
699
  if (cmd && ws && ws.readyState === WebSocket.OPEN) {
700
  ws.send(JSON.stringify({type: 'command', data: cmd}));
701
+ addTerminalLine('> ' + cmd, 'info');
702
+ commandInput.value = '';
 
703
  }
704
  }
705
 
706
  function sendCmd(cmd) {
707
  if (ws && ws.readyState === WebSocket.OPEN) {
708
  ws.send(JSON.stringify({type: 'command', data: cmd}));
709
+ addTerminalLine('[' + cmd + ']', 'info');
 
710
  }
711
  }
712
 
713
+ commandInput.addEventListener('keypress', (e) => {
714
+ if (e.key === 'Enter') sendInput();
 
 
715
  });
716
 
717
+ function switchTab(tab, element) {
718
+ document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
719
+ element.classList.add('active');
720
+ }
721
+
722
+ function toggleFullscreen() {
723
+ if (!document.fullscreenElement) {
724
+ document.documentElement.requestFullscreen();
725
+ } else {
726
+ document.exitFullscreen();
727
+ }
728
+ }
729
 
730
+ document.getElementById('serverAddress').textContent = window.location.hostname;
731
+ addTerminalLine('Connecting to Minecraft server...', 'info');
732
  </script>
733
  </body>
734
  </html>
 
742
  def health():
743
  return {
744
  'status': 'running',
745
+ 'minecraft': mc_process is not None and mc_process.poll() is None
 
746
  }
747
 
748
  @sock.route('/ws')
 
751
 
752
  # Send recent history
753
  history = []
754
+ temp_list = []
755
+ while not output_queue.empty() and len(temp_list) < 100:
756
  try:
757
  line = output_queue.get_nowait()
758
+ temp_list.append(line)
759
  except:
760
  break
761
 
762
+ for line in temp_list:
763
  output_queue.put(line)
764
+ history.append(line)
765
 
766
+ for line in history[-50:]:
767
  try:
768
  ws.send(json.dumps({'type': 'output', 'data': line}))
769
  except:
770
  break
771
 
772
+ def send_stats():
773
+ while True:
774
+ try:
775
+ cpu = psutil.cpu_percent(interval=0.5)
776
+ memory = psutil.virtual_memory()
777
+ running = mc_process is not None and mc_process.poll() is None
778
+
779
+ ws.send(json.dumps({
780
+ 'type': 'stats',
781
+ 'cpu': cpu,
782
+ 'memory': memory.percent,
783
+ 'memoryTotal': memory.total,
784
+ 'running': running,
785
+ 'players': 0
786
+ }))
787
+ except Exception as e:
788
+ print(f"[WS] Stats error: {e}", flush=True)
789
+ break
790
+ time.sleep(2)
791
+
792
+ stats_thread = threading.Thread(target=send_stats, daemon=True)
793
+ stats_thread.start()
794
+
795
+ # Send output
796
  def send_output():
797
  while True:
798
  try:
 
802
  except:
803
  break
804
  except queue.Empty:
805
+ continue
806
+ except:
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  break
808
 
809
  output_thread = threading.Thread(target=send_output, daemon=True)
810
  output_thread.start()
811
 
812
+ # Receive commands
813
  while True:
814
  try:
815
+ data = ws.receive(timeout=5)
816
  if data:
817
  msg = json.loads(data)
818
  if msg.get('type') == 'command':
 
821
  print(f"[WS] Command: {cmd}", flush=True)
822
  command_queue.put(cmd)
823
  except Exception as e:
824
+ if "Connection closed" in str(e):
825
  break
826
  continue
827
 
 
845
  signal.signal(signal.SIGINT, cleanup)
846
 
847
  if __name__ == '__main__':
848
+ print("[MAIN] Starting web server...", flush=True)
849
+
850
+ # Start Minecraft immediately
851
  mc_thread = threading.Thread(target=start_minecraft, daemon=True)
852
  mc_thread.start()
853
 
 
 
 
 
854
  from werkzeug.serving import run_simple
855
  run_simple('0.0.0.0', 7860, app, threaded=True, use_reloader=False)
856
  PYTHON_SCRIPT
 
873
  echo "🌐 Access at: https://your-space.hf.space"
874
  echo "=========================================="
875
 
876
+ # Keep container alive
877
  while true; do
878
  sleep 30
879