MCSManager 面板使用 Geyser 自动更新指南
为什么要这样做?
许多服主在使用 MCSManager 面板管理 Minecraft 服务端时,通常会开启基岩版互通。但日常运维中往往面临两大核心问题:
- 基岩版客户端频繁强制升级:Mojang 对基岩版客户端更新极快,且大多为强制升级。官方一旦推送小版本,旧版 Geyser 就会因为协议不匹配直接导致基岩版玩家无法进服。
- 纯外部更新与面板状态脱节:若服主在上班或半夜无法手动进面板操作,玩家就只能长时间干等。
因此,最优雅的解决方案是将「Geyser 自动更新」与「MCSManager 面板」联动控制:通过 Python 脚本定时检查官方构建并校验替换,随后直接调用 MCSManager 的 Web API (/api/protected_instance/restart) 触发面板自动重启,实现无人值守全自动更新。
MCSManager 与 Geyser 联动的核心避坑要点
为了让 MCSManager 能完美控制 Geyser 并在 API 触发重启时正常退出与拉起,必须先做好以下 3 项关键配置:
1. JLine 导致 MCSM 控制台假死与日志阻塞
- 问题根源:Geyser-Standalone 内置了 JLine 交互式终端。在 MCSManager 的虚拟终端(PTY)环境下运行时,JLine 会尝试接管控制台输入输出,造成 I/O 管道阻塞死锁。表现为面板控制台不刷新日志、输入指令无响应,甚至面板停止实例后后台沦为卡死端口的僵尸进程。
- 解决方案:在 MCSManager 实例的 Java 启动参数 中必须加入
-Dterminal.jline=false,显式禁用 JLine:java -Dterminal.jline=false -XX:+CompactStrings -Xms2G -Xmx2G -jar Geyser-Standalone.jar nogui
2. 将 MCSM 应用实例的关闭命令配置为 geyser stop
- 问题根源:MCSManager 默认的实例停止指令通常是
stop或^C,而 Geyser-Standalone 独立版的正规优雅退出指令是geyser stop。 - 解决方案:进入 MCSManager 面板 -> 目标实例 -> 应用实例设置,将关闭命令修改为
geyser stop。这样当调用 Web API 重启或面板手动关服时,面板才能向控制台发送正确的退出指令,等待 Geyser 优雅释放 UDP 19132 端口并安全退出。
3. MCSManager 开启 API Key 支持
调用 Web API 触发重启需要 API Key 鉴权。MCSM 默认未开启外部 API Key 功能,需在服务端配置文件 /opt/mcsmanager/web/data/SystemConfig/config.json 中确认开启:
{
"enableApiKey": true
}修改后重启 MCSManager Web 服务,并在面板 用户管理 页面中为管理员账号生成专属的 apiKey 即可。
自动化流程设计
整体自动化执行流程非常清晰闭环:
+-------------------------+
| 定时触发 (Cron / Timer) |
+------------+------------+
|
v
+-------------------------+
| 请求 Geyser 官方构建 API | ---> (download.geysermc.org)
+------------+------------+
|
v
+-------------------------+
| 对比本地版本与构建号 |
+------------+------------+
|
+------+------+
| | (发现新构建)
(已是最新) v
| +-------------------------+
| | 下载最新构建临时文件 |
| +------------+------------+
| |
| v
| +-------------------------+
| | SHA-256 完整性校验 |
| +------------+------------+
| |
| v
| +-------------------------+
| | 替换 Jar & 更新记录 |
| +------------+------------+
| |
| v
| +-------------------------+
| | 调用 MCSM Web API 重启 |
| +------------+------------+
| |
+------> 退出 <------+完整 Python 自动化脚本
脚本完全采用 Python 3 原生标准库编写,无需额外安装任何第三方依赖。
获取方式(任选其一)
- 服务器一键拉取(推荐):
sudo mkdir -p /usr/local/scripts sudo curl -fsSL https://skycraft.cn/scripts/update_geyser.py -o /usr/local/scripts/update_geyser.py - 本地直接下载: 📥 点击直接下载 update_geyser.py 脚本文件
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Geyser Standalone Auto-Updater & MCSM Auto-Restart Script
Server: Linux / MCSManager 10+
"""
import os
import sys
import json
import time
import hashlib
import urllib.request
import urllib.error
import urllib.parse
# 1. 基础路径配置(根据你的实际部署路径修改)
GEYSER_DIR = "YOUR_GEYSER_DIR_KEY_HERE"
JAR_PATH = os.path.join(GEYSER_DIR, "Geyser-Standalone.jar")
VERSION_FILE = os.path.join(GEYSER_DIR, ".geyser_version.json")
LOG_FILE = "/var/log/geyser_autoupdate.log"
# 2. Geyser 官方构建与下载 API
API_URL = "https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest"
DOWNLOAD_URL = "https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/standalone"
# 3. MCSManager Web 面板配置
MCSM_WEB_URL = "http://localhost:23333" # MCSManager Web 访问地址
MCSM_API_KEY = "YOUR_MCSM_API_KEY_HERE" # 在 MCSManager 用户管理中生成的 API Key
DAEMON_ID = "YOUR_DAEMON_ID_HERE" # 实例所在的节点 ID (Daemon ID)
INSTANCE_UUID = "YOUR_INSTANCE_UUID_HERE" # Geyser 实例的 UUID
def log(msg):
ts = time.strftime("%Y-%m-%d %H:%M:%S")
entry = f"[{ts}] {msg}"
print(entry)
try:
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(entry + "\n")
except Exception as e:
print(f"Failed to write log: {e}")
def get_current_local_info():
if os.path.exists(VERSION_FILE):
try:
with open(VERSION_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
pass
return {"build": 0, "version": "", "sha256": ""}
def save_local_info(info):
try:
with open(VERSION_FILE, "w", encoding="utf-8") as f:
json.dump(info, f, indent=2)
except Exception as e:
log(f"Failed to save local version info: {e}")
def fetch_remote_build_info():
req = urllib.request.Request(API_URL, headers={"User-Agent": "GeyserAutoUpdater/2.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
if resp.status == 200:
return json.loads(resp.read().decode("utf-8"))
return None
def calculate_sha256(file_path):
h = hashlib.sha256()
with open(file_path, "rb") as f:
while chunk := f.read(65536):
h.update(chunk)
return h.hexdigest()
def restart_geyser_instance():
log("Triggering Geyser restart via MCSM Web API...")
params = urllib.parse.urlencode({
"daemonId": DAEMON_ID,
"uuid": INSTANCE_UUID,
"apikey": MCSM_API_KEY
})
url = f"{MCSM_WEB_URL}/api/protected_instance/restart?{params}"
try:
req = urllib.request.Request(url, headers={"User-Agent": "GeyserAutoUpdater/2.0"})
with urllib.request.urlopen(req, timeout=15) as resp:
data = json.loads(resp.read().decode("utf-8"))
if data.get("status") == 200:
log("MCSManager restart command executed successfully via Web API.")
return True
else:
log(f"MCSManager restart Web API returned non-200: {data}")
return False
except Exception as e:
log(f"Error calling MCSM Web API: {e}")
return False
def main():
force = "--force" in sys.argv
log("=== Checking for Geyser updates ===")
try:
remote_info = fetch_remote_build_info()
except Exception as e:
log(f"Failed to query remote build info: {e}")
return
if not remote_info:
log("Empty response from Geyser API.")
return
remote_build = remote_info.get("build")
remote_version = remote_info.get("version")
remote_sha256 = remote_info.get("downloads", {}).get("standalone", {}).get("sha256", "")
local_info = get_current_local_info()
local_build = local_info.get("build", 0)
log(f"Remote version: {remote_version} (Build {remote_build}) | Local build: {local_build}")
needs_update = force or (remote_build > local_build) or not os.path.exists(JAR_PATH)
if not needs_update:
log("Geyser is already up-to-date. No action needed.")
return
log(f"New build detected: Build {remote_build} (Current: {local_build}). Downloading update...")
temp_jar = JAR_PATH + ".tmp"
try:
req = urllib.request.Request(DOWNLOAD_URL, headers={"User-Agent": "GeyserAutoUpdater/2.0"})
with urllib.request.urlopen(req, timeout=60) as resp, open(temp_jar, "wb") as out_file:
while chunk := resp.read(65536):
out_file.write(chunk)
# SHA-256 完整性校验
if remote_sha256:
downloaded_sha256 = calculate_sha256(temp_jar)
if downloaded_sha256.lower() != remote_sha256.lower():
log(f"Checksum mismatch! Expected {remote_sha256}, got {downloaded_sha256}. Aborting.")
if os.path.exists(temp_jar):
os.remove(temp_jar)
return
# 替换现有 Jar
os.replace(temp_jar, JAR_PATH)
save_local_info({
"build": remote_build,
"version": remote_version,
"sha256": remote_sha256,
"updated_at": time.strftime("%Y-%m-%d %H:%M:%S")
})
log(f"Geyser-Standalone.jar successfully updated to Build {remote_build} ({remote_version}).")
# 触发面板热重启
restart_geyser_instance()
except Exception as e:
log(f"Update failed: {e}")
if os.path.exists(temp_jar):
try:
os.remove(temp_jar)
except Exception:
pass
if __name__ == "__main__":
main()定时任务配置(任选其一)
为了让脚本自动运行,我们配置定时任务(建议每 4 小时或每天凌晨执行)。
提示
请确保下方配置中的脚本路径(如 /usr/local/scripts/update_geyser.py)与实际保存的脚本路径保持一致;Python 路径可通过 which python3 命令确认。
在 /etc/cron.d/geyser_autoupdate 文件中写入(每 4 小时自动检测一次):
# 脚本内置了日志写入功能(默认记录至 /var/log/geyser_autoupdate.log)
0 */4 * * * root /usr/bin/python3 /usr/local/scripts/update_geyser.py > /dev/null 2>&1- 创建服务描述文件
/etc/systemd/system/geyser-autoupdate.service:
[Unit]
Description=Geyser Standalone Auto Updater
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /usr/local/scripts/update_geyser.py
# 脚本内部已自动写入 /var/log/geyser_autoupdate.log,此处由 systemd 收集输出
StandardOutput=journal
StandardError=journal- 创建定时器文件
/etc/systemd/system/geyser-autoupdate.timer:
[Unit]
Description=Run Geyser Auto Updater every 4 hours
[Timer]
OnBootSec=5min
OnUnitActiveSec=4h
Persistent=true
[Install]
WantedBy=timers.target- 启用并启动定时器:
systemctl daemon-reload
systemctl enable --now geyser-autoupdate.timer测试与查看日志
手动强制触发一次更新
加上 --force 参数可以无视版本号强制走一遍下载校验与重启流程:
python3 /usr/local/scripts/update_geyser.py --force跟踪运行日志
tail -f /var/log/geyser_autoupdate.log日志输出示例:
[2026-08-27 04:57:40] === Checking for Geyser updates ===
[2026-08-27 04:57:41] Remote version: 2.11.2 (Build 1232) | Local build: 1232
[2026-08-27 04:57:41] New build detected: Build 1232 (Current: 1232). Downloading update...
[2026-08-27 04:57:46] Geyser-Standalone.jar successfully updated to Build 1232 (2.11.2).
[2026-08-27 04:57:46] Triggering Geyser restart via MCSM Web API...
[2026-08-27 04:57:46] MCSManager restart command executed successfully via Web API.