之前部署用的 SSH 助手放在系统临时目录里,重启就丢了,下一位接手时根本拿不到。 现在把它搬进仓库并补齐文档: 新增 - scripts/ssh-helper.js:SSH 执行与 SFTP 上传。Windows 自带 OpenSSH 但没有 sshpass/plink,无法非交互传密码,所以用 ssh2 直接实现协议层。 凭据全部走环境变量(SSH_HOST/SSH_PORT/SSH_USER/SSH_PASS),脚本里不含任何密钥; ssh2 是 devDependencies,未安装时提示安装而不是裸崩 - scripts/deploy.js:一键部署 —— 打包 → 上传 → 远程执行 update.sh → 轮询确认控制台返回 200,任一步失败都明确报错。可选 DEPLOY_APP / DEPLOY_PORT - npm scripts:deploy、ssh 文档 - TODO.md 新增「部署方法」章节:一键部署与手动部署两种步骤、辅助脚本说明、 update.sh 的行为、pm2 与日志的常用运维命令(含回滚)、服务器上关键文件清单、 新增环境变量时的三件事(.env 真实值 + .env.example 占位符 + 确认 gitignore) - README「部署到服务器」开头加一键更新说明,并指向 TODO.md 的完整章节 - 顺带提醒旧版 Fabric 模组源码在临时目录里会被清理,接手后建议先备份 测试 368 全绿;一键部署已实跑验证(4/4 校验通过,控制台 200 OK)。
18 KiB
TODO:留给后续开发者的待办与已知问题
本文档记录尚未解决的大工程、已经排查过的线索、以及推荐的验证方法。 小事已在代码里做完并提交,这里只写需要集中攻关的部分。
最后更新:2026-09-29
0. 先读这里:项目现状与部署
| 项 | 值 |
|---|---|
| 技术栈 | Node.js ≥ 22 + mineflayer 4.39.0 + mineflayer-pathfinder 2.4.5 |
| 测试 | npm test(368 个用例全绿) |
| 语法与前端 id 自检 | npm run check |
| 本地启动 | npm start(控制台 http://127.0.0.1:3080/) |
| 服务器 | root@103.236.99.177:33851,部署目录 /opt/autostore-bot |
| 控制台 | http://103.236.99.177:78/ ,密码在服务器 .env 的 WEB_PASSWORD |
| 进程管理 | pm2 应用 autostore-bot(端口 78);旧版 Fabric 机器人为 minecraft-bot(端口 91,已停) |
| 私有 Node | /opt/autostore-bot/runtime/current/bin/node(v24.16.0) |
旧版 Fabric 模组源码(重要参考,实机可用的实现):
C:\Users\luoyi\AppData\Local\Temp\autostore-deploy\oldbot.js(1356 行)。
它在这台服务器上是真的能走路、能寻路、能扩地的,所以遇到 mineflayer 行为诡异时,
第一件事应该是拿它的实现逐项对照,而不是自己猜。
⚠️ 上面的临时目录会被系统清理。若已丢失,它只作为历史参考 —— 不影响当前项目运行,但排查移动问题时很有价值,建议接手后先备份一份到仓库外。
0.1 🚀 部署方法(完整步骤)
方式一:一键部署(推荐)
# Windows PowerShell
$env:SSH_HOST='103.236.99.177'; $env:SSH_PORT='33851'
$env:SSH_USER='root'; $env:SSH_PASS='<服务器密码>'
npm run deploy
# Linux / macOS
SSH_HOST=103.236.99.177 SSH_PORT=33851 SSH_USER=root SSH_PASS='<密码>' npm run deploy
scripts/deploy.js 会依次做:打包 → 上传 → 远程执行 update.sh →
轮询确认控制台返回 200。任一步失败都会明确报错。
可选环境变量:DEPLOY_APP(默认 /opt/autostore-bot)、DEPLOY_PORT(默认 78)。
方式二:手动部署
# 1. 打包(务必排除 data / .env / node_modules,否则会覆盖服务器上的真实数据)
tar -czf update.tar.gz \
--exclude=node_modules --exclude=.git --exclude=data --exclude='*.log' --exclude=.env \
-C . .
# 2. 上传
scp -P 33851 update.tar.gz root@103.236.99.177:/opt/autostore-bot/update.tar.gz
# 3. 远程应用(update.sh 会保留 .env / data / node_modules / runtime)
ssh -p 33851 root@103.236.99.177 'bash /opt/autostore-bot/update.sh'
辅助脚本
| 脚本 | 用途 |
|---|---|
scripts/deploy.js |
一键部署(推荐) |
scripts/ssh-helper.js |
单条远程命令 / 上传:node scripts/ssh-helper.js exec "<命令>"、... put <本地> <远程> |
Windows 自带 OpenSSH 但没有 sshpass / plink,无法非交互传密码,
所以这两个脚本用 ssh2 直接实现协议层(ssh2 是 devDependencies,
未安装时脚本会提示 npm install --save-dev ssh2 而不是裸崩)。
凭据一律走环境变量,脚本里不写死任何密码。
update.sh 做了什么
/opt/autostore-bot/update.sh 的行为:
- 解压
update.tar.gz到/opt/autostore-bot(保留.env、data/、node_modules/、runtime/) - 用私有 Node 重新安装/校验依赖
pm2 restart autostore-bot --update-env- 打印进程状态与端口监听情况
常用运维命令
# 进程状态
pm2 status
pm2 list
# 重启 / 停止
pm2 restart autostore-bot --update-env
pm2 stop autostore-bot
# 日志(实时)
tail -f /opt/autostore-bot/data/pm2-out.log # 运行日志
tail -f /opt/autostore-bot/data/pm2-error.log # 告警与错误
pm2 logs autostore-bot
# 端口
ss -lntp | grep ':78 '
# 回滚:把上一份备份目录改回来(update.sh 会先备份旧版本目录)
ls -1d /opt/autostore-bot/backup-* 2>/dev/null | tail -3
服务器上的关键文件
| 路径 | 说明 |
|---|---|
/opt/autostore-bot/.env |
真实凭据(MC_USERNAME、MC_PASSWORD、WEB_PASSWORD、SIMP_API_KEY、SIMP_API_V2_KEY),权限 0600,绝不进仓库 |
/opt/autostore-bot/data/autostore.json |
运行时配置(控制台保存的),会被 update.sh 保留 |
/opt/autostore-bot/data/bots.json |
机器人非敏感设置 |
/opt/autostore-bot/data/bots.credentials.json |
界面添加的账号密码,权限 0600 |
/opt/autostore-bot/data/chat/<玩家名>.jsonl |
公屏完整日志 |
/opt/autostore-bot/data/expansion-log.jsonl |
开拓日志 |
/opt/autostore-bot/data/mapcache/ |
大地图瓦片缓存 |
/opt/autostore-bot/ecosystem.config.js |
pm2 配置 |
/opt/autostore-bot/runtime/current |
私有 Node(服务器自带 Node 太旧时用) |
新增环境变量时
- 在
.env里加真实值(只在服务器上加,不要提交); - 在
.env.example里加占位符(如<your-xxx-key>),绝不含真实密钥; - 确认
.gitignore已包含.env(本项目已含,并有!.env.example例外)。
1. 🔴 最大问题:寻路器驱动的移动会被服务端持续回弹
现象
- 手动按键移动完全正常:通过控制台「移动测试」按 forward 2 秒,实测位移 4~8 格,位置保持。
- 寻路器驱动的移动被回弹:一旦
bot.pathfinder.setGoal()开始执行, 服务端以 5~9 次/秒 的频率强制同步位置(forcedMove),机器人几乎原地不动。 - 偶尔能挪动一点,但速度只有 0.3 格/秒(正常步行 4.3 格/秒), 所以几百格外的目标在超时前根本走不到。
已确认正常的部分(不要再怀疑这些)
| 项 | 状态 | 证据 |
|---|---|---|
player_input 补包 |
✅ 每 physicsTick 都发(~20/s),bitflags 正确 | packetStats.playerInput 持续增长 |
tick_end 补包 |
✅ 每帧发送 | grim.protocolPackets.tickEndSent |
onGround 标志 |
✅ 出站包已按世界数据复核 | 实测空闲时 onGround=true,世界脚下确实是 stone |
| 世界数据可读性 | ✅ world.getBlock 正常,四向可见 115~125 格 |
pathingDiag.visibleDistance |
| 寻路器本身 | ✅ 在开阔地能找到路径 | 实测「已探索 3633 节点 → success,代价 113.8」 |
| 服务端回弹是否持续 | ❌ 只在机器人尝试移动时出现;空闲时入站位置包为 0 | 12 秒采样:空闲 +0,寻路中 +105 |
| 关闭疾跑 | 试过,无改善 | allowSprint=false 后仍是 4.8 次/秒回弹 |
| 关闭合规层(grim.enabled=false) | 试过,无改善 | A/B 实测:开启 6.0 次/秒 vs 关闭 5.5 次/秒,机器人位移都是 0 格 |
| 世界数据是否错误 | ❌ 不是原因 | 实测脚下 stone,blockBelow 正确 |
🎯 最关键的一条线索:水平速度恒为 0
用 /api/status 的 moveDiag 字段采集到的实测数据:
空闲时
{ "controls": { "forward": false, "sprint": false, ... }, "anyPressed": false,
"velocity": { "x": 0, "y": -0.078, "z": 0 }, "speed": 0.078, "onGround": true,
"pathfinder": { "isMoving": false, "goal": null } }
寻路中
{ "controls": { "forward": true, "sprint": true, ... }, "anyPressed": true,
"velocity": { "x": 0, "y": -0.078, "z": 0 }, "speed": 0.078, "onGround": false,
"pathfinder": { "isMoving": true, "goal": {...} } }
解读(这是下一步攻关的起点):
- 寻路器确实在执行 ——
forward/sprint都按下去了,isMoving=true。 所以不是「寻路器没跑」,问题在更底层。 - 水平速度恒为 0,而
velocity.y = -0.078恰好是自由落体第一 tick 的重力值。 这说明速度每 tick 都被清零,机器人永远积累不起速度。 onGround被服务端的 position 包反复置为 false: mineflayer 的physics.js在处理服务端position包时会把entity.onGround置为 false, 而本服下发位置包的频率(5~6 次/秒)远高于物理引擎把它改回来的速度。 结果是:机器人站在石头上,但物理引擎一直认为自己在空中, 于是水平加速走的是「空中控制」分支,而位置包又把速度清零,两者叠加就是「按着前进键却纹丝不动」。- 已尝试的缓解:
_isStandingOnGround()里加了「服务端是否正把我们按在原地」的判断 (_serverPinsUs()),出站包的onGround已经能正确上报。 但bot.entity.onGround(物理引擎内部值)仍然是 false,所以物理计算依旧按空中处理 —— 这可能就是还差的那一步。
排查建议(按优先级,已按实测结果更新)
假设 1:GrimCompat 对 bot.look 的拦截 ❌ 已排除
A/B 实测 grim 开/关对回弹频率没有影响(6.0 vs 5.5 次/秒),机器人位移都是 0。不要再往这个方向查。
假设 2:bot.entity.onGround 被位置包置 false(最可疑,优先测)
这是目前唯一能解释「按键有、速度恒 0」的机制。方向:
- 方案 A:在
physicsTick里,当出站包已判定「脚下有方块」时, 把bot.entity.onGround也强制同步为 true,让物理引擎走地面分支。 (注意:物理引擎每 tick 会自己重算这个值,所以需要在它之后、发送位置包之前介入, 或者直接监听并覆盖。需要读 mineflayerlib/plugins/physics.js确认执行顺序。) - 方案 B:降低服务端下发位置包的频率对我们的影响 ——
例如在收到
position包时,如果位置与我们自己的预测几乎一致(只差浮点误差), 不采纳它的 onGround=false,只同步坐标。 这比方案 A 更贴近「我们是对的、服务端只是话多」的实际情况。 - 验证:改完后看
moveDiag.speed是否变成 0.1 以上、moveDiag.onGround是否稳定为 true, 以及被服务器传送另有的计数是否下降。
假设 3:跳跃 / 跑酷的物理预测不匹配
Grim 的 Prediction 检查对跳跃最严格(起跳时机、跳跃高度、疾跑跳加成)。
mineflayer 的 physics.js 在这些细节上与原版有已知偏差。
验证方法:把路径限制成纯平地,禁用一切跳跃:
{"allowSprint": false, "pathing": {"allowParkour": false, "allow1by1towers": false, "maxDropDown": 1}}
若回弹消失 → 需要在 GrimCompat 里拦下 jump 控制状态,改用更保守的移动策略。
假设 4:发包顺序 / 包类型与原版不一致
旧模组的调试指令 !tp 额外发的是 position_look(同时带位置与朝向)。
需要实测确认:mineflayer 在朝向变化时到底发的是 position 还是 position_look,
以及服务端是否因为「朝向变了却只发 position」而判定异常。
验证方法:在 GrimCompat 里统计出站包类型分布,
与旧模组移动时的包序列逐项对照(旧模组源码见上文路径)。
假设 5:直接抓包对照
用 scripts/check-protocol.js 的思路扩展一个抓包脚本,
分别在「旧模组移动」和「mineflayer 寻路移动」时记录 5 秒的完整出站包序列,
diff 出差异。这是最彻底也最费时的办法,但能一次性定位问题。
💡 备选方案:绕开 mineflayer-pathfinder
既然手动控制状态是有效的,可以考虑不依赖 pathfinder 的执行层,自己写一个简单的转向控制器:
- 用
bot.pathfinder只算路径(bot.pathfinder.getPathTo()拿到路径点数组,不执行); - 自己实现执行:朝下一个路径点
bot.look→ 按住forward→ 到位后切下一个点; - 遇到 1 格高的坎就按
jump,遇到障碍就重新算路径。
这样能完全控制发包与朝向的时序,也便于在 GrimCompat 里做逐 tick 审计。
工作量中等,但比继续调 pathfinder 的参数更可能出结果。
2. 🟡 「被传送到原点」的两种原因(已在代码里区分,但可继续完善)
服务器把玩家丢到世界原点附近有两种完全不同的原因,以前在日志里长得一样, 经常被误判成「机器人坏了」:
| 原因 | 判断依据 | 处理方式 | 现状 |
|---|---|---|---|
| 死亡后被卡住 | 服务器没有要求验证码 | 左右移动即可触发服务器重新传送,可自动自救 | ✅ BotManager._escapeFromOrigin() |
| 被切到登录服 | 服务器要求输入验证码 | 必须重新输入验证码,只能人工完成 | ✅ 已识别并提示,控制台有醒目横幅 |
⚠️ 不要用「脚下是不是基岩」来区分这两种情况 —— 实测两种情况下脚下都是基岩。 唯一可靠的信号是服务器有没有发「请直接输入验证码进行验证」这类提示。 代码里以
_needAuthWarned为准;没要求验证码就按「死亡卡住」处理并尝试自救 (自救在两种情况下都是无害的,所以判断错了也不会把事情弄得更糟)。
待完善:自救的成功率没有统计。可以记录「自救后位置是否恢复」, 失败时再升级为提示用户;也可以尝试更多方向组合(前/后/跳)。
3. 🟡 自动回城(/u return)已加护栏,但仍可改进
回城是破坏性操作:它把机器人拉回安全点,会清掉玩家在游戏客户端里手动摆好的位置。 项目里曾因此反复把机器人从玩家摆好的位置拉走,排查了很久。
现状(已修):
returnOnTimeout默认关闭;- 即便打开,
returnGraceSeconds(默认 300 秒)内不执行回城; - 回城指令可配置(
returnCommand,默认u return); - 每次回城都明确记日志,说明「会丢失当前位置」。
待改进:
- 回城前先判断「原地是否真的更差」——例如用
pathingDiag.neighborCount判断 当前位置是否还有可走方向;如果邻居数正常,回城毫无意义。 - 记录回城前后的坐标,在控制台上展示「回城把机器人从 A 挪到了 B」, 让用户能判断这次回城是否值得。
4. 🟢 其它已知但优先级较低的问题
4.1 大地图
- 目前只有层级 0 会批量预爬取,层级 1~3 靠用户缩放时按需拉取,首次浏览较慢。
- 可以考虑后台低优先级预热:机器人移动到新区域时,顺带爬取周边高层级瓦片。
- 领土多边形在缩放较大时仍是全量绘制,超大地图(全世界视角 + 全部邦国)可能掉帧, 可以按视野做裁剪。
4.2 控制台
- 移动端布局已适配,但日志区在手机上滚动性能一般(每行都是 DOM 节点)。 数据量大时建议改虚拟列表,或限制保留行数。
- 配置面板有 8 个标签页、字段很多,可以加搜索框。
4.3 多机器人
- 领土模型是全局共享的,多机器人同时扩地时对同一批候选区块的协调依赖
_isClaimedByOthers,但没有真正的分布式锁;高并发时可能撞车。 - 建议给「目标区块」加一个带租约的占用表。
4.4 定时消息
- 只支持固定间隔,没有「cron 式」或「每天某时刻」的调度。
- 没有发送结果确认:
/u restore confirm发出去后是否成功,目前只靠人看日志。
4.5 登录验证
- 验证码只能人工获取与输入(服务器设计如此)。 可以在「等待验证码」时通过通知渠道(如飞书机器人)提醒用户,避免错过 120 秒时限。
5. 常用排查命令
# 机器人完整状态(含寻路诊断)
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0]'
# 寻路诊断:邻居数、世界可读性、四向可见距离
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0].pathingDiag'
# 实时日志
tail -f /opt/autostore-bot/data/pm2-out.log
tail -f /opt/autostore-bot/data/pm2-error.log
# 公屏完整日志(含服务器消息,JSONL)
tail -f /opt/autostore-bot/data/chat/<玩家名>.jsonl
# 寻路状态(判断「探索了多少节点」是定位卡死的关键指标)
grep '寻路状态' /opt/autostore-bot/data/pm2-out.log | tail -20
# 被服务器回弹的频率(>100/30秒 说明移动被反作弊拒绝)
grep '被服务器传送另有' /opt/autostore-bot/data/pm2-error.log | tail -5
判断「机器人是不是卡在登录服」
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status \
| jq '.data.bots[0] | {position, chunk, needsVerification}'
needsVerification == true→ 服务器正在等待验证码,控制台会显示醒目提示(这是可靠信号)- 位置靠近原点且
needsVerification == false→ 多半是死亡后卡住,机器人会自动左右移动自救 - ⚠️
world.blockBelow == "bedrock"不能用来判断 —— 实测死亡卡住和登录服两种情况脚下都是基岩
6. 代码约定(改动前请遵守)
- 提交信息用约定式提交(
feat:/fix:/docs:/refactor:/test:/chore:),主题行说清做了什么。 - 所有密钥只走
.env(已在.gitignore里),代码里绝不硬编码; 新增配置项要同时更新.env.example的占位符。 - 注释与文档用中文,解释「为什么」而不是「做了什么」。
- 每个
fix都应补一个能复现原问题的测试;本项目测试在test/,用node:test。 - 改前端记得跑
npm run check,它会校验app.js引用的元素 id 在index.html里都存在。