import os
import sys
import subprocess
import json
import threading
import time
import queue
import signal
from flask import Flask, render_template_string, request, jsonify
from flask_sock import Sock
import psutil
app = Flask(__name__)
sock = Sock(app)
mc_process = None
output_queue = queue.Queue()
command_queue = queue.Queue()
server_ready = threading.Event()
def start_minecraft():
global mc_process
mc_dir = "/app/mcserver"
print("[MC] Starting Minecraft server...", flush=True)
mc_process = subprocess.Popen(
["java", "-Xms1G", "-Xmx14G", "-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled", "-XX:MaxGCPauseMillis=200",
"-jar", "server.jar", "nogui"],
cwd=mc_dir,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
preexec_fn=os.setsid
)
print(f"[MC] Minecraft PID: {mc_process.pid}", flush=True)
def read_output():
try:
for line in mc_process.stdout:
output_queue.put(line)
if "Done" in line and "For help, type" in line:
server_ready.set()
print("[MC] Server is ready!", flush=True)
except Exception as e:
print(f"[MC] Output error: {e}", flush=True)
threading.Thread(target=read_output, daemon=True).start()
def process_commands():
while True:
try:
cmd = command_queue.get(timeout=1)
if mc_process and mc_process.poll() is None:
try:
mc_process.stdin.write(cmd + "\n")
mc_process.stdin.flush()
print(f"[MC] Command: {cmd}", flush=True)
except Exception as e:
print(f"[MC] Command error: {e}", flush=True)
except queue.Empty:
continue
except Exception as e:
print(f"[MC] Processor error: {e}", flush=True)
break
threading.Thread(target=process_commands, daemon=True).start()
HTML_TEMPLATE = """
Minecraft Server - Web Console
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/health')
def health():
return {'status': 'running', 'minecraft': mc_process is not None and mc_process.poll() is None}
@app.route('/api/files')
def get_files():
path = request.args.get('path', '/app/mcserver')
if not path.startswith('/app/mcserver'):
path = '/app/mcserver'
try:
files = []
for item in sorted(os.listdir(path)):
item_path = os.path.join(path, item)
is_dir = os.path.isdir(item_path)
size = os.path.getsize(item_path) if not is_dir else 0
files.append({'name': item, 'is_dir': is_dir, 'size': size, 'path': item_path})
return jsonify(files)
except Exception as e:
return jsonify({'error': str(e)}), 500
@sock.route('/ws')
def websocket(ws):
print("[WS] Client connected", flush=True)
history = []
temp_list = []
while not output_queue.empty() and len(temp_list) < 100:
try:
line = output_queue.get_nowait()
temp_list.append(line)
except:
break
for line in temp_list:
output_queue.put(line)
history.append(line)
for line in history[-50:]:
try:
ws.send(json.dumps({'type': 'output', 'data': line}))
except:
break
def send_stats():
while True:
try:
cpu = psutil.cpu_percent(interval=0.5)
memory = psutil.virtual_memory()
running = mc_process is not None and mc_process.poll() is None
disk_size = 0
try:
for dirpath, dirnames, filenames in os.walk('/app/mcserver'):
for f in filenames:
fp = os.path.join(dirpath, f)
if os.path.exists(fp):
disk_size += os.path.getsize(fp)
except:
pass
ws.send(json.dumps({
'type': 'stats',
'cpu': cpu,
'memory': memory.percent,
'memoryTotal': memory.total,
'running': running,
'players': 0,
'disk': disk_size
}))
except Exception as e:
print(f"[WS] Stats error: {e}", flush=True)
break
time.sleep(2)
threading.Thread(target=send_stats, daemon=True).start()
def send_output():
while True:
try:
line = output_queue.get(timeout=1)
try:
ws.send(json.dumps({'type': 'output', 'data': line}))
except:
break
except queue.Empty:
continue
except:
break
threading.Thread(target=send_output, daemon=True).start()
while True:
try:
data = ws.receive(timeout=5)
if data:
msg = json.loads(data)
if msg.get('type') == 'command':
cmd = msg.get('data', '').strip()
if cmd:
print(f"[WS] Command: {cmd}", flush=True)
command_queue.put(cmd)
except Exception as e:
if "Connection closed" in str(e):
break
continue
print("[WS] Client disconnected", flush=True)
def cleanup(signum=None, frame=None):
print("\n[MAIN] Shutting down...", flush=True)
if mc_process:
try:
mc_process.stdin.write("stop\n")
mc_process.stdin.flush()
mc_process.wait(timeout=30)
except:
try:
os.killpg(os.getpgid(mc_process.pid), signal.SIGTERM)
except:
pass
sys.exit(0)
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
if __name__ == '__main__':
print("[MAIN] Starting web server...", flush=True)
mc_thread = threading.Thread(target=start_minecraft, daemon=True)
mc_thread.start()
from werkzeug.serving import run_simple
run_simple('0.0.0.0', 7860, app, threaded=True, use_reloader=False)