Skip to content

Latest commit

 

History

History
144 lines (110 loc) · 9.45 KB

File metadata and controls

144 lines (110 loc) · 9.45 KB

Docker 部署

基础镜像用 alpine (musl)

sign-proxy 的 Linux .node (src/main/qqProtocol/direct/sign-proxy/) 现在有 musl 变体 (sign-proxy.linux-x64-musl.node / sign-proxy.linux-arm64-musl.node), loader (sign-proxy/index.tspickTriple / isMusl) 在 musl 环境自动选它, 所以能用 node:24-alpine base。镜像 ~540MB, 比 debian (~880MB) 小近一半。

历史 (2026-07 前曾结论"必须 glibc/debian, 不能 alpine"): 当时只有 glibc 链接的 .node (NEEDED libc.so.6, 要 GLIBC_2.29), alpine 加载不了。现已被 musl 变体推翻。glibc 版 (sign-proxy.linux-x64-glibc.node / linux-arm64-glibc.node) 仍保留, 非 musl 环境 (debian/裸机) 走它。glibc/musl 双份是硬约束: musl cdylib 无法静态链接 (rustc crt-static-allows-dylibs=false), 动态 musl .node 又只能在 alpine 跑, 谁都替代不了谁。

llbot.js 顶层 import 链就 require sign-proxy (base.ts → direct → sign.ts → sign-proxy), require 在 import 期即发生, 所以 .node 必须能加载 —— musl 变体保证 alpine 下不崩。

musl sign-proxy 怎么编出来的 (LuckyLillia.SignProxy 仓库)

Windows 本机即可交叉编译 (靠 zig, 不用 alpine 机器)。一键 (确保 std target -> 编两颗 -> 只同步 musl 到 Bot):

npm run build:musl-bot            # scripts/build-musl.mjs, 一条龙

拆开手动跑:

rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl   # 一次性
npm run build:linux-x64-musl      # 或 build:linux-arm64-musl / build:linux-musl(两个)
npm run sync-to-bot               # 注意: 这个拷 dist 下全部 .node, 会覆盖 gnu/win 等; 只要
                                  # musl 用 build:musl-bot (只同步两颗 musl)

国内装 musl std 的坑: rustup target addrust-std-<ver>-*-musl 时, 官方源常卡死 (0 KB/s), tuna 镜像可能没同步新版本 (404)。用 rsproxy: RUSTUP_DIST_SERVER=https://rsproxy.cn rustup target add aarch64-unknown-linux-musl 秒下。zig 交叉本身不需要网络。

命门 (踩过的坑):

  • --cross-compile 让 napi 用 zig 当交叉 C 工具链 + musl 链接器 (不用 alpine/docker)。
  • .cargo/config.toml 给 musl target 设 rustflags = -C target-feature=-crt-static: .node 是 cdylib (.so), musl 默认 +crt-static 会把 libc 静态焊进 .so → alpine dlopen 出问题 且体积大; 关掉 → 动态链系统 musl (/lib/ld-musl-*.so.1), 正常加载。验证过: clean 重编产物 跟手敲 RUSTFLAGS=-crt-static 逐字节一致 (config.toml 确被 zig 交叉吃到)。
  • crypto 能跑因为 SecureSDK 用 rustls + ring (非 openssl), 全 musl 兼容 (SignToken 握手成功即证)。
  • postbuild-rename.mjs 特意保留 -musl 后缀 (只折叠 gnu/msvc), 否则 linux-x64-musl 被改名成 linux-x64 会覆盖掉 glibc 那颗。

在 amd64 开发机上跑整套 arm compose (QEMU 模拟)

想在 x86_64 本机验证 arm 部署 (不换真 ARM 机器), 靠 Docker Desktop 自带的 QEMU 模拟。 坑在于 compose 容易变成混架构: linyuchen/pmhq:*-arm 是 arm64, 但 linyuchen/llbot:test 默认构的是 amd64 (test-build-amd64.ps1 / build.bat 都是 amd64 单架构或 CI 多架构 push, 本地 daemon 里只有 amd64 那颗)。要整套 arm 得补两样:

  1. 构一颗 arm64 llbot 镜像 (dist 里已有 sign-proxy.linux-arm64-musl.node, alpine base 的 loader 自动选它, 现成 Dockerfile.test 直接能构; 跨平台构建走 buildx container driver 而非默认 docker driver, 构完 --load 进本地 daemon):

    docker buildx build --builder mybuilder -f docker/Dockerfile.test \
      --platform linux/arm64 -t linyuchen/llbot:test-arm --load .
    
  2. compose 每个服务显式加 platform: linux/arm64 —— 本机是 amd64, 不声明的话 Docker 按 宿主架构拉/跑, 架构不匹配会报错或行为不定。pmhq + llbot 两个服务都要加。

验证跑对了架构 (应输出 aarch64):

docker exec <container> uname -m

命门:

  • QEMU 模拟只适合测试/验证, 慢; 生产 arm 还是得放真 ARM 机器原生跑。
  • --load 出来的 test-arm 是纯本地镜像, 没 push registry; 换机器要重构或 docker save/load
  • 首次跑 arm 镜像前确认模拟器在: docker run --rm --platform linux/arm64 alpine uname -m 应回 aarch64 (Docker Desktop 一般自带 binfmt, 缺的话 docker run --privileged --rm tonistiigi/binfmt --install arm64)。

容器连接模式: 直连 / PMHQ 有头 (install 脚本二选一)

