从脚本到平台:用 mc-ops 统一管理我的 Minecraft 服务器
半年前我写了 Fabric Server Manager,用一套 Bash 脚本管理服务器的启停、备份和升级。后来又陆续加了 QQ 机器人、frp 内网穿透、crontab 定时任务、看门狗……每个组件单独看都能用,合在一起却越来越难维护。
10 月初服务器出了一次安全事件,排查时我才发现:想回答"凌晨两点是谁、从哪个 IP、用什么客户端登录了这个账号",要在四五个日志文件之间来回翻,而且有一半信息根本没有记录。于是我决定推倒重来,把所有东西收拢成一个平台:mc-ops。
这篇文章介绍 mc-ops 的整体设计。另外两篇分别记录了这一路的踩坑经历和备份方案。
服务器的部署环境
| 项目 | 详情 |
|---|---|
| 游戏 | Minecraft 1.21.11,Fabric,40 多个 Mod,离线模式 + EasyAuth 登录 |
| 主机 | 家里的一台小主机:4 核、16 GB 内存,游戏分 8 GB 堆 |
| 公网入口 | 腾讯云轻量应用服务器跑 frps,家里的 frpc 主动连过去 |
| 玩家 | 一群朋友,白名单 16 人,晚上是高峰 |
网络链路是这样的:
玩家 ──> 云主机 frps ──(frp 隧道)──> 家里 frpc ──> MC 25565 / sshd
这个结构省钱,家里不需要公网 IP,但有一个副作用:MC 和 sshd 看到的来源 IP 全是 127.0.0.1。后来我在 frpc 和 MC 之间加了 PROXY protocol(服务端装 proxy-protocol-support,只信任 frpc 专用的源地址),服务器才第一次看到玩家的真实 IP,这也是后面所有安全规则的基础。
旧系统的问题
重构前我先把旧系统盘点了一遍,写成一份清单,作为新系统的验收标准。问题主要有三类:
- 组件之间靠文本和文件"对暗号"。比如重启脚本在重启前写一个
maintenance.signal文件,QQ 机器人轮询这个文件来发维护通知;机器人执行命令要调用mc.sh,再用正则解析它的输出。我数了一下,这类隐式约定一共有 11 处,改一个地方很容易悄悄弄坏另一个。 - 向 tmux 发按键当作命令通道。
tmux send-keys发出去的命令没有返回值,只能等日志刷出来再去匹配,这是旧系统里最脆弱的一部分。 - 没有统一的数据。登录记录、IP、白名单申请、告警分散在各个日志和 JSON 文件里,没法查询,也没法做关联分析。
设计原则
- 一个进程管全局:服务器生命周期、定时任务、备份、升级、白名单、QQ 机器人、安全审计,全部在一个 asyncio 进程(mcops-agent)里实现。模块之间通过进程内的事件总线和数据库协作,原来的文本约定都变成有类型的 Python 接口。
- MC 不在 agent 进程里:MC 跑在独立的 systemd 单元里。agent 重启、升级都不会让玩家掉线,这一点在后面几十次部署里被反复验证过。
- 能力一个不丢、随时可回退:每个阶段上线前都有回退脚本,旧系统在新系统稳定运行之前不删。
- 本地能完整开发:在笔记本上拉起一个真实的 Fabric 测试服,端到端跑通后再部署到生产。
整体架构
笔记本浏览器 ──SSH 隧道──┐
▼
┌──────────────── 家里的 mc-server ─────────────────┐
│ mcops-agent(Python asyncio,只监听 127.0.0.1) │
│ │
│ api FastAPI:REST + WebSocket(控制台/事件)│
│ core 事件总线 · SQLite(WAL) · 配置 · 审计 │
│ server MC 托管 · RCON · 就绪判定 · watchdog │
│ logs latest.log 实时解析为结构化事件 │
│ players 玩家 / 会话 / IP 画像 · 白名单 · 封禁 │
│ backup tar 冷备 · restic 增量 · 异地副本 · 回退 │
│ upgrade MC + Fabric + Mod 升级 · 失败自动回滚 │
│ scheduler 定时任务(取代 crontab) │
│ bot QQ 机器人 │
│ security 规则引擎 → 告警 │
│ edge 经受限只读 SSH 拉取云主机的 frps 日志 │
│ │
│ └─ mc-server.service(Java,由 agent 决定启停) │
└───────────────────────────────────────────────────┘
几个关键决策:
用 RCON 代替 tmux。 RCON 命令有返回值,whitelist list 直接拿到结果,旧系统里"发命令、等日志、正则回读"的逻辑整个消失了。RCON 只监听本机,本机防火墙默认拒绝,frpc 也不转发这个端口。
SQLite 作为唯一的数据源。 所有结构化事件、玩家会话、IP 画像、告警、审计记录都进同一个库。whitelist.json 这类文件仍然给 MC 读,但只有 agent 会写,写完用 RCON whitelist reload 生效并核对。历史日志也一次性导入了:367 个日志文件回放出 1242 次会话,导入当场就发现了两个在白名单里却从没注册过 EasyAuth 的账号。
云主机不装 agent。 云主机上只建了一个受限用户,authorized_keys 里用 forced-command 把它锁死成只能执行 journalctl -u frps -f -o json。agent 用专用密钥常驻拉取 frps 日志,拿到每条外部连接的真实 IP 和代理名。云主机零新增端口,密钥泄露也只能读日志。
面板不对公网开放。 Web 面板只监听 127.0.0.1,我通过 SSH 隧道访问。即便如此,鉴权也按公网标准做:argon2 密码、HttpOnly + SameSite=Strict 会话、CSRF 校验,所有写操作进审计日志。
技术栈
| 层 | 选择 |
|---|---|
| 后端 | Python 3.12 + uv,FastAPI + uvicorn,SQLAlchemy 2 + SQLite(WAL),自己写的极简 RCON 客户端 |
| qq-botpy(官方机器人) | |
| 前端 | Vite + React + TypeScript + Tailwind,TanStack Query,xterm.js 做控制台 |
| 质量 | ruff + mypy --strict + pytest;GitHub Actions 跑后端和前端的全部检查 |
| 部署 | systemd --user:mcops-agent.service 和 mc-server.service,开启 linger 实现开机自启 |
分阶段迁移
最怕的是"一次性切换,然后周末全在救火"。所以迁移拆成了几个阶段,每一步都可以单独回退:
| 阶段 | 内容 | 回退方式 |
|---|---|---|
| M0 本地开发 | 笔记本上对真实测试服开发,CI 全绿 | — |
| M1 旁路观测 | 生产部署 agent,只读:解析日志、拉 frps、安全告警 | 停掉 agent |
| M2 接管 QQ 机器人 | 停旧机器人,agent 内的新机器人上线 | 重新启用旧服务 |
| M3 接管控制 | MC 改由 systemd + RCON 托管,调度器接管重启和备份,注释掉 crontab | 回退脚本恢复 crontab 和旧启动方式 |
| M4 增强 | restic 增量、异地副本、安全规则细化、面板打磨 | 关开关 |
M1 阶段 agent 只读不写,用的是 observe 模式:它通过 /proc 找到正在运行的 MC 进程并观察它,但不碰它。这样 agent 先在生产上跑了几个小时,解析结果、告警规则都对照真实数据校准过,才开始接管控制权。
M3 切换那天,MC 进程从 tmux 迁到 systemd 单元,玩家只经历了一次正常的重启。之后 agent 本身重启了几十次(每次部署都要重启),MC 进程一次都没断过。
现在能做什么
总览与控制台:服务器状态、在线玩家(真人和 Carpet 假人分开显示)、主机的 CPU / 内存 / 磁盘 / 网络和 MC 进程资源占用,带一小时趋势线;Web 控制台走 WebSocket 实时推送日志,命令走 RCON。
玩家与安全:每个玩家的会话历史、登录 IP 和网段、客户端特征;安全规则引擎目前有十几条规则,都来自真实事件:
- 凌晨登录、已知账号出现在新网段、新账号注册;
- 单 IP 多次 EasyAuth 密码错误;
- 冒用玩家名进服(扫描器从服务器列表拿在线玩家名来试);
moved too quickly等外挂特征(会结合鞘翅飞行状态区分误报,详见踩坑篇);- SSH 隧道单 IP 高频连接。
每条规则都能在面板里开关、调阈值和级别。告警按级别分发:严重的推 QQ 管理员群和邮件,一般的只进面板和邮件。
QQ 机器人:查状态(TPS / MSPT、frp 隧道延迟)、在线玩家、申请白名单。白名单申请获批后 24 小时内必须进服并完成 EasyAuth 注册,否则自动移出,机器人会在申请和获批时说明这条规则。
备份与回退:每天 05:00 冷备份,每 6 小时 restic 增量快照,再自动复制到家里的 NAS。面板上选一个时间点就能回退,回退前会先给当前状态拍一个快照,开服失败会自动换回原来的文件。细节见备份篇。
升级:选目标版本后先检查兼容性,逐个去 Modrinth 查 40 多个 Mod 有没有对应版本,以及是否需要换 Java。准备阶段会用 eula=false 试启动新版本,把 Fabric 依赖提前下好,停服后开服就不用联网。升级失败会自动回滚到旧版本和冷备份。
运维任务:定时重启、备份、异地复制、升级都在同一个任务系统里,有历史、有实时日志;回退这种长任务还有进度条和剩余时间。
其他:配置全部可以在面板里改,改动有历史记录,大部分能即时生效;16 项自检(初始密码是否还在、linger 是否开启、RCON 端口是否对得上、多久没备份……);首次安装向导可以在空目录里从零装好服务端;mc-ops 自身也能在面板里上传安装包升级、一键回滚。
开发过程
这个项目从 10 月 9 日写设计文档开始,M0 到 M3 在同一天上线,之后几天陆续完成 M4 和其余功能。现在后端有将近 300 个测试,前端也有 250 多个。
能这么快,主要靠两件事:
- 全程和 AI 协作。整个项目用 Kiro 开发:设计文档、代码、测试、部署、线上排障都在对话里完成。我主要负责做决定,比如选哪个方案、哪些风险能接受。很多问题它会先读 Mod 的源码再下结论,比如后面踩坑篇里 EasyAuth 拦截数据包的问题。
- 拿生产数据做测试。日志解析器和安全规则都用生产的历史日志回放验证过,测试里必须能复现 10 月 4 日那次安全事件的告警;新规则上线前,也会先在一个临时库里回放最近几天的日志,看会报出什么。好几个误报和漏报就是这样在上线前发现的。
mc-ops 暂时没有开源,代码里还有不少和这台服务器绑定的配置和数据,等整理干净了再考虑。
小结
回头看,这次重构最大的收获不是功能变多了,而是服务器变得"可观测"了:谁在什么时候从哪里进来、哪条规则因为什么触发、哪次备份花了多久,现在都能在一个地方查到,并且有数据支撑。以前很多问题要等玩家来抱怨才知道,现在大多能在面板里先看到。
评论