vericudebuget's picture
Create server.py
1645c43 verified
raw
history blame
4.58 kB
import asyncio
import websockets
import os
from http import HTTPStatus
# --- Configuration ---
HTTP_PORT = 7860
WEBSOCKET_PORT = 8765
MINECRAFT_RAM = "12G"
PLAYIT_SECRET = os.environ.get('PLAYIT_SECRET')
# A set to keep track of all connected WebSocket clients
connected_clients = set()
async def start_subprocess(command):
"""Starts a subprocess and returns the process object."""
print(f"Starting subprocess: {' '.join(command)}")
process = await asyncio.create_subprocess_exec(
*command,
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
return process
async def broadcast(message):
"""Sends a message to all connected clients."""
if connected_clients:
await asyncio.wait([client.send(message) for client in connected_clients])
async def read_stream(stream, prefix=""):
"""Reads lines from a stream and broadcasts them."""
while True:
line = await stream.readline()
if not line:
break
output = line.decode('utf-8', errors='replace').strip()
print(f"{prefix}{output}")
await broadcast(f"{prefix}{output}")
async def websocket_handler(websocket, path):
"""Handles WebSocket connections."""
global mc_process
print("Client connected to WebSocket.")
connected_clients.add(websocket)
try:
# Keep the connection open and listen for commands from the client
async for message in websocket:
print(f"Received command: {message}")
if mc_process and not mc_process.stdin.is_closing():
# Add a newline character, as Minecraft expects
command = message.strip() + '\n'
mc_process.stdin.write(command.encode('utf-8'))
await mc_process.stdin.drain()
else:
print("Minecraft process is not running or stdin is closed.")
await websocket.send("Error: Minecraft server is not running.")
except websockets.exceptions.ConnectionClosed:
print("Client disconnected.")
finally:
connected_clients.remove(websocket)
async def http_handler(reader, writer):
"""Handles basic HTTP requests to serve the HTML file."""
try:
request_line = await reader.readline()
if not request_line:
return
# Simple request parsing
method, path, _ = request_line.decode().split(' ')
if method == "GET" and path == "/":
writer.write(b"HTTP/1.1 200 OK\r\n")
writer.write(b"Content-Type: text/html\r\n")
writer.write(b"\r\n")
with open("index.html", "rb") as f:
writer.write(f.read())
else:
writer.write(b"HTTP/1.1 404 Not Found\r\n\r\n")
writer.write(b"Not Found")
await writer.drain()
except Exception as e:
print(f"HTTP Server Error: {e}")
finally:
writer.close()
async def main():
global mc_process
# 1. Start playit.gg
if PLAYIT_SECRET:
playit_command = ['playit', '--secret', PLAYIT_SECRET]
playit_process = await start_subprocess(playit_command)
# Create tasks to forward playit's output to the console logs
asyncio.create_task(read_stream(playit_process.stdout, "[playit] "))
asyncio.create_task(read_stream(playit_process.stderr, "[playit/err] "))
else:
print("Warning: PLAYIT_SECRET not set. Tunnel will not be started.")
# 2. Start Minecraft server
mc_command = [
'java', f'-Xmx{MINECRAFT_RAM}', '-Xms1G',
'-jar', 'server.jar', 'nogui'
]
mc_process = await start_subprocess(mc_command)
# Create tasks to forward Minecraft's output to websockets
asyncio.create_task(read_stream(mc_process.stdout, ""))
asyncio.create_task(read_stream(mc_process.stderr, "[mc/err] "))
# 3. Start WebSocket and HTTP servers
websocket_server = await websockets.serve(websocket_handler, "0.0.0.0", WEBSOCKET_PORT)
print(f"WebSocket server started on port {WEBSOCKET_PORT}")
http_server = await asyncio.start_server(http_handler, "0.0.0.0", HTTP_PORT)
print(f"HTTP server started on port {HTTP_PORT}")
# Wait for servers to close (which they won't, unless an error occurs)
await asyncio.gather(
websocket_server.wait_closed(),
http_server.wait_closed()
)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("Server shutting down.")