install 脚本开头让用户选连接模式 (存 PROTOCOL_MODE), startup.sh 按此 env 分发:

  • 直连 (PROTOCOL_MODE 未设/非 pmhq): 纯代码复刻协议, 省内存。单 llbot 服务。 node llbot.js [-q <uin>], 启动哪个号由用户决定, 两条路:

    • AUTO_LOGIN_QQ env 设了 → -q <uin> 恢复该号 (无头部署重启后免扫码自动恢复);
    • 没设 → 起在 WebUI 登录页, 用户从快速登录列表点选账号 (或扫码)。 install 脚本生成的 compose 里始终带 AUTO_LOGIN_QQ=(留空), 方便用户后填。 不再 "data 里恰好一个 session 就自动用它" (2026-07 改): 那是替用户做了选择, 跟 WebUI 快速登录列表的设计冲突。
  • PMHQ 有头 (PROTOCOL_MODE=pmhq): 真实 QQ 客户端跑在独立 linyuchen/pmhq 容器 (有头, 收发 PB), 更稳不易掉线, 代价是吃内存 + 拉一个额外镜像。startup.sh 走 node llbot.js --pmhq-port=$PMHQ_PORT --pmhq-host=$PMHQ_HOST (缺省 13000 / pmhq), 参数名与 pmhq.ts getPMHQHostPort() 对齐, 代码侧靠 isPmhqMode() (检 --pmhq-port= argv) 触发。compose 是 pmhq + llbot 双服务, 同 app_network, 共享 ./llbot_config 卷; llbot depends_on: pmhq。llbot 靠网络 (--pmhq-host=pmhq) 连 pmhq, 自身不挂 QQ 目录。

    • PMHQ 分支固定有头, 不保留旧脚本的无头 y/n 问句 (无头虽省内存但易掉线, 想省内存的直接选直连模式)。
    • 账号登录由 pmhq 容器的 AUTO_LOGIN_QQ 处理, llbot 侧不传 -q
    • 镜像源检测在 pmhq 模式下要求 llbot + pmhq 两个镜像都在该镜像源可用才命中。

session 加密 key: 容器内从 data/machine_guid.bin 派生 (直连 session 存活的关键)

直连 session 的敏感字段 (d2/tgt 等) 落盘前用 AES-256-GCM 加密, key 由 getMachineKey() 提供 (src/main/qqProtocol/direct/session.ts)。非容器绑 OS machine id;容器里 /etc/machine-id 随重建而变, 绑它 = 每次重建都要重新扫码, 所以 isDockerEnvironment() 为真时改从 data/machine_guid.bin (设备 GUID, machineGuid.ts 管理, 随 data volume 持久化) 派生。startup.sh 不碰 /etc/machine-id, 也没有额外的 key 文件。

权衡 (有意取舍, 别改回去): machine_guid.bin 的值 == session 文件里明文的 guid 字段 (machineGuid.ts overwriteMachineGuid <-> saveSession 双向同步), 拿到 session 文件即可还原 key —— 容器场景这层加密不防"单独泄露 session 文件", 防线实为整个 data 卷的访问边界。 曾实现过独立随机 session-key.bin 来堵这一点, 按维护者决定撤掉了: 卷内多一个 key 文件 与密文同卷, 实际防线相同, 不值得多一套文件/逻辑。收益: 备份/迁移整个 data 卷后 session 直接可用, 免重新扫码。

startup.sh 的 sed 用 POSIX 字符类 (兼容 busybox, 不装 GNU sed)

startup.sh 改 default_config.json 的 sed 用 [[:space:]] (POSIX 字符类) 而非 \s (GNU 扩展)。alpine 自带 busybox sed 不认 \s, 会静默失配 (port/host/ffmpeg 路径都不替换, 且不报错); [[:space:]] 则 busybox 和 GNU sed 都认, 所以不用 apk add sed。 (踩过: 早期版本用 \s + 装 GNU sed 覆盖, 后来改 POSIX 去掉这层依赖。) 换源那处 sed -i "s|dl-cdn...|" 是简单替换, busybox sed 本就 OK。 startup.sh shebang 是 #!/bin/sh (POSIX, alpine 的 /bin/sh = busybox ash 也能跑)。

healthcheck

alpine / debian slim 都没有 curl, llbot 容器的 healthcheck 用 node 内置 fetch 探 WebUI: node -e "fetch('http://127.0.0.1:'+(process.env.WEBUI_PORT||3080))..."。 (用户在 WebUI 里关掉 webui 的话会显示 unhealthy, 只影响状态展示, 不影响运行。)

本地构建走代理 (BUILD_PROXY)

Dockerfile.local / Dockerfile.testARG BUILD_PROXY(默认 http://192.168.1.101:7890), 只在 build 期的 RUN 里 export http_proxy/https_proxy(apk / yarn / npm 走它), 不写进运行时 ENV —— 否则容器自己出站会去连这个 LAN 代理。覆盖: --build-arg BUILD_PROXY=http://host:port; 关掉: --build-arg BUILD_PROXY=(空 = 直连)。生产 Dockerfile 无此 ARG (CI 直连)。 test.yml 的 e2e 构建就传 --build-arg BUILD_PROXY= 走直连。

文件清单

文件 用途
docker/Dockerfile 发布镜像 (alpine), 从 GitHub release 下载 LLBot.zip; 生产/CI 用, 无代理无注释; release zip 须含 musl .node (v8.0.8+)
docker/Dockerfile.local 本地两阶段构建 (builder debian 跑 yarn build, production alpine); 走 BUILD_PROXY 代理
docker/Dockerfile.test 本地测试, COPY 本机预构建 dist/ (需先 yarn build), alpine; 走 BUILD_PROXY 代理; test-build-amd64.ps1 用
docker/startup.sh 容器入口, 按 PROTOCOL_MODE 分发直连/PMHQ; shebang 是 #!/bin/sh (POSIX, alpine ash 兼容)
script/install-llbot-docker.sh 交互式向导, 连接模式二选一: 直连(单 llbot 服务) / PMHQ 有头(pmhq+llbot 双服务)