部署与运维手册
本文档覆盖两种目标环境(Debian/Ubuntu 与 Arch Linux),以及三种部署形态:
| 形态 | 适用场景 | 说明 |
|---|---|---|
| A. 单机全量 | 个人电脑 / 已登录 QQ 的机器 | 归档服务器 + 采集端在同一台机器 |
| B. 服务器 + 采集端分离 | 有 Linux 服务器 | 服务器只存归档、跑网页;采集端在个人电脑 |
| C. 纯离线导入 | 完全不装采集端 | 只用 import 导入导出文件,网页查看 |
重要前提:本系统的采集端不依赖任何第三方 QQ 协议实现的内置代码,只通过 HTTP 调用
OneBot v11 的只读接口。OneBot 实现的安装与登录属于你自己负责的部分,见第 6 节。
1. 系统要求
| 项目 | 要求 |
|---|---|
| 操作系统 | Debian 11+ / Ubuntu 20.04+ / Arch Linux / 其他现代 Linux;macOS 与 Windows 也可运行(Windows 有一键脚本,见 §9.1) |
| 架构 | x86_64 / arm64 均可(无原生编译依赖) |
| Node.js | ≥ 22.5(需要 node:sqlite 与 TypeScript 类型剥离)。推荐 24.x LTS |
| 磁盘 | 归档本身按消息量与媒体量估算;建议至少留出媒体总量的 2 倍(备份需要空间) |
| 内存 | 服务常驻约 80–150 MB;导入大文件时按文件大小预留 |
| 可选 | ffmpeg(视频缩略图/转码)。缺失不影响归档,只是视频没有缩略图 |
安装 Node.js
Debian / Ubuntu:
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v # 应显示 v24.x
Arch Linux:
sudo pacman -S nodejs npm
node -v
可选 ffmpeg:
sudo apt-get install -y ffmpeg # Debian/Ubuntu
sudo pacman -S ffmpeg # Arch
2. 安装归档服务
# 1) 取代码(放在 /opt,属于 root,普通用户不可改)
sudo git clone <你的仓库地址> /opt/qq-archive
cd /opt/qq-archive
# 2) 安装开发依赖(仅 TypeScript 类型检查用;生产运行不需要 node_modules)
sudo npm install --omit=optional
# 若你的环境无法访问 npm,可跳过:运行时不依赖 node_modules
# 3) 建系统用户与目录
sudo useradd --system --home /var/lib/qq-archive --shell /usr/sbin/nologin qqarchive
sudo mkdir -p /var/lib/qq-archive /var/backups/qq-archive /etc/qq-archive
sudo chown -R qqarchive:qqarchive /var/lib/qq-archive /var/backups/qq-archive
sudo chmod 750 /var/lib/qq-archive /var/backups/qq-archive /etc/qq-archive
# 4) 生成配置
sudo cp config.example.json /etc/qq-archive/config.json
sudo sed -i "s|\"dataDir\": \"./data\"|\"dataDir\": \"/var/lib/qq-archive\"|" /etc/qq-archive/config.json
sudo sed -i "s|\"backupDir\": \"\"|\"backupDir\": \"/var/backups/qq-archive\"|" /etc/qq-archive/config.json
# 5) 生成管理令牌(务必替换占位值)
TOKEN=$(openssl rand -base64 32)
sudo sed -i "s|\"token\": \".*\"|\"token\": \"$TOKEN\"|" /etc/qq-archive/config.json
sudo chmod 600 /etc/qq-archive/config.json
sudo chown qqarchive:qqarchive /etc/qq-archive/config.json
# 6) 自检(会用 /etc/qq-archive/config.json)
sudo -u qqarchive QQ_ARCHIVE_CONFIG=/etc/qq-archive/config.json \
node --experimental-strip-types /opt/qq-archive/apps/cli/src/main.ts doctor
创建命令软链(可选,方便调用)
sudo tee /usr/local/bin/qq-archive >/dev/null <<'EOF'
#!/bin/sh
# 归档 CLI 包装脚本:以 qqarchive 身份运行,使用固定配置
exec sudo -u qqarchive QQ_ARCHIVE_CONFIG=/etc/qq-archive/config.json \
/usr/bin/node --experimental-strip-types /opt/qq-archive/apps/cli/src/main.ts "$@"
EOF
sudo chmod +x /usr/local/bin/qq-archive
qq-archive stats
3. systemd 服务
仓库 deploy/ 下已提供两个 unit:
| 文件 | 作用 |
|---|---|
deploy/qq-archive.service |
归档服务器(数据库 + API + 网页 + 可选内建采集) |
deploy/qq-archive-collect.service |
独立采集端(服务器与采集端分离时使用) |
3.1 只部署归档服务器(形态 A / C)
sudo cp deploy/qq-archive.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now qq-archive
systemctl status qq-archive
启用内建采集(形态 A):编辑 /etc/qq-archive/config.json 把 collector.kind 改为 "onebot",
并填好 collector.baseUrl,然后 sudo systemctl restart qq-archive。
3.2 服务器 + 采集端分离(形态 B)
- 服务器:只装
qq-archive.service,collector.kind保持"none"。 - 采集端机器(已登录 QQ 的那台):装代码 + 配置,只启用采集服务:
sudo cp deploy/qq-archive-collect.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now qq-archive-collect
journalctl -u qq-archive-collect -f
采集端写入的数据库在采集端本机。周期性把归档同步到服务器有三种做法:
# 做法 1(推荐):采集端跑服务,服务器从采集端拉取备份
# 采集端:qq-archive backup
# 服务器:rsync 拉回备份目录后再 restore
rsync -av --delete qqarchive@collector-host:/var/backups/qq-archive/ /var/backups/qq-archive/
# 做法 2:把媒体目录单向同步(只增不改)
rsync -av --ignore-existing qqarchive@collector-host:/var/lib/qq-archive/media/ /var/lib/qq-archive/media/
# 做法 3:直接在采集端也跑网页,服务器仅作冷备份
⚠️ 不要用双向同步工具同时写两边数据库。SQLite 文件不能并发多写。
3.3 常用运维命令
systemctl start|stop|restart|status qq-archive
journalctl -u qq-archive -f # 实时日志
journalctl -u qq-archive --since today # 今日日志
qq-archive doctor # 环境自检
qq-archive stats # 归档统计
4. 配置说明
配置文件路径优先级:--config 参数 > 环境变量 QQ_ARCHIVE_CONFIG > ./config.json。
4.1 关键字段
| 字段 | 默认 | 说明 |
|---|---|---|
server.host |
127.0.0.1 |
默认只允许本机访问。改为 0.0.0.0 前请先读第 5 节 |
server.port |
8787 |
监听端口 |
server.token |
自动生成 | 网页登录令牌。至少 32 位随机;非本机监听的校验会拒绝过短令牌 |
server.allowDownload |
true |
关闭后媒体接口只允许预览不允许下载 |
storage.dataDir |
./data |
数据根目录(相对路径以配置文件所在目录为基准) |
storage.backupDir |
<dataDir>/backups |
备份目录,建议放另一块磁盘 |
backup.intervalHours |
24 |
自动备份间隔,0 关闭 |
backup.keep |
7 |
保留份数 |
logs.dbKeepDays |
30 |
数据库日志(网页「日志」页)保留天数,0 = 不清理。启动时清一次 |
filters.enabled |
false |
采集过滤总开关。默认只标记不丢弃:命中的消息照样入库、导出照带,只是默认不显示 |
filters.excludeKeywords |
[] |
命中的关键词(子串、大小写不敏感) |
filters.excludeSenders |
[] |
命中的发送者(QQ 号 / uid / 昵称,精确相等) |
filters.excludeTypes |
[] |
命中的消息类型(如 system、json) |
filters.drop |
false |
true = 命中的消息跳过存储(真的不入库)。改动前请确认这就是你要的 |
alert.diskLowGb |
2 |
磁盘剩余告警阈值 |
collector.kind |
none |
onebot 启用采集 |
collector.baseUrl |
http://127.0.0.1:3000 |
OneBot HTTP 地址 |
collector.token |
空 | OneBot 侧配置的 access token |
collector.syncMedia |
true |
是否下载媒体文件到本地归档 |
4.2 环境变量覆盖(适合放 systemd EnvironmentFile)
所有覆盖都以 QQ_ARCHIVE_ 开头,优先于配置文件:
QQ_ARCHIVE_CONFIG=/etc/qq-archive/config.json
QQ_ARCHIVE_HOST=127.0.0.1
QQ_ARCHIVE_PORT=8787
QQ_ARCHIVE_TOKEN=... # 避免令牌落在配置文件里
QQ_ARCHIVE_DATA_DIR=/var/lib/qq-archive
QQ_ARCHIVE_DB_PATH=/var/lib/qq-archive/archive.db
QQ_ARCHIVE_MEDIA_DIR=/var/lib/qq-archive/media
QQ_ARCHIVE_BACKUP_DIR=/var/backups/qq-archive
QQ_ARCHIVE_COLLECTOR_KIND=onebot
QQ_ARCHIVE_COLLECTOR_URL=http://127.0.0.1:3000
QQ_ARCHIVE_COLLECTOR_TOKEN=...
QQ_ARCHIVE_COLLECTOR_INTERVAL=60
QQ_ARCHIVE_LOG_LEVEL=info
QQ_ARCHIVE_DISK_LOW_GB=5
QQ_ARCHIVE_ALERT_WEBHOOK=https://example.com/hook
QQ_ARCHIVE_TRUST_PROXY=0
密钥放 EnvironmentFile 时:
sudo install -m 600 -o qqarchive -g qqarchive /dev/null /etc/qq-archive/secrets.env
sudo tee /etc/qq-archive/secrets.env >/dev/null <<'EOF'
QQ_ARCHIVE_TOKEN=把强随机令牌写这里
QQ_ARCHIVE_COLLECTOR_TOKEN=OneBot的token
EOF
然后在 unit 里取消注释 EnvironmentFile=-/etc/qq-archive/secrets.env。
5. 安全说明(务必阅读)
默认只听 127.0.0.1。归档里有你的全部聊天记录,不要直接暴露到公网。
需要远程访问时,推荐只在 SSH 隧道里访问(最安全,不需要改配置):
# 在你自己的电脑上执行 ssh -N -L 8787:127.0.0.1:8787 user@server # 然后浏览器打开 http://127.0.0.1:8787/若确实要监听公网地址,必须同时做到:
server.token≥ 32 位随机字符(用openssl rand -base64 32);- 用反向代理(Nginx/Caddy)加 HTTPS,并把
server.trustProxy设为true(否则登录限速按代理 IP 统计); - 用防火墙或代理只放行你自己的 IP;
- 定期看
/api/logs里的auth作用域日志。
令牌与 OneBot token 不要写进源码、不要提交到 git。
config.json权限600。数据库、媒体文件含隐私内容,备份目录同样要
chmod 700。日志已做脱敏(token/密钥/base64 会被替换为
<redacted>),但仍不要公开journalctl输出。媒体接口只从数据库读取文件相对路径,并对结果做前缀校验,不接受任何用户提供的文件路径。
6. 采集端(OneBot 实现)运行环境限制
采集端本身不包含任何 QQ 协议实现。它需要你在旁边部署一个提供 OneBot v11 HTTP 接口的实现,
常见选择:
| 实现 | 许可证 | 需要 QQ 客户端 | Linux 无头 | 备注 |
|---|---|---|---|---|
| NapCatQQ | 自定义许可(禁商用/禁发布修改版,非 OSI 开源) | 是 | 支持 | 本项目实际对接验证的目标 |
| LLOneBot (LuckyLilliaBot) | GPL-2.0 | 是(LiteLoaderQQNT 插件) | 较弱 | 支持 OneBot11 |
| Lagrange.Core | 无 LICENSE 文件(默认保留所有权利) | 否(纯协议实现) | 支持 | 封号风险更高,不建议主账号 |
必须知道的技术限制(有据可查,详见 docs/qq-export-projects-research.md):
- QQ 服务端的历史记录无法被完整获取。OneBot 的历史消息接口读的是客户端本地/会话视图,
NapCat 维护者在 Issue #1838 明确表示:
机器人离线期间的消息不会被补齐,且这是设计行为(避免大量消息涌入)。 - NapCat 没有数据库,消息缓存是 LRU,约 5000 条后被清理;已撤回消息无法再取回。
- 手机端未同步到电脑端的消息,没有任何 API 能拿到。要拿只能走设备端解密导出
(需 root/Frida),风险与门槛都高,本项目不提供这条路。 - 上游导出工具 QCE 的文档原文:「任何响应都不能证明腾讯服务端的聊天记录完整性」。
结论:本系统的价值在于「一旦消息到达过采集端,就再也不会丢」,而不是「把 QQ 服务端的历史都捞回来」。
所以采集端应当长期挂着(systemd 自动重启 + 开机自启),离线期间的消息窗口越短越好。
6.1 扫码登录(无头服务器上怎么让 NapCat 登录)
采集端需要先登录 QQ 才能拉消息。在无图形界面的服务器上,你有两种方式让 NapCat 登录,
两种都走 NapCat WebUI 的 REST 接口(与采集用的 OneBot 接口是两套东西、两个端口):
- 本系统网页里扫:
qq-archive serve→ 浏览器进「QQ 登录」页 → 手机 QQ 扫码。 - 终端里扫:
qq-archive login把二维码用色块直接画在终端里,手机对着终端屏幕扫。
配置(config.json 的 collector.napcat):
"collector": {
"kind": "onebot",
"baseUrl": "http://127.0.0.1:3000",
"token": "OneBot 的 access token",
"napcat": {
"webuiUrl": "http://127.0.0.1:6099", // NapCat WebUI 地址(会在 6099 之后自增端口)
"token": "NapCat WebUI 的 token", // 取自 NapCat 的 config/webui.json,**不是**上面那个 OneBot token
"probeRange": 20
}
}
常见退出码与处置:3 = 连不上 WebUI 或 token 不对(检查 NapCat 是否在跑、config/webui.json 里的 token);2 = 本系统配置/参数问题(collector.napcat.token 未填、--uin 非法)。
安全提醒:二维码 URL 本身就是一次性的登录凭据。本系统只在内存中流转它——网页端只下发
PNG(data: URL),终端只画色块;不会写进 JSON 响应、日志或文件。反过来说,不要把
NapCat WebUI 的端口暴露到公网(它自带 token 鉴权,但仍是本机管理面)。
采集端安全建议
- 用一个专门的小号或至少在 NapCat 官方建议的隔离环境里跑,不要与你的常用账号共用设备/IP。
- 只使用本项目的只读接口(
get_*),不要把 OneBot 的 HTTP 端口暴露到公网。 - 采集端配置里只填
baseUrl与token,不要填 QQ 密码。
7. 备份
7.1 手动备份
qq-archive backup # 含完整性校验
qq-archive backup --no-verify # 大库赶时间时跳过校验
qq-archive backup --label 升级前
备份产物结构:
/var/backups/qq-archive/2026-10-13T09-30-00-000Z/
├── archive.db # SQLite 一致性快照(VACUUM INTO,不是文件拷贝)
├── manifest.json # 消息/媒体计数、每个文件的 sha256、schema 版本、时间
├── media/ # 媒体文件(按原目录结构)
└── RESTORE.md # 恢复步骤速查
7.2 自动备份
在配置里设 backup.intervalHours(默认 24)。服务运行期间会自动执行,
超过 backup.keep 份后删除最旧的。
7.3 恢复
永远先 dry-run:
# 1) 演练:只校验清单、sha256、计数,不修改任何数据
qq-archive restore /var/backups/qq-archive/2026-10-13T09-30-00-000Z
# 2) 确认无误后真正恢复(会先把当前库另存为 archive.db.bak-<时间戳>)
sudo systemctl stop qq-archive
qq-archive restore /var/backups/qq-archive/2026-10-13T09-30-00-000Z --apply
sudo systemctl start qq-archive
qq-archive stats # 核对计数
恢复媒体(如果换了机器):
rsync -av /var/backups/qq-archive/2026-.../media/ /var/lib/qq-archive/media/
7.4 备份完整性检查
# 校验数据库与媒体清单
qq-archive restore <备份目录> # dry-run 会做 sqlite quick_check + 逐文件 sha256
# 校验当前正在使用的媒体文件是否与数据库记录一致
qq-archive verify
# 改了 filters 规则之后,对**已有消息**重算过滤标记(只改标记,不删消息)
qq-archive refilter
7.4 采集过滤怎么用
filters 段默认关闭,关闭时行为与没有这个功能完全一样。开启后按三类规则给消息打标记:
"filters": {
"enabled": true,
"excludeKeywords": ["广告", "加群"],
"excludeSenders": ["123456789"],
"excludeTypes": ["system"],
"drop": false
}
- 默认
drop: false= 只标记不丢弃:命中的消息照样入库、qq-archive refilter与导出都照带,
只是网页时间线和搜索默认不显示;勾选「包含已过滤消息」即可看到,消息上会显示命中的规则。 drop: true才会让之后采集到的命中消息跳过存储(已入库的不会被删除)。- 用
qq-archive refilter在改规则后重算已有消息的标记;它是幂等且可逆的
(把enabled关掉再跑一次即清空标记)。命令只改两列标记,从不删除任何消息。
提醒:这是防丢失系统。除非你确定”这些消息连存档都不要”,否则保持
drop: false。
8. 升级
cd /opt/qq-archive
qq-archive backup --label "升级前-$(date +%F)" # 先备份
sudo systemctl stop qq-archive
sudo git pull
sudo npm install --omit=optional # 仅在需要类型检查时
sudo systemctl start qq-archive
qq-archive doctor # 数据库迁移会自动执行
journalctl -u qq-archive -n 50
数据库迁移是自动的:启动时对比 schema_migrations,只执行未应用的版本,每个版本在事务里完成。
迁移只做向后兼容的新增(加表/加索引/加可空列),不会删除你的数据。
回滚
sudo systemctl stop qq-archive
sudo git checkout <上一个正常版本>
qq-archive restore <升级前的备份> --apply # 若新版本已改动 schema
sudo systemctl start qq-archive
9. Windows 与 macOS
系统本身跨平台(只用 Node 内置模块)。区别只是进程守护方式。
9.1 Windows:仓库自带脚本(推荐)
deploy/windows/ 下有一整套 PowerShell 脚本,不依赖 NSSM,默认以当前用户身份注册计划任务(不需要管理员):
| 文件 | 作用 |
|---|---|
install.ps1 |
幂等安装器:检查 Node、建目录、生成配置、注册两个计划任务 |
uninstall.ps1 |
移除计划任务;默认保留数据,-PurgeData 才删 |
status.ps1 |
只读状态:任务状态 / 上次运行结果 / 日志尾部 / 配置与目录 |
run-collect.ps1 |
计划任务调用:跑一轮 collect --once,带日志与轮转 |
run-serve.ps1 |
计划任务调用:常驻 serve(网页 + API),带日志与轮转 |
check-syntax.mjs |
纯 Node 静态自检器(本机没有 pwsh 时唯一能跑的门禁) |
# 普通权限即可
powershell -ExecutionPolicy Bypass -File .\deploy\windows\install.ps1
.\deploy\windows\status.ps1 # 看任务状态与上次运行结果
.\deploy\windows\uninstall.ps1 -PurgeData # 连数据一起删
默认注册两个任务:QQArchive-Collect(每 15 分钟跑一次 collect --once)与QQArchive-Serve(登录后 30 秒启动常驻 serve)。参数详见deploy/windows/README.md。
诚实声明(重要):本项目的开发与测试环境是 Linux,没有
pwsh/powershell,
因此这些脚本从未在真实 Windows 上执行过。已做的是node deploy/windows/check-syntax.mjs
静态自检 —— 它检查param(声明、参数被引用、$PSScriptRoot推导、ExecutionPolicy
用法、日志轮转、只读status等 80 项,全部通过。静态检查不等于能在真实 PowerShell 上跑通,
首次部署请按deploy/windows/README.md§4.2 的手工验收清单逐条确认。
手工方式(不想用脚本时):用「任务计划程序」创建开机启动任务,程序填 node,参数填--experimental-strip-types <路径>\apps\cli\src\main.ts serve,起始位置填项目根目录,
并设置「失败后重启」。
9.2 macOS
写一个 launchd plist(~/Library/LaunchAgents/com.qqarchive.plist),ProgramArguments 同上,KeepAlive=true,RunAtLoad=true。
两种系统都建议把 server.host 保持 127.0.0.1。
10. 故障排查
| 症状 | 排查 |
|---|---|
| 服务起不来 | qq-archive doctor;journalctl -u qq-archive -n 100。配置错误会直接在启动时打印原因并退出(退出码 2) |
| 网页打不开 | 确认监听地址:默认只有本机可访问。远程请用 SSH 隧道 |
| 忘记令牌 | qq-archive token |
| 采集一直失败 | qq-archive doctor 看「采集端可用」一项;确认 OneBot HTTP 服务在 collector.baseUrl 可达;401 说明 collector.token 不对 |
| 导入报格式未知 | 用 `–format qce-json |
| 媒体显示「缺失」 | 源文件在导出时就已经不存在(QQ 缓存被清理)。系统不会伪造文件,只会标记。可在源机器上重新导出后再次 import(幂等,不会重复计数) |
| 磁盘告警 | 清理旧备份(backup.keep)、qq-archive verify 后清理损坏媒体,或迁移到更大磁盘 |
| 中文搜不到 | 确认搜的是 ≥2 个汉字(单字走字面匹配);关键字命中在 /api/search 有 <mark> 片段 |








