fix: restore docs/docker.md utf-8 encoding and keep image placeholder

This commit is contained in:
SMNET Studio
2026-08-10 18:22:21 +08:00
parent e3f5fff36d
commit e655908995
+143 -106
View File
@@ -1,60 +1,68 @@
# Docker ?¨ç½²
# 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` |
| 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
# 配置环境变量(勿提交 .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
# 用户自定义 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 .
# 推荐:升版 + 打 OTA 包 + 构建(无需 Cookie
pnpm docker:up
# 自定义镜像名:
pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# 包在 dist/release/<版本>/files.json
# 浏览器登录 /admin → 部署节点 →「上传通道包」→ 再点节点「更新」
# ?…在 dist/release/<?ˆæœ¬>/files.json
# æµè??¨ç™»å½?/admin ???¨ç½²?‚点 ?’「ä?传通é??…ã€â? ?点?‚点?Œæ›´?°ã€?
docker compose logs -f wechat-ai
```
### ?•独?„建 tools ?œå?
### 单独构建 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`??
详见 `docs/ai-gateway.md``huggingface/wechat-ai-tools/README.md`
> **?ˆæœ¬ä¸Žé€šé?ï¼?* `pnpm docker:build` 默认:å?????**?¬åœ° pack** ??Docker??
> ?šé??‘å??ªèµ°ç½‘页:`/admin` ??上ä? `files.json`(æ? CLI Cookie)ã€?
> ?´æŽ¥è·?`docker build` **ä¸ä?**?‡ç??ä?**ä¸ä?**?“通é??…ã€?
| ?°å? | 说æ? |
> **版本与通道:** `pnpm docker:build` 默认:升版 → **本地 pack** → Docker。
> 通道发布只走网页:`/admin` → 上传 `files.json`(无 CLI Cookie)。
> 直接跑 `docker build` **不会**升版、也**不会**打通道包。
| 地址 | 说明 |
|------|------|
| `/` | ?Ÿèƒ½ä»‹ç??½åœ°é¡µï?OG ?†äº«??`/og.jpg`ï¼?|
| `/app` | ?¨æˆ·ä¸­å?(LINUX DO ?»å??å??ºå™¨äººï? |
| `/admin` | 管ç??Žå°ï¼ˆä»ªè¡¨ç? / Tokenï¼?|
| `/health` | ?¥åº·æ£€??|
| `/` | 功能介绍落地页(OG 分享图 `/og.jpg` |
| `/app` | 用户中心(LINUX DO 登录、加机器人) |
| `/admin` | 管理后台(仪表盘 / Token |
| `/health` | 健康检查 |
## 仅用 Dockerfile
```bash
# ?‡ç? + docker build -t wechat-ai .
# 升版 + docker build -t wechat-ai .
pnpm docker:build -- --raw
# ?–自定ä?:node scripts/docker-build.mjs -- docker build -t wechat-ai:0.2.1 .
# 或自定义: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 \
@@ -64,49 +72,61 @@ docker run -d --name wechat-ai --restart unless-stopped \
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` ?Œå?)ï??建容器ä¸ä?丢登å½?
## 常用?½ä»¤
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
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
# 然后 /admin → 上传通道包 (files.json) → 更新节点
pnpm docker:build -- --no-channel # 只升版构建,不 pack
```
## OTA 增é??´æ–°ï¼ˆå??‚点?¥å¸¸?­ä¿®ï¼?
业务æºç?å°æ”¹?¯ä?å¿…æ???`docker build`:本??pack ??管ç??Žå°ä¸Šä??šé?????对è½?Žè??¹ç‚¹?Œæ›´?°ã€ã€?
## OTA 增量更新(多节点日常热修)
业务源码小改可不必每台 `docker build`:本地 pack → 管理后台上传通道包 → 对落后节点点「更新」。
```bash
# ?„建顺带?“通é???pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# ?–ä? packï¼?pnpm release:pack
# 构建顺带打通道包
pnpm docker:build -- -- docker build -t your-dockerhub-user/wechat-ai:latest .
# 或仅 pack
pnpm release:pack
# æµè???/admin ???¨ç½²?‚点 ?’「ä?传通é??…ã€é€?dist/release/<ver>/files.json
# ?’「更?°å…¨?¨è½?Žã€?```
# 浏览器 /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` ?¶ç?ä¾è?大å? |
| 差量 | 按文件 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??
## ?代示ä?
环境变量:`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 ?¶ï?
## 反代示例
生产推荐把域名挂在 **Cloudflare**(橙云代理 + Cache Rules),见 **`docs/cloudflare.md`**Business 缓存规则、忽略 Cookie、Purge 清单)。
### Caddy(无 CF 时)
```caddy
your.domain.com {
@@ -114,7 +134,8 @@ your.domain.com {
}
```
### Nginx(æ? CF ?¶ï???CF ??Nginx ??Nodeï¼?
### Nginx(无 CF 时,或 CF Nginx Node
```nginx
server {
listen 443 ssl;
@@ -128,80 +149,96 @@ server {
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 ?¶å¯?¨ï?
# 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??## ?œå?说æ?
源站已输出 `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`
- 基础镜像:`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 ?žå???
| ?¯å??˜é? | 默认 | 说æ? |
## 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` | `500` | 本进程最多同时 long-poll bot |
| `REPLY_CONCURRENCY` | `16` | 同时进行的 LLM/发送任务数 |
| `LEASE_TTL_SEC` | `45` | 租约 TTL(同镜像多副本时防重复 poll |
?ºå™¨äººå?多时优å?è°ƒé? `MAX_BOTS_PER_WORKER` 与系ç»?`ulimit -n`(注?å?å­˜ä??ºç?连接?°ï???
默认 **?•副?¬ä?体部ç½?* ?³å¯ï¼›å??œå?多副?¬å·²?¯æ?(Redis 租约?†ç? poll)ã€?
## 多è??¹å??„部署ï?10+ ?°ï?
机器人很多时优先调高 `MAX_BOTS_PER_WORKER` 与系统 `ulimit -n`(注意内存与出站连接数)。
默认 **单副本一体部署** 即可;同镜像多副本已支持(Redis 租约分片 poll)。
æ¯å°?务?¨è?**?Œä??œå?**(API + Worker)ï??±ç”¨ä¸€ä¸?Upstash Redis;用?·åªè®¿é—®**主å???*??
### 应用 env(全站ä??´ï?
## 多节点同构部署(10+ 台)
每台服务器跑**同一镜像**(API + Worker),共用一个 Upstash Redis;用户只访问**主域名**。
### 应用 env(全站一致)
```env
PUBLIC_BASE_URL=https://ä½ ç?主å???LINUXDO_REDIRECT_URI=https://ä½ ç?主å???api/v1/auth/callback
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 # ?¯é€?```
NODE_LABEL=cn-east-1a # 可选,管理后台展示
NODE_REGION=cn-east # 可选
```
**ä¸è?**ç»™æ??°è®¾ä¸å???`PUBLIC_BASE_URL`?‚æ?站直连地?€ï¼ˆIP:8787)åª?™åœ¨ Cloudflare Worker ??`ORIGINS`,è? `cloudflare-worker/README.md`??
### ?¨ç½²æ­¥éª¤?˜è?
**不要**给每台设不同的 `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 ??
### 部署步骤摘要
| ?¢æ´» | è·¯å? |
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` |
| Docker / 轻量 | `GET /health` |
| LB 就绪(含 Redis | `GET /health/ready` |
管ç? API:`GET /api/v1/admin/nodes`(Cookie 管ç??˜ï???
?«ç?? æœº?¨äººä¼šè??¶æ€åœ¨ Redis,HTTP ? é?粘性ä?è¯ã€?
### 租约?ªåЍ?平衡ï?rebalanceï¼?
多è??¹é?è®?**å¼€??*:ç?约å?多ç?è¿›ç?会周?Ÿæ€?**主动?Šæ”¾** 多ä? lease(ä? pause bot)ï?空闲?‚点下ä?è½?`claim` ?¡èµ°ï¼Œä½¿?„è???bot ?°æŽ¥è¿‘å??†ã€?
| ?¯å??˜é? | 默认 | 说æ? |
管理 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` | æ¯æ¬¡?€å¤šé??¾æ•°ï¼ˆé¿?瞬?´ç©ºçª—è?大ï? |
| `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)` ?†é??…æ”¶?›ã€?
### 强制下线?‚点
日志关键字:`[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` ?»æ?该æ?ç«™ã€
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` 去掉该源站。