MickeyMiao

起点草甸

MickeyMiao

code is cheap, show me your prompt

51 篇文章 · 18 个分类 · 4 位朋友

向下滑动,出发 (或按 1-6 直达)

服务器小镇

家里小主机上的 Minecraft 生存服,白名单制。加入:QQ 群

状态
读取中…
在线
-
版本
-
已开服
-

每 10 分钟更新一次

关于雪松林

嗨,我是王思淼

图形渲染开发工程师 @ 小米。喜欢图形学、游戏和折腾服务器。

完整介绍 GitHub Bilibili Steam

回到森林 Esc 关闭

游戏Minecraft

从脚本到平台:用 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,这也是后面所有安全规则的基础。

旧系统的问题

重构前我先把旧系统盘点了一遍,写成一份清单,作为新系统的验收标准。问题主要有三类:

  1. 组件之间靠文本和文件"对暗号"。比如重启脚本在重启前写一个 maintenance.signal 文件,QQ 机器人轮询这个文件来发维护通知;机器人执行命令要调用 mc.sh,再用正则解析它的输出。我数了一下,这类隐式约定一共有 11 处,改一个地方很容易悄悄弄坏另一个。
  2. 向 tmux 发按键当作命令通道。tmux send-keys 发出去的命令没有返回值,只能等日志刷出来再去匹配,这是旧系统里最脆弱的一部分。
  3. 没有统一的数据。登录记录、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 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 多个。

能这么快,主要靠两件事:

  1. 全程和 AI 协作。整个项目用 Kiro 开发:设计文档、代码、测试、部署、线上排障都在对话里完成。我主要负责做决定,比如选哪个方案、哪些风险能接受。很多问题它会先读 Mod 的源码再下结论,比如后面踩坑篇里 EasyAuth 拦截数据包的问题。
  2. 拿生产数据做测试。日志解析器和安全规则都用生产的历史日志回放验证过,测试里必须能复现 10 月 4 日那次安全事件的告警;新规则上线前,也会先在一个临时库里回放最近几天的日志,看会报出什么。好几个误报和漏报就是这样在上线前发现的。

mc-ops 暂时没有开源,代码里还有不少和这台服务器绑定的配置和数据,等整理干净了再考虑。

小结

回头看,这次重构最大的收获不是功能变多了,而是服务器变得"可观测"了:谁在什么时候从哪里进来、哪条规则因为什么触发、哪次备份花了多久,现在都能在一个地方查到,并且有数据支撑。以前很多问题要等玩家来抱怨才知道,现在大多能在面板里先看到。

#Minecraft#Fabric#Python#FastAPI#React#systemd#运维#自动化

评论