Compare commits
1 Commits
8a3b1b1638
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd09688bb |
@@ -3,6 +3,7 @@ import asyncio
|
|||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
@@ -13,6 +14,7 @@ import traceback
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from prompt_toolkit import Application
|
from prompt_toolkit import Application
|
||||||
from prompt_toolkit.layout import Layout, HSplit
|
from prompt_toolkit.layout import Layout, HSplit
|
||||||
|
from prompt_toolkit.lexers import Lexer
|
||||||
from prompt_toolkit.widgets import TextArea
|
from prompt_toolkit.widgets import TextArea
|
||||||
from prompt_toolkit.key_binding import KeyBindings
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
from prompt_toolkit.styles import Style
|
from prompt_toolkit.styles import Style
|
||||||
@@ -131,11 +133,51 @@ class ServerJarClient(Application):
|
|||||||
|
|
||||||
# Text style
|
# Text style
|
||||||
self.style = Style.from_dict({
|
self.style = Style.from_dict({
|
||||||
"log": "bg:#000000 #ffffff",
|
|
||||||
"input": "bg:#222222 #ffffff",
|
"input": "bg:#222222 #ffffff",
|
||||||
"separator-area": "bg:#000000 #ffffff",
|
"separator-area": "bg:#000000 #ffffff",
|
||||||
"message-area": "bg:#111111 #ffffff"
|
"message-area": "bg:#111111 #ffffff",
|
||||||
|
"log": "bg:#000000 #ffffff",
|
||||||
|
"warning": "bg:#000000 ansiyellow",
|
||||||
|
"error": "bg:#000000 ansibrightred bold",
|
||||||
|
"system": "bg:#000000 ansicyan",
|
||||||
|
"process-log": "bg:#000000 ansigreen",
|
||||||
|
"process-error": "bg:#000000 ansired",
|
||||||
|
"unknown": "bg:#000000 ansiwhite bold",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
class LogLexer(Lexer):
|
||||||
|
tag_pattern = re.compile(r"^\[([A-Za-z0-9_. -]+)([:|][A-Za-z0-9_. -]+)?\]")
|
||||||
|
line_style = {
|
||||||
|
"auth_err": "class:error",
|
||||||
|
"auth_ok": "class:process-log",
|
||||||
|
"auth_required": "class:warning",
|
||||||
|
"client": "class:log",
|
||||||
|
"client|err": "class:error",
|
||||||
|
"client|warn": "class:warning",
|
||||||
|
"download_log_begin": "class:system",
|
||||||
|
"download_log_end": "class:system",
|
||||||
|
"err": "class:process-error",
|
||||||
|
"log": "class:process-log",
|
||||||
|
"ok": "class:process-log",
|
||||||
|
"sys": "class:system",
|
||||||
|
"sys:err": "class:error",
|
||||||
|
"unknown": "class:unknown",
|
||||||
|
}
|
||||||
|
|
||||||
|
def lex_document(self, document):
|
||||||
|
def get_line(lineno):
|
||||||
|
line = document.lines[lineno]
|
||||||
|
match = self.tag_pattern.match(line)
|
||||||
|
tag = "unknown"
|
||||||
|
if match:
|
||||||
|
tag = (match.group(1) + (match.group(2) or "")).lower()
|
||||||
|
|
||||||
|
style = self.line_style.get(tag, self.line_style["unknown"])
|
||||||
|
|
||||||
|
return [(style, line)]
|
||||||
|
|
||||||
|
return get_line
|
||||||
|
|
||||||
# Socket
|
# Socket
|
||||||
self.sock = None
|
self.sock = None
|
||||||
|
|
||||||
@@ -155,6 +197,7 @@ class ServerJarClient(Application):
|
|||||||
self.log_area = TextArea(
|
self.log_area = TextArea(
|
||||||
style="class:log",
|
style="class:log",
|
||||||
wrap_lines=True,
|
wrap_lines=True,
|
||||||
|
lexer=LogLexer(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.separator_area = TextArea(text="=" * 10 + " Enter Command Here " + "=" * 10, height=1,
|
self.separator_area = TextArea(text="=" * 10 + " Enter Command Here " + "=" * 10, height=1,
|
||||||
@@ -368,7 +411,7 @@ class ServerJarClient(Application):
|
|||||||
|
|
||||||
def _help(cmd):
|
def _help(cmd):
|
||||||
for key, value in cmd_map.items():
|
for key, value in cmd_map.items():
|
||||||
self._log(f"{key}: {value.get("description")}")
|
self._log(f"{key}: {value.get('description')}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _clear(cmd):
|
def _clear(cmd):
|
||||||
@@ -506,8 +549,15 @@ class ServerJarClient(Application):
|
|||||||
# def clear_screen():
|
# def clear_screen():
|
||||||
# os.system("cls" if os.name == "nt" else "clear")
|
# os.system("cls" if os.name == "nt" else "clear")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensure_log_tag(message):
|
||||||
|
message = f"{message}"
|
||||||
|
if re.match(r"^\[[A-Za-z0-9_. -]+(?:[:|][A-Za-z0-9_. -]+)?\]", message):
|
||||||
|
return message
|
||||||
|
return f"[unknown] {message}"
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
self.incoming.put(f"{message}")
|
self.incoming.put(self._ensure_log_tag(message))
|
||||||
|
|
||||||
def _log(self, message):
|
def _log(self, message):
|
||||||
# Nothing change
|
# Nothing change
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ ServerJar
|
|||||||
Wei - 2026
|
Wei - 2026
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import shlex
|
|
||||||
import signal
|
import signal
|
||||||
import socketserver
|
import socketserver
|
||||||
import logging
|
import logging
|
||||||
@@ -21,7 +20,7 @@ import hmac
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import click
|
import click
|
||||||
import yaml
|
import yaml
|
||||||
from utils.common import download_latest_paper_jar, get_latest_version_minecraft, get_specific_version_paper_builds, \
|
from utils.common import get_latest_version_minecraft, get_specific_version_paper_builds, \
|
||||||
download_server_jar, download_latest_build_paper_jar, get_latest_paper_version, download_vanilla_server_jar
|
download_server_jar, download_latest_build_paper_jar, get_latest_paper_version, download_vanilla_server_jar
|
||||||
from utils.file_settings import FileSettings
|
from utils.file_settings import FileSettings
|
||||||
from utils.file_settings import required_list, required_value
|
from utils.file_settings import required_list, required_value
|
||||||
@@ -33,7 +32,7 @@ from cryptography.hazmat.primitives import serialization
|
|||||||
|
|
||||||
ROOT_DIR = Path(os.getcwd())
|
ROOT_DIR = Path(os.getcwd())
|
||||||
SERVER_CONFIG_PATH = ROOT_DIR / "config" / "server.yml"
|
SERVER_CONFIG_PATH = ROOT_DIR / "config" / "server.yml"
|
||||||
VERSION = "1.0"
|
VERSION = "1.1"
|
||||||
LOG_DIR_NAME = "logs"
|
LOG_DIR_NAME = "logs"
|
||||||
SERVERJAR_LOG_FILE = "serverjar.log"
|
SERVERJAR_LOG_FILE = "serverjar.log"
|
||||||
LOG_DOWNLOAD_CHUNK_SIZE = 4096
|
LOG_DOWNLOAD_CHUNK_SIZE = 4096
|
||||||
@@ -42,7 +41,6 @@ def exit(message):
|
|||||||
click.echo(click.style(message, fg='green'))
|
click.echo(click.style(message, fg='green'))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
def main():
|
def main():
|
||||||
print(f"ServerJar v{VERSION}"
|
print(f"ServerJar v{VERSION}"
|
||||||
@@ -243,6 +241,12 @@ def create_server(name, mc_version, build, server_type, snapshot, latest, list_b
|
|||||||
|
|
||||||
print("Done")
|
print("Done")
|
||||||
|
|
||||||
|
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.clients = []
|
||||||
|
self.clients_lock = threading.Lock()
|
||||||
|
|
||||||
class SocketServer:
|
class SocketServer:
|
||||||
def __init__(self, host, port, enable_tls, certfile: Path, keyfile: Path, password: str = ""):
|
def __init__(self, host, port, enable_tls, certfile: Path, keyfile: Path, password: str = ""):
|
||||||
self.logger = logging.getLogger("SocketServer")
|
self.logger = logging.getLogger("SocketServer")
|
||||||
@@ -259,7 +263,7 @@ class SocketServer:
|
|||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
self._tcp_server: socketserver.ThreadingTCPServer | None = None
|
self.tcp_server: ThreadedTCPServer | None = None
|
||||||
self._tcp_thread: threading.Thread | None = None
|
self._tcp_thread: threading.Thread | None = None
|
||||||
|
|
||||||
self._log_subscribers: set[queue.Queue] = set()
|
self._log_subscribers: set[queue.Queue] = set()
|
||||||
@@ -293,14 +297,16 @@ class SocketServer:
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
def publish_log(self, server_name: str, line: str | None = None):
|
def publish_log(self, server_name: str, line: str | None = None):
|
||||||
if line is None:
|
if line is None:
|
||||||
|
log_server = None
|
||||||
message = server_name
|
message = server_name
|
||||||
else:
|
else:
|
||||||
|
log_server = server_name
|
||||||
message = f"[{server_name}] {line}"
|
message = f"[{server_name}] {line}"
|
||||||
|
|
||||||
with self._sub_lock:
|
with self._sub_lock:
|
||||||
for q in list(self._log_subscribers):
|
for q in list(self._log_subscribers):
|
||||||
try:
|
try:
|
||||||
q.put_nowait(message)
|
q.put_nowait((log_server, message))
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -316,10 +322,18 @@ class SocketServer:
|
|||||||
|
|
||||||
def _format_command_help(self, command_map):
|
def _format_command_help(self, command_map):
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
f"{command}: {description}"
|
f"[SYS] {command}: {description}"
|
||||||
for command, description in command_map.items()
|
for command, description in command_map.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def broadcast_all(socks, message):
|
||||||
|
for sock in list(socks):
|
||||||
|
try:
|
||||||
|
sock.sendall((message + "\n").encode("utf-8"))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_socket_help_message(self, current_server=None):
|
def get_socket_help_message(self, current_server=None):
|
||||||
socket_commands = {
|
socket_commands = {
|
||||||
"__help": "Display this help message",
|
"__help": "Display this help message",
|
||||||
@@ -335,6 +349,8 @@ class SocketServer:
|
|||||||
"__status": "Show target server process status",
|
"__status": "Show target server process status",
|
||||||
"__stop": "Stop the target server process",
|
"__stop": "Stop the target server process",
|
||||||
"__start": "Start the target server process",
|
"__start": "Start the target server process",
|
||||||
|
"__restart": "Restart the target server process",
|
||||||
|
"__info": "Show server information",
|
||||||
"<minecraft command>": "Send command to the target Minecraft server process",
|
"<minecraft command>": "Send command to the target Minecraft server process",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +371,7 @@ class SocketServer:
|
|||||||
|
|
||||||
server_names = sorted(self.command_receivers.keys())
|
server_names = sorted(self.command_receivers.keys())
|
||||||
return "[SYS] Available server shells:\n" + "\n".join(
|
return "[SYS] Available server shells:\n" + "\n".join(
|
||||||
f"- {server_name}" for server_name in server_names
|
f"[SYS] - {server_name}" for server_name in server_names
|
||||||
)
|
)
|
||||||
|
|
||||||
def handler_command(self, command: str):
|
def handler_command(self, command: str):
|
||||||
@@ -437,7 +453,7 @@ class SocketServer:
|
|||||||
def _build_tcp_server(self):
|
def _build_tcp_server(self):
|
||||||
manager = self
|
manager = self
|
||||||
|
|
||||||
class TCPServer(socketserver.ThreadingTCPServer):
|
class TCPServer(ThreadedTCPServer):
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
|
|
||||||
@@ -466,6 +482,20 @@ class SocketServer:
|
|||||||
mgr: Server = self.server.manager
|
mgr: Server = self.server.manager
|
||||||
|
|
||||||
mgr.logger.info(f"[SYS] Client from {self.client_address[0]}:{self.client_address[1]} connected,")
|
mgr.logger.info(f"[SYS] Client from {self.client_address[0]}:{self.client_address[1]} connected,")
|
||||||
|
with self.server.clients_lock:
|
||||||
|
self.server.clients.append(self.request)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
with self.server.clients_lock:
|
||||||
|
try:
|
||||||
|
self.server.clients.remove(self.request)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
self.current_server_record.pop(
|
||||||
|
f"{self.client_address[0]}:{self.client_address[1]}",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
super().finish()
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
mgr: Server = self.server.manager
|
mgr: Server = self.server.manager
|
||||||
@@ -477,9 +507,16 @@ class SocketServer:
|
|||||||
def push_logs():
|
def push_logs():
|
||||||
while not stop_evt.is_set():
|
while not stop_evt.is_set():
|
||||||
try:
|
try:
|
||||||
line = log_q.get(timeout=0.5)
|
log_server, line = log_q.get(timeout=0.5)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
current_server = self.current_server_record.get(
|
||||||
|
f"{self.client_address[0]}:{self.client_address[1]}",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
# Only push client connected server's log
|
||||||
|
if log_server is not None and log_server != current_server:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
self.request.sendall(f"[LOG] {line}\n".encode("utf-8"))
|
self.request.sendall(f"[LOG] {line}\n".encode("utf-8"))
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -563,6 +600,7 @@ class SocketServer:
|
|||||||
self.request.sendall(
|
self.request.sendall(
|
||||||
f"[SYS] Stopping all servers...bye\n".encode("utf-8")
|
f"[SYS] Stopping all servers...bye\n".encode("utf-8")
|
||||||
)
|
)
|
||||||
|
mgr.broadcast_all(mgr.tcp_server.clients, "[SYS] Server stopping... (Stop by remote client)")
|
||||||
mgr.stop_event.set()
|
mgr.stop_event.set()
|
||||||
return
|
return
|
||||||
elif cmd.startswith("__sync_log"):
|
elif cmd.startswith("__sync_log"):
|
||||||
@@ -646,26 +684,27 @@ class SocketServer:
|
|||||||
return TCPServer((self.host, self.port), Handler)
|
return TCPServer((self.host, self.port), Handler)
|
||||||
|
|
||||||
def start_socket_server(self):
|
def start_socket_server(self):
|
||||||
if self._tcp_server:
|
if self.tcp_server:
|
||||||
print("[SOCK] already running")
|
print("[SOCK] already running")
|
||||||
return
|
return
|
||||||
|
|
||||||
self._tcp_server = self._build_tcp_server()
|
self.tcp_server = self._build_tcp_server()
|
||||||
|
|
||||||
def loop():
|
def loop():
|
||||||
self.logger.info(f"[SOCK] listening on {self.host}:{self.port}")
|
self.logger.info(f"[SOCK] listening on {self.host}:{self.port}")
|
||||||
self._tcp_server.serve_forever(poll_interval=0.5)
|
self.tcp_server.serve_forever(poll_interval=0.5)
|
||||||
|
|
||||||
self._tcp_thread = threading.Thread(target=loop, daemon=True)
|
self._tcp_thread = threading.Thread(target=loop, daemon=True)
|
||||||
self._tcp_thread.start()
|
self._tcp_thread.start()
|
||||||
|
|
||||||
def stop_socket_server(self):
|
def stop_socket_server(self):
|
||||||
if not self._tcp_server:
|
if not self.tcp_server:
|
||||||
return
|
return
|
||||||
self.logger.info("[SOCK] shutting down")
|
self.logger.info("[SOCK] shutting down")
|
||||||
self._tcp_server.shutdown()
|
self.broadcast_all(self.tcp_server.clients, "[SYS] Server stopping...")
|
||||||
self._tcp_server.server_close()
|
self.tcp_server.shutdown()
|
||||||
self._tcp_server = None
|
self.tcp_server.server_close()
|
||||||
|
self.tcp_server = None
|
||||||
if self._tcp_thread and self._tcp_thread.is_alive():
|
if self._tcp_thread and self._tcp_thread.is_alive():
|
||||||
self._tcp_thread.join(timeout=2)
|
self._tcp_thread.join(timeout=2)
|
||||||
self._tcp_thread = None
|
self._tcp_thread = None
|
||||||
@@ -940,6 +979,7 @@ class Server:
|
|||||||
"__stop: Stop the target server process\n"
|
"__stop: Stop the target server process\n"
|
||||||
"__start: Start the target server process\n"
|
"__start: Start the target server process\n"
|
||||||
"__restart: Restart the target server process\n"
|
"__restart: Restart the target server process\n"
|
||||||
|
"__info: Show target server process status\n"
|
||||||
"<minecraft command>: Send command to the target Minecraft server process")
|
"<minecraft command>: Send command to the target Minecraft server process")
|
||||||
elif command == "__status":
|
elif command == "__status":
|
||||||
with self.proc_lock:
|
with self.proc_lock:
|
||||||
@@ -953,9 +993,15 @@ class Server:
|
|||||||
elif command == "__start":
|
elif command == "__start":
|
||||||
self.start()
|
self.start()
|
||||||
return True, f"Server \"{self.name}\" process started"
|
return True, f"Server \"{self.name}\" process started"
|
||||||
elif command == "__restart":
|
elif command == "__info":
|
||||||
self.restart()
|
return True, (f"serverName: {self.name}\n"
|
||||||
return True, f"Server \"{self.name}\" process restarted"
|
f"serverPID: {self.proc.pid}\n"
|
||||||
|
f"description: {self.description}\n"
|
||||||
|
f"arguments: {self.args}\n"
|
||||||
|
f"host: {self.host}\n"
|
||||||
|
f"port: {self.port}\n"
|
||||||
|
f"version: {self.version}\n"
|
||||||
|
f"status: {"Running" if self.is_process_alive() else "Died"}")
|
||||||
else:
|
else:
|
||||||
return False, f"Unknown command: {command}"
|
return False, f"Unknown command: {command}"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
prompt-toolkit==3.0.52
|
||||||
|
cryptography==48.0.0
|
||||||
Reference in New Issue
Block a user