From e6559089953668459fbbdee4325a56829993fb26 Mon Sep 17 00:00:00 2001 From: SMNET Studio Date: Mon, 10 Aug 2026 18:22:21 +0800 Subject: [PATCH] fix: restore docs/docker.md utf-8 encoding and keep image placeholder --- docs/docker.md | 249 ++++++++++++++++++++++++++++--------------------- 1 file changed, 143 insertions(+), 106 deletions(-) diff --git a/docs/docker.md b/docs/docker.md index 2eecb28..135fcb5 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -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//files.json -# ?「更?全?落??``` +# 浏览器 /admin → 部署节点 →「上传通道包」选 dist/release//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` ??该?站 \ No newline at end of file +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` 去掉该源站。 \ No newline at end of file