nex_docus/docs/deploy/README.md

178 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 部署指南(Docker Compose)
> 本地开发请用 [`./scripts/start.sh`](../quickstart.md),不要用本文流程。
> 本文所有命令都在**仓库根目录**执行;部署脚本已迁到 `scripts/`,写法固定为 `./scripts/deploy.sh …`。
## 1. 运行拓扑
`docker-compose.yml` 定义 4 个服务,容器名前缀 `nex-docus-`,基础镜像走华为云 SWR 镜像站(国内直连可用):
| 服务 | 容器 | 端口(宿主机→容器) | 说明 |
| --- | --- | --- | --- |
| `mysql` | `nex-docus-mysql` | `${MYSQL_PORT:-3306}` → 3306 | MySQL 8.0,`utf8mb4` / `utf8mb4_unicode_ci`,数据在 `${STORAGE_PATH}/mysql` |
| `redis` | `nex-docus-redis` | `${REDIS_PORT:-6379}` → 6379 | Redis 7,`requirepass` + AOF,数据在 `${STORAGE_PATH}/redis` |
| `backend` | `nex-docus-backend` | `${BACKEND_PORT:-8000}` → 8000 | 由 `backend/Dockerfile` 构建;启动命令 `python scripts/init_db.py && uvicorn main:app …`,即**建表/迁移/种子全自动** |
| `frontend` | `nex-docus-frontend` | `${FRONTEND_PORT:-8080}` → 80 | 由 `frontend/Dockerfile` 构建(node 构建 + nginx 托管),nginx 反代 `/api/`、`/mcp` 到后端 |
- 网络:`nex-docus-network`;后端在容器内的上游主机名是 `backend`,前端 nginx 依赖该名字。
- **文件存储**:宿主机 `${STORAGE_PATH}` 挂到后端 `/data/nex_docus_store`(后端配置里的 `STORAGE_ROOT`)。文档正文、向量索引、搜索索引、PDF 缓存都在这里。
- ⚠️ compose 里的相对路径按**执行 compose 的当前目录**解析,建议 `.env` 中把 `STORAGE_PATH` 写成绝对路径(如 `/data/nexdocus/storage`),避免换目录执行时数据"凭空消失"。
## 2. 前置要求
- Docker 20.10+,Docker Compose v2(`docker compose`;脚本也兼容老 `docker-compose`)
- 磁盘 ≥ 15 GB 空闲(后端镜像要装 PyTorch / weasyprint / zvec,镜像本身 + 向量索引 + 文档存储增长都吃磁盘)
- 内存 ≥ 4 GB(若启用本地 Embedding `sentence-transformers`,建议 8 GB)
- 出站网络可达所选 LLM / Embedding 服务
## 3. 配置 `.env`
```bash
cp .env.example .env
$EDITOR .env
```
| 键 | 默认值 | 说明 |
| --- | --- | --- |
| `MYSQL_ROOT_PASSWORD` | `root_password_change_me` | **必改** |
| `DB_NAME` / `DB_USER` / `DB_PASSWORD` | `nex_docus` / `nexdocus` / `password_change_me` | 应用库与账号;**必改密码** |
| `MYSQL_PORT` | `3306` | 宿主机端口;不需要外部访问时建议改成 `127.0.0.1:3306` 或干脆不映射 |
| `REDIS_PASSWORD` / `REDIS_PORT` / `REDIS_DB` | `redis_password_change_me` / `6379` / `8` | **必改密码** |
| `SECRET_KEY` | 占位串 | **必改**:`openssl rand -hex 32`。改了会让已登录用户全部失效 |
| `DEBUG` | `false` | 保持 false(true 会打印全量 SQL) |
| `BACKEND_PORT` / `FRONTEND_PORT` | `8000` / `8080` | 对外端口;用户访问的是 `FRONTEND_PORT` |
| `STORAGE_PATH` | `./storage` | 持久化根目录,**建议绝对路径**;MySQL/Redis 数据也放这里 |
| `CHUNK_SIZE` / `CHUNK_OVERLAP` | `800` / `150` | RAG 分块策略,改动后需重新向量化才生效 |
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL` / `ADMIN_NICKNAME` | `admin` / `Admin@123456` / `admin@example.com` / `系统管理员` | **只在第一次建库时生效**,账号已存在则不会改密码 |
| `DEFAULT_USER_PASSWORD` | `User@123456` | 管理员在「系统管理 → 用户管理」新建用户时的初始密码;**建议改随机值**,否则后端启动会打 `[安全自检]` 告警 |
| `DISABLE_SSL_VERIFY` | `false` | 仅自签名证书的内部模型服务才开 |
| `ZVEC_DATA_DIR` | 空 | 留空即 `${STORAGE_ROOT}/vector_index` |
| `ZVEC_EMBEDDING_DIM` | `1536` | 必须与选定的 embedding 模型维度一致,换模型要重建索引 |
> 前端**没有** API 地址配置项:容器内 nginx 固定把 `/api/` 反代到 `backend:8000`。跨域直连后端需自行改 `frontend/nginx.conf`。
> 这套 `.env`(部署用)与 `backend/.env`(开发用,键名不同)是两套配置,容器里只认前者。
## 4. 首次部署
```bash
./scripts/deploy.sh init
```
依次执行:`.env` 检查(没有就从 `.env.example` 生成并中止,等你填)→ Docker 检查 → `pull` → `build --no-cache` → 起 mysql/redis → 等 15s → `run --rm backend python scripts/init_db.py` → `up -d` → 打印访问信息。
等价的手工流程:
```bash
docker compose pull
docker compose build
docker compose up -d mysql redis
until docker compose exec -T mysql mysqladmin ping -hlocalhost -uroot -p"$MYSQL_ROOT_PASSWORD" --silent; do sleep 2; done
docker compose run --rm backend python scripts/init_db.py
docker compose up -d
docker compose ps
```
验证:
```bash
curl -fsS http://127.0.0.1:8000/health # {"status":"healthy"}
docker compose ps # 4 个服务 healthy/running
open http://<服务器IP>:8080 # 登录页;默认 admin / .env 里的 ADMIN_PASSWORD
```
## 5. 日常运维
| 命令 | 作用 |
| --- | --- |
| `./scripts/deploy.sh start` / `stop` / `restart` | 起停服务 |
| `./scripts/deploy.sh status` | 容器状态 |
| `./scripts/deploy.sh logs` | 跟踪全部日志 |
| `./scripts/deploy.sh logs backend` | 跟踪单个服务(`mysql` / `redis` / `backend` / `frontend`) |
| `./scripts/deploy.sh upgrade` | 升级:交互确认 → `git pull` → 停前后端 → `build --no-cache` → 跑 `init_db.py`(自动补列/种子) → `up -d` → `docker image prune -f` |
| `./scripts/deploy.sh backup` | `mysqldump` 应用库到 `./backups/nex_docus_<时间戳>.sql` |
| `./scripts/deploy.sh restore <文件>` | 覆盖式恢复数据库(需输入 `yes`) |
| `./scripts/deploy.sh uninstall` | 删除容器/网络/本地构建镜像;**不会**删除 `STORAGE_PATH` 数据 |
> `upgrade` 会执行 `git pull`:如果服务器上有本地改动或未切换分支,请先自行处理。
## 6. 备份与恢复
文档正文在文件系统、权限在数据库,**两者必须一起备份**,单独还原任何一侧都不算恢复。
```bash
# 备份
./scripts/deploy.sh backup # 数据库
tar czf nexdocus_storage_$(date +%F).tar.gz -C "$STORAGE_PATH" . # 文件 + 索引 + MySQL/Redis 数据
# 恢复(先起 mysql/redis,再灌数据,最后起应用)
docker compose up -d mysql redis
./scripts/deploy.sh restore backups/nex_docus_20260930_120000.sql
docker compose up -d backend frontend
```
迁移到新机器:拷贝 `storage` 目录 + SQL 备份 + `.env`,新机 `STORAGE_PATH` 指到新目录,恢复 SQL 后直接 `up -d`。项目的磁盘目录名是 `projects.storage_key`(UUID),与项目名解耦,可以整目录搬迁。
定期备份建议交给系统 `cron` 或备份软件,注意 `backups/` 也在 `.gitignore` 内,别提交到仓库。
## 7. 反向代理与 HTTPS
前端容器只监听 80。生产建议前置一层网关做 TLS 与域名:
```nginx
server {
listen 443 ssl http2;
server_name docs.example.com;
ssl_certificate /etc/ssl/fullchain.pem;
ssl_certificate_key /etc/ssl/privkey.pem;
client_max_body_size 100M; # 附件上传
location / {
proxy_pass http://127.0.0.1:8080;
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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_kind;
proxy_set_header Connection "upgrade";
}
}
```
要点:
- 前端容器内的 nginx 已对 `/api/v1/chat/send/stream`(SSE)与 `/mcp` 关闭缓冲并放大超时;**外层网关若另有 location 命中这两条路径,也要关闭 `proxy_buffering`**,否则 AI 对话会"卡住不出字"。
- `map $http_upgrade $http_kind { default upgrade; '' close; }` 放在 `http {}` 中(示例已简化)。
- 上传大小限制要逐层一致(示例 100M)。
## 8. 上线前安全清单
- [ ] `MYSQL_ROOT_PASSWORD` / `DB_PASSWORD` / `REDIS_PASSWORD` / `SECRET_KEY` 全部改掉默认值
- [ ] `ADMIN_PASSWORD` / `DEFAULT_USER_PASSWORD` 改为随机值;启动后 `docker compose logs backend | grep 安全自检` **应无输出**(有输出说明仍在用仓库默认值)
- [ ] 首次登录后修改 `admin` 密码;删除或禁用演示账号
- [ ] MySQL / Redis 端口不对公网开放(安全组或改绑 `127.0.0.1`)
- [ ] `DEBUG=false`
- [ ] TLS 前置,HTTP 跳转 HTTPS
- [ ] `STORAGE_PATH` 与数据库备份纳入例行备份(数据库里含 Git Token、模型 API Key、分享密码明文)
- [ ] 磁盘监控:Redis 落盘失败会让整站变只读(见下)
## 9. 常见问题
| 现象 | 原因 / 处理 |
| --- | --- |
| 前端 502 / 一直转圈 | 后端还没通过 `/health`(首次 `init_db.py` 需要时间)。`./scripts/deploy.sh logs backend` 观察 |
| `backend` 反复重启,日志 `MISCONF ... RDB snapshot` | Redis 服务端落盘失败并拒绝写入 → **磁盘满或目录权限问题**。清磁盘后 `redis-cli CONFIG SET stop-writes-on-bgsave-error no` 只是临时解封,重启 Redis 会复原,必须根治磁盘 |
| `Access denied for user` | `.env` 与实际库账号不一致;改过 `DB_PASSWORD` 后已初始化的 MySQL 不会自动同步,需要进容器 `ALTER USER` 或删库重建 |
| 换机器后项目列表空了 | `STORAGE_PATH` 用了相对路径,数据实际写在别的目录 |
| 改了 `ADMIN_PASSWORD` 但登录仍是旧密码 | 管理员已存在,`init_db.py` 不会重置密码;在「系统管理 → 用户管理」里改 |
| AI 对话无引用 / 报维度不匹配 | Embedding 模型或 `ZVEC_EMBEDDING_DIM` 变过 → 重新向量化项目(项目页触发全量) |
| PDF 导出中文乱码 | 后端镜像已内置 `fonts-wqy-microhei`;若自定义基础镜像请自带中文字体 |
| `docker compose` 提示 `version` 已废弃 | 已从 `docker-compose.yml` 移除该字段;若用老 `docker-compose` v1,建议升级到 Compose v2 |
## 10. 本版验证边界
- ✅ 已验证:`./scripts/deploy.sh` 的参数路由与脚本语法、Compose 文件解析、后端镜像构建步骤文本与启动命令。
- ❌ **未在本轮做端到端 `docker compose up` 实测**(缺少可用 Docker 环境)。首个正式部署请把它当作演练:`init → status → /health → 登录 → 建项目 → 上传 → AI 问答 → 备份`,并把结果登记到 [sdd/releases/v1.0.0.md](../sdd/releases/v1.0.0.md) 的验证边界。
- ⚠️ `frontend/Dockerfile` 目前 `rm -rf package-lock.json && npm install`(历史原因是 Alpine 下 rollup 可选依赖问题),构建**不保证可重现**;确认网络环境稳定后建议改回 `npm ci`。