Files
WeChat-AI/docs/docker.md
T

244 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Docker 部署
## 前置条件
| 依赖 | 说明 |
|------|------|
| Docker + Compose | 本机或服务器已安装 |
| Upstash Redis | `.env``REDIS_URL=rediss://...` |
| LINUX DO OAuth | Client ID/Secret + **公网回调地址** |
| 平台 LLM(主站直连) | `LLM_API_KEY` / `LLM_BASE_URL` / `LLM_MODEL` |
| 工具网关(用户自定义 API + 搜索) | `TOOLS_BASE_URL` / `TOOLS_API_KEY`;镜像见 `huggingface/wechat-ai-tools` |
## 快速启动
```bash
cd /path/to/WeChat-AI
# 配置环境变量(勿提交 .env
# 生产务必修改:
# PUBLIC_BASE_URL=https://你的域名
# LINUXDO_REDIRECT_URI=https://你的域名/api/v1/auth/callback
# REDIS_URL / LLM_* / LINUXDO_CLIENT_*
# 用户自定义 API + 搜索:TOOLS_BASE_URL / TOOLS_API_KEY
# docker compose --profile tools up -d --build
# TOOLS_BASE_URL=http://wechat-ai-tools:7860
# 推荐:升版 + 打 OTA 包 + 构建(无需 Cookie
pnpm docker:up
# 自定义镜像名:
pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# 包在 dist/release/<版本>/files.json
# 浏览器登录 /admin → 部署节点 →「上传通道包」→ 再点节点「更新」
docker compose logs -f wechat-ai
```
### 单独构建 tools 镜像
```bash
docker build -t wechat-ai-tools:latest -f huggingface/wechat-ai-tools/Dockerfile huggingface/wechat-ai-tools
docker run --rm -p 7860:7860 -e TOOLS_API_KEY=secret -e ALLOW_REQUEST_UPSTREAM=true wechat-ai-tools:latest
```
详见 `docs/ai-gateway.md``huggingface/wechat-ai-tools/README.md`
> **版本与通道:** `pnpm docker:build` 默认:升版 → **本地 pack** → Docker。
> 通道发布只走网页:`/admin` → 上传 `files.json`(无 CLI Cookie)。
> 直接跑 `docker build` **不会**升版、也**不会**打通道包。
| 地址 | 说明 |
|------|------|
| `/` | 功能介绍落地页(OG 分享图 `/og.jpg` |
| `/app` | 用户中心(LINUX DO 登录、加机器人) |
| `/admin` | 管理后台(仪表盘 / Token) |
| `/health` | 健康检查 |
## 仅用 Dockerfile
```bash
# 升版 + docker build -t wechat-ai .
pnpm docker:build -- --raw
# 或自定义:node scripts/docker-build.mjs -- docker build -t wechat-ai:0.2.1 .
docker run -d --name wechat-ai --restart unless-stopped \
--env-file .env \
-e WECHAT_AI_HOST=0.0.0.0 \
-e WECHAT_AI_PORT=8787 \
-p 8787:8787 \
wechat-ai
```
Bot token 与表情包均存 **Redis**(与 `REDIS_URL` 同库),容器重建不丢;无需本地数据卷。
## 生产环境检查清单
1. **LINUX DO** 应用回调与 `.env` 完全一致:
`https://你的域名/api/v1/auth/callback`
2. **`PUBLIC_BASE_URL`** = `https://你的域名`(无尾斜杠)
3. **HTTPS** 时设置 `COOKIE_SECURE=true`
4. **Redis** 使用 Upstash `rediss://`,服务器能访问外网
5. Bot **token 已写入 Redis**(与 `REDIS_URL` 同库),重建容器不会丢登录
## 常用命令
```bash
docker compose ps
docker compose logs -f
docker compose restart
docker compose down # 停服务(Bot token 在 Redis,不受影响)
docker compose down -v # 同 down(本服务无持久化 volume
pnpm docker:up # 升版+本地 pack+compose up --build
pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# 然后 /admin → 上传通道包 (files.json) → 更新节点
pnpm docker:build -- --no-channel # 只升版构建,不 pack
```
## OTA 增量更新(多节点日常热修)
业务源码小改可不必每台 `docker build`:本地 pack → 管理后台上传通道包 → 对落后节点点「更新」。
```bash
# 构建顺带打通道包
pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# 或仅 pack
pnpm release:pack
# 浏览器 /admin → 部署节点 →「上传通道包」选 dist/release/<ver>/files.json
# →「更新全部落后」
```
| 项 | 说明 |
|----|------|
| 差量 | 按文件 sha256 比对,只下发变更文件 |
| 重启 | 节点 `process.exit(0)`,依赖 `restart: unless-stopped` 拉起**同一容器**(可写层保留补丁) |
| 版本 | 心跳 `version``.wa-version`OTA 写入)→ `APP_VERSION` → 根 `package.json` |
| 仍需镜像 | Node 基础镜像、系统包、Dockerfile、`OTA_ALLOW_INSTALL=false` 时的依赖大变 |
环境变量:`OTA_ENABLED`(默认 true)、`OTA_ALLOW_INSTALL``APP_VERSION`(无 OTA 戳时可选)、`OTA_STAGING_DIR`
**注意:** OTA 只改文件、不改环境变量;版本靠 `/app/.wa-version` 上报,无需、也不应靠 `APP_VERSION` 跟版。
`docker compose up --build` / 重建容器会丢掉仅靠 OTA 写入的补丁;长期仍以镜像为 source of truth。
## 反代示例
生产推荐把域名挂在 **Cloudflare**(橙云代理 + Cache Rules),见 **`docs/cloudflare.md`**Business 缓存规则、忽略 Cookie、Purge 清单)。
### Caddy(无 CF 时)
```caddy
your.domain.com {
reverse_proxy 127.0.0.1:8787
}
```
### Nginx(无 CF 时,或 CF → Nginx → Node
```nginx
server {
listen 443 ssl;
server_name your.domain.com;
# ssl_certificate ...;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cloudflare 还原真实 IP 时可用:
# proxy_set_header CF-Connecting-IP $http_cf_connecting_ip;
}
}
```
源站已输出 `Cache-Control` / `Cloudflare-CDN-Cache-Control`、HTML ETag、公开表情 `/cdn/s/:id?v=`。反代层**不必**再写 `proxy_cache`,除非你不用 Cloudflare。
## 镜像说明
- 基础镜像:`node:22-bookworm-slim`
- 包管理:pnpm monorepo
- 启动:`pnpm db:seed`(幂等)→ `pnpm --filter @wechat-ai/api start`**API + Worker 同进程**
- 非 root 用户 `appuser` 运行
- 健康检查:`GET /health`
## Worker 规模(单镜像)
默认仍是 **一个容器跑全部**HTTP + iLink 长轮询 + AI 回复。
| 环境变量 | 默认 | 说明 |
|----------|------|------|
| `MAX_BOTS_PER_WORKER` | `500` | 本进程最多同时 long-poll 的 bot 数 |
| `REPLY_CONCURRENCY` | `16` | 同时进行的 LLM/发送任务数 |
| `LEASE_TTL_SEC` | `45` | 租约 TTL(同镜像多副本时防重复 poll) |
机器人很多时优先调高 `MAX_BOTS_PER_WORKER` 与系统 `ulimit -n`(注意内存与出站连接数)。
默认 **单副本一体部署** 即可;同镜像多副本已支持(Redis 租约分片 poll)。
## 多节点同构部署(10+ 台)
每台服务器跑**同一镜像**(API + Worker),共用一个 Upstash Redis;用户只访问**主域名**。
### 应用 env(全站一致)
```env
PUBLIC_BASE_URL=https://你的主域名
LINUXDO_REDIRECT_URI=https://你的主域名/api/v1/auth/callback
REDIS_URL=rediss://...
COOKIE_SECURE=true
WORKER_ENABLED=true
```
### 每节点不同
```env
WORKER_ID=node-01 # 必填且唯一
NODE_LABEL=cn-east-1a # 可选,管理后台展示
NODE_REGION=cn-east # 可选
```
**不要**给每台设不同的 `PUBLIC_BASE_URL`。源站直连地址(IP:8787)只写在 Cloudflare Worker 的 `ORIGINS`,见 `cloudflare-worker/README.md`
### 部署步骤摘要
1. 各机:`docker run ... --env-file .env -e WORKER_ID=node-0N -p 8787:8787 wechat-ai`
2. 配置并部署 `cloudflare-worker``ORIGINS=http://ip1:8787,http://ip2:8787,...`
3. 主域名绑到 Worker
4. 打开 `/admin`**节点**:应看到各 `WORKER_ID` 心跳与租约 bot 数
| 探活 | 路径 |
|------|------|
| Docker / 轻量 | `GET /health` |
| LB 就绪(含 Redis | `GET /health/ready` |
管理 API`GET /api/v1/admin/nodes`Cookie 管理员)。
扫码加机器人会话状态在 Redis,HTTP 无需粘性会话。
### 租约自动再平衡(rebalance
多节点默认 **开启**:租约偏多的进程会周期性 **主动释放** 多余 lease(不 pause bot),空闲节点下一轮 `claim` 捡走,使各节点 bot 数接近均分。
| 环境变量 | 默认 | 说明 |
|----------|------|------|
| `REBALANCE_ENABLED` | `true` | 设为 `false` 关闭(租约粘在首占节点) |
| `REBALANCE_INTERVAL_SEC` | `60` | 同一进程两次 shed 最小间隔 |
| `REBALANCE_SLACK` | `2` | 允许高出均分多少个再释放 |
| `REBALANCE_MAX_PER_TICK` | `50` | 每次最多释放数(避免瞬间空窗过大) |
日志关键字:`[worker] rebalance shed N bot(s)`。约 `ceil(超额 / 50)` 分钟内收敛。
### 强制下线节点
管理后台 **节点** 页 → **强制下线**
1. 写入 Redis fence`wa:worker:{id}:fence`
2. 释放该 WORKER_ID 下全部 bot 租约
3.`wa:workers:reg` 移除
目标进程下一轮 reconcile 发现 fence 后停止认领;其他节点 claim 这些 bot。
**解除下线** 后该节点可重新加入。
这**不会** `docker stop`;若要从 LB 摘流量,还要从 Cloudflare Worker `ORIGINS` 去掉该源站。