Compare commits

..

11 Commits

Author SHA1 Message Date
wei 5acc033cad Update version. 2026-06-07 13:24:00 +08:00
wei 7f5c4e11b7 Fix restart bug. 2026-06-07 13:23:33 +08:00
wei 09256ee9f3 Fix timeout error. 2026-06-07 00:12:48 +08:00
wei fed9e3de71 Update paper api new latest version. 2026-06-06 22:29:48 +08:00
wei 196322b49f Update missing packages 2026-06-06 21:59:45 +08:00
wei 0f4af9e1f0 Add startup order support. 2026-06-06 21:42:31 +08:00
wei d422f5e0df Update missing packages. 2026-06-06 17:42:13 +08:00
wei dd079bdbc7 Update missing packages. 2026-06-06 17:40:27 +08:00
wei b7ff3cba4c Update scripts. 2026-06-06 17:24:47 +08:00
wei 7d4846225f Add execute permission to scripts. 2026-06-06 17:23:26 +08:00
wei 91ce4c32ff Update requirements.txt and add init script. 2026-06-06 17:21:35 +08:00
7 changed files with 101 additions and 21 deletions
Regular → Executable
+5
View File
@@ -1,8 +1,13 @@
if [ ! -d "$DIR" ]; then
uv venv
fi
echo "Checking environment..." echo "Checking environment..."
if command -v nuitka &> /dev/null; then if command -v nuitka &> /dev/null; then
echo "Nuitka installed" echo "Nuitka installed"
else else
echo "Nuitka not installed. Install it via \"uv pip install nuitka\" !" >&2 echo "Nuitka not installed. Install it via \"uv pip install nuitka\" !" >&2
exit 1
fi fi
nuitka --onefile --standalone --output-filename=serverjar main.py --output-filename=serverjar nuitka --onefile --standalone --output-filename=serverjar main.py --output-filename=serverjar
+4
View File
@@ -762,6 +762,10 @@ class ServerJarClient(Application):
context = self.get_tls_context() context = self.get_tls_context()
s = context.wrap_socket(raw, server_hostname=self.host) s = context.wrap_socket(raw, server_hostname=self.host)
# The connection timeout should not become an idle read timeout.
# Keep recv() blocking so quiet but healthy connections stay open.
s.settimeout(None)
with self.sock_lock: with self.sock_lock:
self.sock = s self.sock = s
Executable
+13
View File
@@ -0,0 +1,13 @@
echo "Checking environment..."
if command -v uv &> /dev/null; then
echo "uv installed"
else
echo "uv not installed. Install it via \"dnf install uv\" or \"apt install uv\" !" >&2
exit 1
fi
if [ ! -d "$DIR" ]; then
uv venv
fi
uv pip install -r requirements.txt
Regular → Executable
+1 -1
View File
@@ -7,6 +7,6 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
sudo install -Dm755 "$SCRIPT_DIR/sarclient" /usr/local/bin/sarclient sudo install -Dm755 "$SCRIPT_DIR/sarclient" /usr/local/bin/sarclient
sudo install -Dm755 "$SCRIPT_DIR/serverjar" /usr/local/bin/serverjar sudo install -Dm755 "$SCRIPT_DIR/serverjar" /usr/local/bin/serverjar
echo "安裝完成" echo "Install completed"
echo "sarclient -> /usr/local/bin/sarclient" echo "sarclient -> /usr/local/bin/sarclient"
echo "serverjar -> /usr/local/bin/serverjar" echo "serverjar -> /usr/local/bin/serverjar"
+26 -5
View File
@@ -41,7 +41,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.1" VERSION = "1.2"
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
@@ -82,7 +82,7 @@ def load_settings():
"workDir": "", "workDir": "",
"port": 25565, "port": 25565,
"host": "127.0.0.1", "host": "127.0.0.1",
"enable": True "enable": True,
}, },
use_same_form=True, use_same_form=True,
) )
@@ -417,9 +417,10 @@ def resolve_java_executable(java_exec_path, minecraft_version, release, auto_ins
help="Port of the server", required=True) help="Port of the server", required=True)
@click.option("--server-property", "-sp", "server_properties", multiple=True, @click.option("--server-property", "-sp", "server_properties", multiple=True,
help="server.properties override written after the first startup. Use key=value; can be repeated.") help="server.properties override written after the first startup. Use key=value; can be repeated.")
@click.option("--order", "-o", help="Startup order when starting the server", type=int, default=None)
def create_server(name, mc_version, build, server_type, snapshot, latest, list_builds, filename, extra_args, java_exec_path, def create_server(name, mc_version, build, server_type, snapshot, latest, list_builds, filename, extra_args, java_exec_path,
install_java, x_memory_initial, x_memory_maximum, nogui, custom_args, server_port, server_host, install_java, x_memory_initial, x_memory_maximum, nogui, custom_args, server_port, server_host,
server_properties): server_properties, order):
server_dir = Path("servers", name) server_dir = Path("servers", name)
server_type = server_type.lower() server_type = server_type.lower()
property_overrides = parse_server_property_overrides(server_properties) property_overrides = parse_server_property_overrides(server_properties)
@@ -550,6 +551,7 @@ def create_server(name, mc_version, build, server_type, snapshot, latest, list_b
"port": server_port, "port": server_port,
"host": server_host, "host": server_host,
"enable": True, "enable": True,
"order": order
}) })
print("Done") print("Done")
@@ -1006,6 +1008,16 @@ class SocketServer:
except (ConnectionResetError, OSError): except (ConnectionResetError, OSError):
mgr.logger.info( mgr.logger.info(
"[SYS] Client disconnected. From {}:{}".format(self.client_address[0], self.client_address[1])) "[SYS] Client disconnected. From {}:{}".format(self.client_address[0], self.client_address[1]))
except Exception as e:
mgr.logger.exception(
"[SYS] Client handler error. From %s:%s",
self.client_address[0],
self.client_address[1],
)
try:
self.request.sendall(f"[SYS:ERR] Server handler error: {e}\n".encode("utf-8"))
except OSError:
pass
finally: finally:
stop_evt.set() stop_evt.set()
if log_q is not None: if log_q is not None:
@@ -1335,6 +1347,9 @@ class Server:
f"port: {self.port}\n" f"port: {self.port}\n"
f"version: {self.version}\n" f"version: {self.version}\n"
f"status: {"Running" if self.is_process_alive() else "Died"}") f"status: {"Running" if self.is_process_alive() else "Died"}")
elif command == "__restart":
self.restart()
return True, f"Server \"{self.name}\" process restarted"
else: else:
return False, f"Unknown command: {command}" return False, f"Unknown command: {command}"
@@ -1356,7 +1371,8 @@ def load_all_server_from_settings(settings: FileSettings):
servers = [] servers = []
with settings.edit() as s: with settings.edit() as s:
for server_conf in s.get("servers", []): for server_conf in s.get("servers", []):
servers.append(Server( order = server_conf.get("order", None)
server = Server(
name=server_conf.get("name"), name=server_conf.get("name"),
version=server_conf.get("version"), version=server_conf.get("version"),
description=server_conf.get("description"), description=server_conf.get("description"),
@@ -1365,7 +1381,12 @@ def load_all_server_from_settings(settings: FileSettings):
port=server_conf.get("port"), port=server_conf.get("port"),
host=server_conf.get("host"), host=server_conf.get("host"),
enable=server_conf.get("enable") enable=server_conf.get("enable")
)) )
if order is None or order-1<0:
servers.append(server)
else:
servers.insert(order-1, server)
return servers return servers
+5
View File
@@ -1,3 +1,8 @@
prompt-toolkit==3.0.52 prompt-toolkit==3.0.52
cryptography==48.0.0 cryptography==48.0.0
jproperties==2.1.2 jproperties==2.1.2
nuitka==4.1.2
click==8.4.1
pyyaml==6.0.3
requests==2.34.2
jproperties==2.1.2
+39 -7
View File
@@ -1,11 +1,13 @@
import hashlib
import warnings
from pathlib import Path from pathlib import Path
import requests import requests
import click import click
import os import os
from jproperties import Properties from jproperties import Properties
PAPER_VERSION_API = "https://api.papermc.io/v2/projects/paper/versions/{}" PAPER_VERSION_API = "https://fill.papermc.io/v3/projects/paper/versions/{}"
PAPER_SERVER_JAR_API = "https://api.papermc.io/v2/projects/paper/versions/{}/builds/{}/downloads/paper-{}-{}.jar" PAPER_BUILD_API = "https://fill.papermc.io/v3/projects/paper/versions/{}/builds/{}"
MOJANG_VERSION_MANIFEST_V2 = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" MOJANG_VERSION_MANIFEST_V2 = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
@@ -69,7 +71,7 @@ def jar_filename(filename: str | None, default: str) -> str:
return default return default
def download_file(url: str, destination: Path, chunk_size: int = 1024 * 512): def download_file(url: str, destination: Path, chunk_size: int = 1024 * 512, sha256=None):
destination.parent.mkdir(parents=True, exist_ok=True) destination.parent.mkdir(parents=True, exist_ok=True)
with requests.get(url, stream=True, timeout=30) as r: with requests.get(url, stream=True, timeout=30) as r:
@@ -91,6 +93,19 @@ def download_file(url: str, destination: Path, chunk_size: int = 1024 * 512):
if chunk: if chunk:
f.write(chunk) f.write(chunk)
if sha256:
sha256_hash = hashlib.sha256()
with destination.open(mode="rb", buffering=chunk_size) as f:
for chunk in iter(lambda: f.read(chunk_size), b""):
sha256_hash.update(chunk)
hexdigest = sha256_hash.hexdigest()
if hexdigest == sha256:
click.echo(f"File {os.path.basename(destination)} verified.")
else:
raise RuntimeError(f"Download {os.path.basename(destination)} failed. Hash does not match.")
def get_specific_version_paper_builds(minecraft_version: str) -> list[dict[str, str]]: def get_specific_version_paper_builds(minecraft_version: str) -> list[dict[str, str]]:
""" """
@@ -203,24 +218,41 @@ def get_specific_version_minecraft_require_java_version(minecraft_version, relea
return metadata.get("javaVersion", {}).get("majorVersion", None) return metadata.get("javaVersion", {}).get("majorVersion", None)
def get_paper_server_jar_info(minecraft_version: str, build_version: str):
url = PAPER_BUILD_API.format(minecraft_version, build_version)
try:
r = requests.get(url)
if r.status_code == 200:
return r.json().get("downloads", {}).get("server:default", None)
else:
raise Exception(f"Unable to fetch paper server jar information. (VER:{minecraft_version},BUILD:{build_version})\n")
except requests.exceptions.RequestException as e:
raise Exception(f"Unable to get paper version information. Exec: {e}\n")
def download_server_jar(minecraft_version: str, build_version: str, destination: Path, filename: str | None = None): def download_server_jar(minecraft_version: str, build_version: str, destination: Path, filename: str | None = None):
""" """
Download server jar (paper server only) Download server jar (paper server only)
""" """
url = PAPER_SERVER_JAR_API.format(minecraft_version, build_version, minecraft_version, build_version) data = get_paper_server_jar_info(minecraft_version, build_version)
jar_name = jar_filename(filename, os.path.basename(url)) download_url = data.get("url", None)
sha256 = data.get("checksums", {}).get("sha256", None)
jar_name = jar_filename(filename, os.path.basename(download_url))
destination.parent.mkdir(parents=True, exist_ok=True) destination.parent.mkdir(parents=True, exist_ok=True)
destination = Path(destination, jar_name) destination = Path(destination, jar_name)
if not sha256:
warnings.warn("Unable to verify server jar due to hash from server is invalid.", UserWarning)
try: try:
download_file(url, destination) download_file(download_url, destination, sha256=sha256)
return destination return destination
except Exception as e: except Exception as e:
raise Exception( raise Exception(
"Unable to download server jar for version {}\nURL: {}\nError: {}".format(minecraft_version, url, e)) "Unable to download server jar for version {}\nURL: {}\nError: {}".format(minecraft_version, download_url, e))
def get_latest_build_of_version(minecraft_version: str) -> str: def get_latest_build_of_version(minecraft_version: str) -> str: