SnowLuma

Docker 部署

在 Linux 上用官方 Docker 镜像运行 SnowLuma

SnowLuma 在 Linux 上只通过 Docker 运行。镜像内置 Linux QQ + Xvfb + VNC + noVNC + supervisord,开箱即用;宿主机只需要 Docker。

镜像构建脚本与 Compose 模板在独立仓库 SnowLuma/SnowLuma.Docker.Framework。

镜像信息

项值
镜像motricseven7/snowluma:latest (或具体 tag :v1.8.0)
架构linux/amd64、linux/arm64
基础node:22-bookworm-slim
内置Linux QQ、Xvfb、VNC、noVNC、supervisord、SnowLuma lite 发行包

镜像不在容器内重新编译 SnowLuma —— 它消费主仓 GitHub Release 的预编译 lite tarball,所以发版与镜像构建解耦。

端口

端口用途
5900VNC(远程桌面,密码登录 QQ 用)
6081noVNC(浏览器版 VNC,无客户端访问)
5099SnowLuma WebUI
3000OneBot HTTP(默认)
3001OneBot WebSocket(默认)

宿主机准备:放开 ptrace 限制

这一步必须先做,否则容器起来后 hook 注入会失败。

SnowLuma 的 native addon 通过 ptrace 把 hook 注入到容器内的 QQ 进程。现代 Linux 发行版(Ubuntu / Debian / Arch 等)默认把 kernel.yama.ptrace_scope 设为 1 或更高,即使容器启动加了 --cap-add=SYS_PTRACE,注入也会被宿主内核拦掉。

在宿主机上只需要执行一次:

echo 'kernel.yama.ptrace_scope=0' | sudo tee /etc/sysctl.d/99-snowluma-ptrace.conf
sudo sysctl --system

验证是否生效:

sysctl kernel.yama.ptrace_scope
# 期望输出:kernel.yama.ptrace_scope = 0

写到 /etc/sysctl.d/99-snowluma-ptrace.conf 是为了让设置在重启后自动恢复。如果你的环境对宿主机安全有更严格要求(比如多租户、面向公网),请考虑使用受信任的隔离机制(独立物理机 / VM)替代这一步。

一行 docker run

docker run -d \
  --name snowluma \
  --restart unless-stopped \
  --shm-size=1g \
  --cap-add=SYS_PTRACE \
  --security-opt seccomp=unconfined \
  -e VNC_PASSWD=vncpasswd \
  -p 5900:5900 \
  -p 6081:6081 \
  -p 5099:5099 \
  -p 3000:3000 \
  -p 3001:3001 \
  -v snowluma-data:/app/snowluma-data \
  -v snowluma-qq-config:/app/.config \
  -v snowluma-qq-data:/app/.local/share \
  motricseven7/snowluma:latest

--cap-add=SYS_PTRACE 和 --security-opt seccomp=unconfined 不能省。SnowLuma 的 native addon 通过 ptrace 把 hook 注入 QQ 进程,默认 seccomp profile 会拦截。

--shm-size=1g 是 QQ(基于 Chromium)的运行要求,容器默认 /dev/shm 太小会崩。

docker-compose

更推荐用 Compose。仓库 docker-compose.yml:

services:
  snowluma:
    image: ${SNOWLUMA_IMAGE:-motricseven7/snowluma:latest}
    container_name: ${SNOWLUMA_CONTAINER:-snowluma}
    restart: unless-stopped
    shm_size: 1gb
    cap_add:
      - SYS_PTRACE
    security_opt:
      - seccomp=unconfined
    environment:
      VNC_PASSWD: ${VNC_PASSWD:-vncpasswd}
      SNOWLUMA_UID: ${SNOWLUMA_UID:-1000}
      SNOWLUMA_GID: ${SNOWLUMA_GID:-1000}
      SNOWLUMA_WEBUI_PORT: ${SNOWLUMA_WEBUI_PORT:-5099}
      SNOWLUMA_LOG_LEVEL: ${SNOWLUMA_LOG_LEVEL:-info}
      SNOWLUMA_SCREEN: ${SNOWLUMA_SCREEN:-1920x1080x16}
      SNOWLUMA_HOOK_AUTOLOAD: ${SNOWLUMA_HOOK_AUTOLOAD:-1}
    ports:
      - "${VNC_PORT:-5900}:5900"
      - "${NOVNC_PORT:-6081}:6081"
      - "${SNOWLUMA_WEBUI_HOST_PORT:-5099}:${SNOWLUMA_WEBUI_PORT:-5099}"
      - "${ONEBOT_HTTP_PORT:-3000}:3000"
      - "${ONEBOT_WS_PORT:-3001}:3001"
    volumes:
      - snowluma-data:/app/snowluma-data
      - snowluma-qq-config:/app/.config
      - snowluma-qq-data:/app/.local/share

volumes:
  snowluma-data:
  snowluma-qq-config:
  snowluma-qq-data:

启动:

docker compose up -d

升级镜像:

docker compose pull
docker compose up -d

环境变量

变量默认说明
VNC_PASSWDvncpasswdVNC / noVNC 登录密码。改成自己的,不然别人也能进容器里看你 QQ。
SNOWLUMA_UID1000容器内 snowluma 用户的 uid。和你挂载卷的宿主属主匹配。
SNOWLUMA_GID1000容器内 snowluma 用户的 gid。
SNOWLUMA_WEBUI_PORT5099WebUI 监听端口(容器内)。
SNOWLUMA_LOG_LEVELinfoerror / warn / info / debug。
SNOWLUMA_SCREEN1920x1080x16Xvfb 分辨率 + 色深。
SNOWLUMA_HOOK_AUTOLOAD1镜像默认开自动注入。设 0 切回手动 Load 流程。

环境变量优先级高于 runtime.json,所以 Docker 用环境变量覆盖最方便。

数据卷

卷容器路径内容
snowluma-data/app/snowluma-dataSnowLuma 配置、缓存、SDB 等。config/onebot.json 和 config/runtime.json 都在这里。
snowluma-qq-config/app/.configQQ 客户端配置。
snowluma-qq-data/app/.local/shareQQ 用户数据(登录态、缓存等)。

升级镜像不要删这三个卷,否则会丢登录态和配置。

首次登录

  1. 启动容器后用浏览器打开 http://<IP>:6081/(noVNC),输入 VNC_PASSWD。
  2. 在远程桌面里能看到 QQ 已自动起来,扫码登录。
  3. 登录完成后 hook 会自动从被动观察切到工作模式,无需在 WebUI 里手动 Load。
  4. 浏览器打开 http://<IP>:5099/ 访问 SnowLuma WebUI。

找 WebUI 临时密码

首次启动会在日志里输出一次性密码:

docker logs snowluma 2>&1 | grep -E "临时密码|initial credentials" | tail -n 1

只要密码字符串:

docker logs snowluma 2>&1 | \
  sed -nE 's/.*(临时密码: |initial credentials: user=admin password=)([^[:space:]]+).*/\2/p' | tail -n 1

临时密码只在全新数据卷首次启动时输出一次。复用旧卷 / 重启都不会重新生成。

多开 QQ(多账号)

Linux QQ 自带单实例锁:在已有 QQ 跑着的桌面里直接再点一次 QQ 图标,只会把已有窗口聚焦,起不来第二个进程。锁文件落在 $HOME/.config/QQ/SingletonLock,所以解法是给第二个实例一个独立的 HOME。

SnowLuma 一侧不需要任何额外配置:HookManager 会遍历所有 QQ 主进程,新启动一个就自动注入一个,每个 UIN 拉一个独立 OneBotInstance,配置存到对应的 config/onebot_<uin>.json。

在 noVNC 桌面里启第二个 QQ

  1. 在 noVNC 桌面里打开终端(任务栏右键 / 用 xterm / 按容器内任意终端快捷键)。

  2. 用独立 HOME 起新 QQ:

    mkdir -p /app/qq-acct2
    HOME=/app/qq-acct2 qq --no-sandbox &
    disown
  3. 一个新 QQ 登录窗口会出现,扫码 / 账密登录另一个号即可。

  4. 登录完成后 SnowLuma 自动注入并按新 UIN 起一个 OneBot 实例。

disown 让 QQ 与终端解绑 —— 关掉终端窗口 QQ 不会跟着退出。

让它持久化

上面那条命令里 /app/qq-acct2 是容器内路径,容器重建会丢。要持久化,在 docker-compose.yml 给每个账号加独立卷:

services:
  snowluma:
    # ... 原有配置 ...
    volumes:
      - snowluma-data:/app/snowluma-data
      - snowluma-qq-config:/app/.config         # 主账号
      - snowluma-qq-data:/app/.local/share      # 主账号
      - snowluma-qq2:/app/qq-acct2              # 第二个账号的完整 HOME
      - snowluma-qq3:/app/qq-acct3              # 第三个账号 ...

volumes:
  snowluma-data:
  snowluma-qq-config:
  snowluma-qq-data:
  snowluma-qq2:
  snowluma-qq3:

之后每次需要拉起第二个号,在 noVNC 终端跑:

HOME=/app/qq-acct2 qq --no-sandbox &
disown

开机自动拉所有账号

把上面的命令写成一个 shell 脚本,放进持久卷 /app/snowluma-data/start-extra-qq.sh:

#!/bin/sh
for home in /app/qq-acct2 /app/qq-acct3; do
  [ -d "$home" ] || continue
  HOME="$home" qq --no-sandbox >/dev/null 2>&1 &
done

然后在 noVNC 桌面进系统的开机自启目录(XFCE 是 ~/.config/autostart/)放一个 desktop 入口指向这个脚本。或者更稳的:把脚本加到 supervisord.conf 作为新的 program。后者需要重建镜像,前者重启容器就生效。

限制 & 注意

  • 每个 QQ 实例独占自己的 HOME —— 别让两个 QQ 共用同一个 HOME,会冲突。
  • 资源:每个 QQ 进程跑 Chromium 大概吃 300-500MB 内存,--shm-size 主账号 1GB 够用,多开建议加到 2gb 或更高。
  • noVNC 同屏可见:多个 QQ 窗口都在同一个 Xvfb 桌面里,VNC 进去可以分别操作。如果只想登一次就走,登完后把 QQ 窗口最小化即可,不会影响 hook。
  • WebUI 自动列出所有 UIN:SnowLuma 不需要单独配置就能识别新登录的账号,WebUI / get_login_info 都会出现新 UIN。

自动注入

镜像默认开 SNOWLUMA_HOOK_AUTOLOAD=1:容器一启动就把 hook 注入 QQ 主进程(被动观察),扫码登录完成后 hook 自动切到工作模式。supervisor 让 QQ 崩溃自动重启时也走同样流程。

关闭自动注入

想保留传统的"WebUI 里手动 Load"流程:

docker run -e SNOWLUMA_HOOK_AUTOLOAD=0 ... motricseven7/snowluma:latest

或在 docker-compose.yml 改 SNOWLUMA_HOOK_AUTOLOAD: 0,或编辑持久卷里的 /app/snowluma-data/config/runtime.json 设 "hookAutoLoad": false。环境变量优先。

常用运维

进容器:

docker exec -it snowluma bash

查日志:

docker logs -f snowluma

只看 SnowLuma 主进程日志(过滤掉 QQ 自己的输出):

docker logs -f snowluma 2>&1 | grep -v "QtNetwork\|GLib\|libQt6"

进入 SnowLuma 数据目录:

docker exec -it snowluma bash -c "cd /app/snowluma-data && ls -la"

修改 OneBot 配置:编辑 /app/snowluma-data/config/onebot_<uin>.json,重启容器:

docker restart snowluma

端口冲突

宿主机端口被占用时,调宿主侧映射即可,容器内端口不变。例:把 OneBot HTTP 改为宿主的 8080:

ports:
  - "8080:3000"   # 宿主 8080 → 容器 3000

或通过环境变量(Compose 模板已支持):

ONEBOT_HTTP_PORT=8080 docker compose up -d

自己构建镜像

如果想用未发布的 SnowLuma 版本(比如本地分支):

# 在 SnowLuma 主仓本地构建出 lite tarball
pnpm build:all
# 把 SnowLuma-<TAG>-linux-x64-lite.tar.gz 放到 Docker 仓根目录并重命名
cp dist/SnowLuma-*-linux-x64-lite.tar.gz \
   ../SnowLuma.Docker.Framework/SnowLuma.Framework.tar.gz

cd ../SnowLuma.Docker.Framework
./scripts/build-image.sh

或者直接让脚本从 release 拉:

SNOWLUMA_TAG=v1.8.0 ./scripts/build-image.sh

多架构 manifest 合并请走 CI(.github/workflows/docker-image.yml);本地脚本只构建单平台。

安全提示

  • SnowLuma 用 native addon 把代码注入 QQ 进程;容器启动需要 SYS_PTRACE 能力和 seccomp=unconfined。仅在受信任的宿主上运行。
  • VNC / noVNC 默认密码 vncpasswd 务必改掉,否则你的 QQ 桌面在公网上裸奔。
  • 请遵守 NTQQ / Tencent 的使用许可,以及 SnowLuma 与依赖项的开源协议。

本页目录