diff --git a/frontend-web/.env.development b/frontend-web/.env.development index 6228c720..c15a7c7b 100644 --- a/frontend-web/.env.development +++ b/frontend-web/.env.development @@ -2,8 +2,10 @@ # 个人覆盖请新建 .env.development.local(已 gitignore,优先级更高) # Gateway / DeerFlow API -VITE_BACKEND_BASE_URL=http://127.0.0.1:8001 -VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api +# VITE_BACKEND_BASE_URL=http://127.0.0.1:8001 +# VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api +VITE_BACKEND_BASE_URL=http://47.88.25.99 +VITE_LANGGRAPH_BASE_URL=http://47.88.25.99/api # 轻应用「新建笔记」系列接口 VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000 diff --git a/frontend-web/.env.example b/frontend-web/.env.example index adb14fb8..80a2b0f7 100644 --- a/frontend-web/.env.example +++ b/frontend-web/.env.example @@ -5,13 +5,14 @@ # ├─────────────────┼──────────────────────────────────────────────────┤ # │ pnpm dev │ .env.development + .env.development.local │ # │ pnpm build │ .env.production + .env.production.local │ +# │ pnpm build:network │ .env.network + .env.network.local(内网地址) │ # │ scripts/start-* │ 脚本注入的环境变量会覆盖上述文件(见下方说明) │ # └─────────────────┴──────────────────────────────────────────────────┘ # # 推荐: # - 日常本地开发:改 .env.development(已提交,团队默认 UAT 地址) # - 本机连 127.0.0.1:8001:复制 .env.development.local.example → .env.development.local -# - 内网打包部署:改 .env.production 后再执行 pnpm build +# - 内网打包部署:改 .env.network 后执行 pnpm build:network(或改 .env.production 后 pnpm build) # # 若浏览器直连后端报 CORS,请在 Gateway 配置 CORS_ORIGINS,包含前端源站,例如: # CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:5174 diff --git a/frontend-web/.env.network b/frontend-web/.env.network new file mode 100644 index 00000000..f8962ea6 --- /dev/null +++ b/frontend-web/.env.network @@ -0,0 +1,30 @@ +# 内网 / 局域网生产打包(npm run build:network 或 pnpm build:network) +# 打包前请将 192.168.1.100 改为实际服务器 IP;变量会写入 dist,运行时不可再改 + +# Gateway / DeerFlow API(与 offline-backend DEPLOY 文档一致) +VITE_BACKEND_BASE_URL=http://47.88.25.99 +VITE_LANGGRAPH_BASE_URL=http://47.88.25.99/api + +# 轻应用「新建笔记」系列接口 +VITE_NOTE_API_BASE_URL=http://192.168.1.100:8000 +# CMZS 后端接口 +VITE_API_BASE_URL=http://192.168.1.100/zncm +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=http://192.168.1.100/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=http://192.168.1.100/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=http://192.168.1.100/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=http://192.168.1.100/knowlibrary +VITE_AIPI_B1COH_URL=http://192.168.1.100/web +VITE_AIPI_B2ACTOR_URL=http://192.168.1.100 +VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 +VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=http://192.168.1.100/knowes/api/iframeKnowledgeUrl +VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red +VITE_AIPI_MOPECHAT_URL=http://192.168.1.100/likun_b1_mope +VITE_AIPI_BACKENP_URL=http://192.168.1.100 +VITE_AIPI_CONSUMER_URL=http://192.168.1.100/consumer +VITE_AIPI_PGLINKTO_URL=http://192.168.1.100 +VITE_AIPI_XKLINKTO_URL=http://192.168.1.100 + +# 可选:Canvas 独立 LangGraph 地址 +# VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/package.json b/frontend-web/package.json index 1613e744..bcc34ca7 100644 --- a/frontend-web/package.json +++ b/frontend-web/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build", + "build:network": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode network", "preview": "vite preview", "typecheck": "tsc --noEmit" }, diff --git a/offline-backend-20260512/.dockerignore b/offline-backend-20260512/.dockerignore deleted file mode 100644 index ae7e815a..00000000 --- a/offline-backend-20260512/.dockerignore +++ /dev/null @@ -1,34 +0,0 @@ -# Docker build context exclusions (context = offline-backend-20260512/) -.env -.env.* -!.env.example - -# Runtime / local data — must not enter the image -data/ -backend/.deer-flow/ -backend/.venv/ -backend/venv/ - -# Python caches -backend/**/__pycache__/ -backend/**/*.py[cod] -backend/.pytest_cache/ -backend/.ruff_cache/ -backend/.coverage -backend/htmlcov/ - -# IDE / OS -.git/ -.idea/ -.vscode/ -.DS_Store -Thumbs.db - -# Docs and tests (not needed in runtime image) -backend/tests/ -backend/docs/ -*.md -!backend/README.md - -# Large optional trees (uncomment if build context is too slow) -# backend/knowledge/ diff --git a/offline-backend-20260512/.gitattributes b/offline-backend-20260512/.gitattributes deleted file mode 100644 index 001b9d83..00000000 --- a/offline-backend-20260512/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Shell scripts must use LF so Linux/bash can run them directly. -*.sh text eol=lf -scripts/*.sh text eol=lf diff --git a/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md b/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md deleted file mode 100644 index e3aadf61..00000000 --- a/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md +++ /dev/null @@ -1,495 +0,0 @@ -# DeerFlow 后端 Linux Docker 部署指南 - -本文档说明如何在 **Linux + Docker** 环境下一键部署 DeerFlow 后端(Gateway API,默认端口 **8001**)。 - -部署目录:`offline-backend-20260512/` - ---- - -## 1. 架构概览 - -```text -┌─────────────────────────────────────────────────────┐ -│ Linux 宿主机 │ -│ ┌───────────────────────────────────────────────┐ │ -│ │ Docker 容器: deerflow-backend │ │ -│ │ ┌─────────────────────────────────────────┐ │ │ -│ │ │ Gateway API (uvicorn, port 8001) │ │ │ -│ │ │ + LangGraph Agent Runtime │ │ │ -│ │ └─────────────────────────────────────────┘ │ │ -│ │ 挂载卷: │ │ -│ │ config.yaml / .env │ │ -│ │ backend/ 源码(可热更新 Python 代码) │ │ -│ │ data/ → .deer-flow 持久化数据 │ │ -│ └───────────────────────────────────────────────┘ │ -│ │ 8001 │ -│ ▼ │ -│ 前端 / Nginx / 其他服务 │ -└─────────────────────────────────────────────────────┘ -``` - -**持久化数据**(挂载到 `data/`): - -| 路径 | 说明 | -|------|------| -| `data/data/deerflow.db` 或 `checkpoints.db` | LangGraph checkpoint(SQLite) | -| `data/data/` | 业务 SQLite 文件(未切 MySQL 时) | -| `data/python-vendor/` | 离线安装的 Python 包(asyncmy、croniter 等) | -| `data/users/` | 用户线程、上传、记忆等 | -| `data/archives/` | 会话归档 | - ---- - -## 2. 服务器要求 - -| 项目 | 最低要求 | -|------|----------| -| 操作系统 | Linux x86_64(CentOS 7+、Ubuntu 20.04+、Debian 11+ 等) | -| Docker | 20.10+ | -| Docker Compose | V2(`docker compose` 命令可用) | -| 内存 | 建议 4 GB+ | -| 磁盘 | 建议 20 GB+(含镜像、数据、日志) | -| 网络 | 能访问 LLM API;内网部署可配置 Python 私服 | - -### 2.1 安装 Docker(如未安装) - -**Ubuntu / Debian:** - -```bash -curl -fsSL https://get.docker.com | sh -sudo usermod -aG docker "$USER" -newgrp docker -``` - -**验证:** - -```bash -docker --version -docker compose version -``` - ---- - -## 3. 部署包准备 - -将以下内容上传到服务器(例如 `/opt/deerflow/offline-backend-20260512`): - -```text -offline-backend-20260512/ -├── deploy.sh # 一键部署脚本(主入口) -├── start_backend.sh # 启动(deploy.sh 的别名) -├── stop_backend.sh # 停止 -├── docker-compose.yaml -├── .env.example -├── config.yaml # 应用配置 -├── skill_evolution.json -├── backend/ # 后端源码 + Dockerfile + wheelhouse -│ ├── Dockerfile -│ ├── wheelhouse/ # 离线 Python 包 -│ └── ... -├── scripts/ -│ └── container-entrypoint.sh -└── data/ # 首次部署后自动创建 -``` - ---- - -## 4. 一键部署(推荐) - -### 4.1 配置环境变量 - -```bash -cd /opt/deerflow/offline-backend-20260512 - -cp .env.example .env -vi .env -``` - -**必须修改的项:** - -```env -# 至少配置一个可用模型的 API Key -DEEPSEEK_API_KEY=sk-xxxxxxxx - -# JWT 密钥,部署后不要随意更改 -AUTH_JWT_SECRET=your-long-random-secret - -# 前端实际访问地址(CORS) -GATEWAY_CORS_ORIGINS=http://192.168.1.100:5174,http://localhost:5174 -``` - -**使用 MySQL 时**(`config.yaml` 中 `database.backend: mysql`): - -```env -# 不要用 127.0.0.1 指向宿主机 MySQL;密码特殊字符须 URL 编码 -MYSQL_DATABASE_URL=mysql+asyncmy://user:pass@192.168.1.100:3306/deerflow?charset=utf8mb4 -``` - -**内网无法访问公网 PyPI 时:** - -```env -UV_INDEX_URL=http://your-python-mirror/simple -PIP_INDEX_URL=http://your-python-mirror/simple -UV_TRUSTED_HOST=your-python-mirror -PIP_TRUSTED_HOST=your-python-mirror -``` - -### 4.2 检查 config.yaml - -确认模型配置、沙箱模式、数据库配置正确。`config.yaml` 中与 `$` 开头的值会从 `.env` 读取,例如 `$MYSQL_DATABASE_URL`。 - -### 4.3 执行一键部署 - -```bash -chmod +x deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh -bash deploy.sh -``` - -脚本会自动: - -1. 检查 Docker 环境 -2. 创建 `data/` 目录和兼容符号链接 -3. 若无 `.env` 则从 `.env.example` 复制 -4. 构建 Docker 镜像(或加载离线 tar) -5. 启动容器并做健康检查 - -### 4.4 验证 - -```bash -curl http://127.0.0.1:8001/health -# 期望返回 JSON,HTTP 200 - -curl -s http://127.0.0.1:8001/openapi.json | head -``` - -浏览器访问 API 文档:`http://<服务器IP>:8001/docs` - ---- - -## 5. 常用运维命令 - -| 操作 | 命令 | -|------|------| -| 一键部署/启动 | `bash deploy.sh` 或 `bash start_backend.sh` | -| 停止 | `bash deploy.sh down` 或 `bash stop_backend.sh` | -| 重启 | `bash deploy.sh restart` | -| 查看状态 | `bash deploy.sh status` | -| 查看日志 | `bash deploy.sh logs` | -| 仅构建镜像 | `bash deploy.sh build` | -| 健康检查 | `bash deploy.sh health` | - -### 5.1 自定义端口 - -```bash -HOST_PORT=9001 bash deploy.sh up -``` - -### 5.2 离线镜像模式(内网/无法访问 docker.io 时必用) - -**典型失败日志:** - -```text -failed to fetch anonymous token: Get "https://auth.docker.io/token...": i/o timeout -``` - -说明服务器访问不了 Docker Hub,**不能在线 build**,必须用离线 tar。 - -**步骤 1:在有公网的机器上导出镜像** - -```bash -bash scripts/export-offline-image.sh -# 产出: deerflow-backend-offline-image-20260505.tar -``` - -**步骤 2:上传到内网服务器部署目录** - -**步骤 3:内网服务器加载并启动** - -```bash -DEPLOY_MODE=offline \ -IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \ -IMAGE_TAG=deerflow-backend-offline:20260505 \ -bash deploy.sh up -``` - -若 tar 已在目录中,`deploy.sh` 会**自动检测**并切换为离线模式。 - -或分步: - -```bash -bash deploy.sh load -IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up -``` - -### 5.3 配置 Docker 镜像加速(可选,有内网镜像源时) - -编辑 `/etc/docker/daemon.json`: - -```json -{ - "registry-mirrors": [ - "https://你的内网或国内镜像地址" - ], - "dns": ["114.114.114.114", "223.5.5.5"] -} -``` - -```bash -sudo systemctl daemon-reload -sudo systemctl restart docker -bash deploy.sh build -``` - ---- - -## 6. 前端对接 - -前端需配置后端地址(Vite 环境变量示例): - -```env -VITE_BACKEND_BASE_URL=http://192.168.1.100:8001 -VITE_LANGGRAPH_BASE_URL=http://192.168.1.100:8001/api -``` - -确保 `.env` 中 `GATEWAY_CORS_ORIGINS` 包含前端实际 Origin。 - ---- - -## 7. MySQL 切换(可选) - -若初始使用 SQLite,后续可切换到 MySQL: - -```bash -bash stop_backend.sh - -MYSQL_DATABASE_URL='mysql+asyncmy://user:pass@192.168.1.100:3306/deerflow?charset=utf8mb4' \ -bash switch_database_to_mysql.sh - -bash start_backend.sh -``` - -详细说明见 [MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md](./MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md)。 - ---- - -## 8. 代码热更新 - -当前部署方式将 `backend/app`、`backend/packages` 等目录挂载进容器,**更新 Python 代码后重启即可生效**,无需重新构建镜像: - -```bash -# 上传新代码后 -bash deploy.sh restart -``` - -若修改了 `pyproject.toml` / 依赖,需重新构建: - -```bash -bash deploy.sh build -bash deploy.sh restart -``` - ---- - -## 9. 目录与卷说明 - -| 宿主机 | 容器内 | 说明 | -|--------|--------|------| -| `./config.yaml` | `/app/config.yaml` | 主配置(只读) | -| `./.env` | 环境变量注入 | 密钥、CORS 等 | -| `./backend/app` | `/app/backend/app` | 应用层代码 | -| `./backend/packages` | `/app/backend/packages` | harness 框架 | -| `./backend/skills` | `/app/backend/skills` | Agent Skills | -| `./backend/wheelhouse` | `/app/wheelhouse` | 离线 wheel 包 | -| `./data` | `/app/backend/.deer-flow` | 全部运行时数据 | - ---- - -## 10. 故障排查 - -### 10.0 Docker 未运行或无权限 - -报错示例: - -```text -[FAIL] Docker 未运行或无权限... -``` - -**第一步:看具体原因** - -```bash -docker info -``` - -**情况 A:permission denied(最常见)** - -说明当前用户不在 `docker` 组: - -```bash -# 把当前用户加入 docker 组 -sudo usermod -aG docker "$USER" - -# 让组权限立即生效(或重新 SSH 登录) -newgrp docker - -# 验证 -docker info -docker ps - -# 再部署 -bash deploy.sh -``` - -若仍不行,确认 socket 权限: - -```bash -ls -l /var/run/docker.sock -# 期望类似: srw-rw---- root docker ... -``` - -**情况 B:Cannot connect to the Docker daemon** - -说明 Docker 服务没起来: - -```bash -sudo systemctl status docker -sudo systemctl start docker -sudo systemctl enable docker -docker info -bash deploy.sh -``` - -**情况 C:未安装 Docker** - -```bash -curl -fsSL https://get.docker.com | sh -sudo usermod -aG docker "$USER" -newgrp docker -bash deploy.sh -``` - -**临时绕过(仅测试用,不推荐生产)** - -```bash -sudo bash deploy.sh -``` - -### 10.1 容器启动后立即退出 - -```bash -bash deploy.sh logs -``` - -常见原因: - -- `.env` 中 API Key 或 `AUTH_JWT_SECRET` 未配置 -- `config.yaml` 语法错误 -- MySQL 连接失败(检查 `MYSQL_DATABASE_URL` 是否用了容器可达的 IP) -- 缺少 `asyncmy`(MySQL 模式):确认 `backend/wheelhouse` 中有对应 wheel,或配置 Python 私服 - -### 10.1.1 脚本报 `$'\r': command not found` 或 `pipefail: invalid option` - -这是 **Windows 换行符(CRLF)** 导致的。在 Linux 上执行: - -```bash -sed -i 's/\r$//' deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh switch_database_to_mysql.sh -chmod +x deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh -bash deploy.sh -``` - -或在 Windows 上传前,用 VS Code 右下角把文件编码改为 **LF**。 - -### 10.1.2 构建时报 `.venv cannot be used because it is not a valid Python environment` - -原因:开发机(尤其 Windows)上的 `backend/.venv` 被打包进 Docker 构建上下文,Linux 容器内无法使用。 - -解决:仓库已添加 `.dockerignore` 排除 `.venv`。更新代码后重新构建: - -```bash -bash deploy.sh build -# 或 -bash deploy.sh up -``` - -若仍报错,手动删除后再构建: - -```bash -rm -rf backend/.venv -bash deploy.sh build -``` - -### 10.2 健康检查失败 - -```bash -docker ps -a -docker logs deerflow-backend --tail 100 -curl -v http://127.0.0.1:8001/health -``` - -确认端口未被占用: - -```bash -ss -tlnp | grep 8001 -``` - -### 10.3 前端报 CORS / Failed to fetch - -检查 `.env`: - -```env -GATEWAY_CORS_ORIGINS=http://你的前端地址:端口 -``` - -修改后重启: - -```bash -bash deploy.sh restart -``` - -### 10.4 Docker 权限问题 - -```bash -sudo usermod -aG docker "$USER" -newgrp docker -``` - -### 10.5 沙箱需要 Docker-in-Docker - -若 `config.yaml` 使用 `AioSandboxProvider`,容器需挂载宿主机 Docker socket(默认已挂载 `/var/run/docker.sock`)。确认: - -```bash -ls -l /var/run/docker.sock -``` - ---- - -## 11. 生产环境建议 - -1. **反向代理**:前面加 Nginx,配置 HTTPS 和限流 -2. **防火墙**:仅开放必要端口(如 443、8001 内网) -3. **备份**:定期备份 `data/` 目录和 `config.yaml`、`.env` -4. **日志**:配置 Docker 日志轮转(`/etc/docker/daemon.json` 中 `log-opts`) -5. **资源**:根据并发调整 `GATEWAY_WORKERS`(`.env`,默认 2) -6. **文档**:生产环境建议 `GATEWAY_ENABLE_DOCS=false` - ---- - -## 12. 回滚 - -```bash -bash stop_backend.sh - -# 恢复配置和数据备份 -cp data/backups/xxx/config.yaml.bak config.yaml -cp data/backups/xxx/env.bak .env -cp -a data.bak data - -bash start_backend.sh -``` - ---- - -## 13. 相关文档 - -- [MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md](./MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md) — MySQL 切换 -- [backend/docs/BACKEND_RUN_STOP_ZH.md](./backend/docs/BACKEND_RUN_STOP_ZH.md) — 本地非 Docker 启停 -- [backend/docs/CONFIGURATION.md](./backend/docs/CONFIGURATION.md) — 配置项详解 diff --git a/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md b/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md index 4f3f6551..2fc884b7 100644 --- a/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md +++ b/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md @@ -1,21 +1,13 @@ # Linux Docker 版:业务库切换 MySQL 发版说明 -适用场景:Linux 服务器上使用 Docker 部署 DeerFlow 后端。 +适用场景:Linux 服务器上使用 `offline-backend-20260512/start_backend.sh` 启动 Docker 后端。 -推荐一键部署: - -```bash -bash deploy.sh -``` - -或使用兼容脚本: +原启动脚本保持不变: ```bash bash start_backend.sh ``` -详细步骤见 [DEPLOY_LINUX_DOCKER_ZH.md](./DEPLOY_LINUX_DOCKER_ZH.md)。 - 单独新增切换脚本: ```bash diff --git a/offline-backend-20260512/backend/Dockerfile b/offline-backend-20260512/backend/Dockerfile index 506b9d62..94cfbe5f 100644 --- a/offline-backend-20260512/backend/Dockerfile +++ b/offline-backend-20260512/backend/Dockerfile @@ -47,9 +47,8 @@ COPY backend ./backend # Install dependencies with cache mount # When UV_EXTRAS is set (e.g. "postgres"), installs optional dependencies. -# Remove host .venv if present — Windows/macOS venvs break Linux container builds. RUN --mount=type=cache,target=/root/.cache/uv \ - sh -c "cd backend && rm -rf .venv && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}" + sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}" # ── Stage 2: Dev ────────────────────────────────────────────────────────────── # Retains compiler toolchain from builder so startup-time `uv sync` can build diff --git a/offline-backend-20260512/deploy.sh b/offline-backend-20260512/deploy.sh deleted file mode 100644 index be0dd967..00000000 --- a/offline-backend-20260512/deploy.sh +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env bash -# -# deploy.sh — DeerFlow 后端 Linux Docker 一键部署脚本 -# -# 用法: -# bash deploy.sh # 首次部署: 检查环境 → 构建/加载镜像 → 启动 -# bash deploy.sh up # 启动服务 -# bash deploy.sh down # 停止并移除容器 -# bash deploy.sh restart # 重启 -# bash deploy.sh status # 查看状态 -# bash deploy.sh logs # 跟踪日志 -# bash deploy.sh build # 仅构建镜像 -# bash deploy.sh load # 从离线 tar 加载镜像 -# bash deploy.sh health # 健康检查 -# -# 环境变量(可选): -# HOST_PORT 宿主机映射端口,默认 8001 -# CONTAINER_NAME 容器名,默认 deerflow-backend -# IMAGE_TAG 镜像标签,默认 deerflow-backend:latest -# IMAGE_TAR 离线镜像 tar 路径 -# DEPLOY_MODE build | offline,默认 build -# SKIP_BUILD 1 时跳过构建/加载,直接启动 - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "${SCRIPT_DIR}" - -COMPOSE_PROJECT="${COMPOSE_PROJECT:-deerflow-backend}" -COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yaml" -COMPOSE=(docker compose -p "${COMPOSE_PROJECT}" -f "${COMPOSE_FILE}") - -HOST_PORT="${HOST_PORT:-8001}" -CONTAINER_NAME="${CONTAINER_NAME:-deerflow-backend}" -IMAGE_TAG="${IMAGE_TAG:-deerflow-backend:latest}" -IMAGE_TAR="${IMAGE_TAR:-${SCRIPT_DIR}/deerflow-backend-offline-image.tar}" -DEPLOY_MODE="${DEPLOY_MODE:-build}" -SKIP_BUILD="${SKIP_BUILD:-0}" - -resolve_offline_tar() { - if [ -f "${IMAGE_TAR}" ]; then - return 0 - fi - local candidate - for candidate in \ - "${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar" \ - "${SCRIPT_DIR}/deerflow-backend-offline-image.tar"; do - if [ -f "${candidate}" ]; then - IMAGE_TAR="${candidate}" - return 0 - fi - done - return 1 -} - -# 内网服务器通常无法访问 docker.io,检测到离线 tar 时自动切换模式 -resolve_offline_tar || true -if [ -f "${IMAGE_TAR}" ] && [ "${DEPLOY_MODE}" = "build" ]; then - DEPLOY_MODE="offline" - if [ "${IMAGE_TAG}" = "deerflow-backend:latest" ]; then - IMAGE_TAG="deerflow-backend-offline:20260505" - fi - warn "检测到离线镜像 ${IMAGE_TAR},已自动切换为 DEPLOY_MODE=offline" -fi - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' - -info() { echo -e "${BLUE}[INFO]${NC} $*"; } -ok() { echo -e "${GREEN}[ OK ]${NC} $*"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -fail() { echo -e "${RED}[FAIL]${NC} $*" >&2; exit 1; } - -usage() { - sed -n '3,18p' "$0" | sed 's/^# \?//' - exit 0 -} - -require_cmd() { - command -v "$1" >/dev/null 2>&1 || fail "未找到命令: $1" -} - -ensure_layout() { - mkdir -p "${SCRIPT_DIR}/data/data" "${SCRIPT_DIR}/data/backups" "${SCRIPT_DIR}/data/archives/conversations" - - # switch_database_to_mysql.sh 等脚本期望根目录有这些路径 - [ -e "${SCRIPT_DIR}/wheelhouse" ] || ln -sfn backend/wheelhouse "${SCRIPT_DIR}/wheelhouse" - [ -e "${SCRIPT_DIR}/skills" ] || ln -sfn backend/skills "${SCRIPT_DIR}/skills" - if [ ! -e "${SCRIPT_DIR}/extensions_config.json" ]; then - ln -sfn backend/extensions_config.json "${SCRIPT_DIR}/extensions_config.json" - fi - - chmod +x "${SCRIPT_DIR}/scripts/container-entrypoint.sh" 2>/dev/null || true -} - -ensure_env() { - if [ ! -f "${SCRIPT_DIR}/.env" ]; then - if [ -f "${SCRIPT_DIR}/.env.example" ]; then - cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env" - warn "已从 .env.example 生成 .env,部署前请编辑 API Key 和 AUTH_JWT_SECRET" - else - fail "缺少 .env 文件,请先创建并配置环境变量" - fi - fi - - if [ ! -f "${SCRIPT_DIR}/config.yaml" ]; then - fail "缺少 config.yaml,请复制并编辑 config.example.yaml" - fi - - if [ ! -f "${SCRIPT_DIR}/skill_evolution.json" ]; then - echo '{"enabled":false,"curator":{"enabled":false}}' > "${SCRIPT_DIR}/skill_evolution.json" - warn "已创建默认 skill_evolution.json" - fi -} - -check_docker() { - require_cmd docker - - local docker_err - docker_err="$(docker info 2>&1)" || { - echo "${docker_err}" >&2 - echo >&2 - if echo "${docker_err}" | grep -qiE 'permission denied|connect: permission'; then - fail "当前用户无权访问 Docker。请执行: - sudo usermod -aG docker \$USER - newgrp docker -然后重新运行: bash deploy.sh - -临时方案(不推荐长期使用): sudo bash deploy.sh" - fi - if echo "${docker_err}" | grep -qiE 'cannot connect|Is the docker daemon running|connection refused'; then - fail "Docker 服务未运行。请先启动: - sudo systemctl start docker - sudo systemctl enable docker -然后重新运行: bash deploy.sh" - fi - fail "Docker 不可用。请先在服务器上执行 docker info 查看详细错误" - } - - docker compose version >/dev/null 2>&1 || fail "未找到 docker compose,请安装 Docker Compose V2" -} - -generate_secret_if_needed() { - local key="$1" - local file="$2" - if grep -qE "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env" 2>/dev/null; then - local val - val="$(grep -E "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env" | tail -n1 | sed 's/^[^=]*=//')" - if [ -n "${val}" ] && [ "${val}" != "change-me-to-a-long-random-string" ]; then - return 0 - fi - fi - if [ -f "${file}" ]; then - return 0 - fi - mkdir -p "$(dirname "${file}")" - if command -v openssl >/dev/null 2>&1; then - openssl rand -hex 32 > "${file}" - chmod 600 "${file}" - warn "已生成 ${key} → ${file},建议将其写入 .env 后删除该文件" - fi -} - -load_offline_image() { - if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then - ok "镜像已存在: ${IMAGE_TAG}" - return 0 - fi - [ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR} - -内网服务器无法访问 docker.io 时,请: - 1. 在有公网的机器上执行 bash scripts/export-offline-image.sh 导出 tar - 2. 上传到本目录后执行: DEPLOY_MODE=offline bash deploy.sh up" - info "加载离线镜像: ${IMAGE_TAR}" - local load_out loaded_tag - load_out="$(docker load -i "${IMAGE_TAR}")" - echo "${load_out}" - loaded_tag="$(echo "${load_out}" | sed -n 's/^Loaded image: //p' | tail -n1)" - if [ -n "${loaded_tag}" ] && ! docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then - info "将 ${loaded_tag} 标记为 ${IMAGE_TAG}" - docker tag "${loaded_tag}" "${IMAGE_TAG}" - fi - docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1 || fail "镜像加载后未找到 ${IMAGE_TAG},请检查 IMAGE_TAG 是否与 tar 内标签一致" - ok "镜像加载完成: ${IMAGE_TAG}" -} - -build_image() { - info "构建 Docker 镜像: ${IMAGE_TAG}(需要能访问 docker.io / ghcr.io)" - export IMAGE_TAG - if ! "${COMPOSE[@]}" build gateway; then - fail "在线构建失败。内网服务器请改用离线镜像: - DEPLOY_MODE=offline IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar bash deploy.sh up - -或在 /etc/docker/daemon.json 配置 registry-mirrors 后重试。" - fi - ok "镜像构建完成" -} - -prepare_image() { - if [ "${SKIP_BUILD}" = "1" ]; then - info "SKIP_BUILD=1,跳过构建/加载" - return 0 - fi - if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then - ok "使用已有镜像: ${IMAGE_TAG}" - return 0 - fi - case "${DEPLOY_MODE}" in - offline) - load_offline_image - ;; - build|*) - build_image - ;; - esac -} - -wait_for_health() { - local url="http://127.0.0.1:${HOST_PORT}/health" - local retries="${HEALTH_RETRIES:-30}" - local i=1 - info "等待服务就绪: ${url}" - while [ "${i}" -le "${retries}" ]; do - if curl -fsS "${url}" >/dev/null 2>&1; then - ok "健康检查通过" - return 0 - fi - sleep 2 - i=$((i + 1)) - done - warn "健康检查超时,请执行: bash deploy.sh logs" - return 1 -} - -cmd_up() { - check_docker - ensure_layout - ensure_env - generate_secret_if_needed "AUTH_JWT_SECRET" "${SCRIPT_DIR}/data/.auth-jwt-secret" - - export HOST_PORT CONTAINER_NAME IMAGE_TAG - - if ! docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then - prepare_image - fi - - info "启动容器..." - if [ "${DEPLOY_MODE}" = "offline" ] || docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then - "${COMPOSE[@]}" up -d --no-build --remove-orphans - else - "${COMPOSE[@]}" up -d --remove-orphans - fi - ok "容器已启动: ${CONTAINER_NAME}" - echo - echo " API 地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 127.0.0.1):${HOST_PORT}" - echo " 健康检查: http://127.0.0.1:${HOST_PORT}/health" - echo " API 文档: http://127.0.0.1:${HOST_PORT}/docs" - echo - wait_for_health || true -} - -cmd_down() { - check_docker - export HOST_PORT CONTAINER_NAME IMAGE_TAG - info "停止容器..." - "${COMPOSE[@]}" down --remove-orphans - ok "已停止" -} - -cmd_restart() { - cmd_down - cmd_up -} - -cmd_status() { - check_docker - export HOST_PORT CONTAINER_NAME IMAGE_TAG - "${COMPOSE[@]}" ps - echo - if docker ps --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then - curl -fsS "http://127.0.0.1:${HOST_PORT}/health" 2>/dev/null && echo || warn "健康检查失败" - else - warn "容器未运行" - fi -} - -cmd_logs() { - check_docker - export HOST_PORT CONTAINER_NAME IMAGE_TAG - "${COMPOSE[@]}" logs -f --tail=200 gateway -} - -cmd_build() { - check_docker - ensure_layout - build_image -} - -cmd_load() { - check_docker - DEPLOY_MODE=offline - load_offline_image -} - -cmd_health() { - curl -fsS "http://127.0.0.1:${HOST_PORT}/health" && echo || fail "健康检查失败" -} - -main() { - local cmd="${1:-up}" - case "${cmd}" in - -h|--help|help) usage ;; - up|start|"") cmd_up ;; - down|stop) cmd_down ;; - restart) cmd_restart ;; - status) cmd_status ;; - logs) cmd_logs ;; - build) cmd_build ;; - load) cmd_load ;; - health) cmd_health ;; - deploy) cmd_up ;; - *) - fail "未知命令: ${cmd}。运行 bash deploy.sh --help 查看帮助" - ;; - esac -} - -main "${1:-up}" diff --git a/offline-backend-20260512/docker-compose.yaml b/offline-backend-20260512/docker-compose.yaml deleted file mode 100644 index 0a67f1b2..00000000 --- a/offline-backend-20260512/docker-compose.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# DeerFlow 后端 — Linux Docker 部署 -# 推荐使用 deploy.sh 管理;也可直接: docker compose up -d - -services: - gateway: - build: - context: . - dockerfile: backend/Dockerfile - args: - APT_MIRROR: ${APT_MIRROR:-} - UV_IMAGE: ${UV_IMAGE:-ghcr.io/astral-sh/uv:0.7.20} - UV_INDEX_URL: ${UV_INDEX_URL:-https://pypi.org/simple} - UV_EXTRAS: ${UV_EXTRAS:-} - image: ${IMAGE_TAG:-deerflow-backend:latest} - container_name: ${CONTAINER_NAME:-deerflow-backend} - ports: - - "${HOST_PORT:-8001}:8001" - env_file: - - .env - environment: - CI: "true" - DEER_FLOW_PROJECT_ROOT: /app - DEER_FLOW_HOME: /app/backend/.deer-flow - DEER_FLOW_CONFIG_PATH: /app/config.yaml - DEER_FLOW_EXTENSIONS_CONFIG_PATH: /app/backend/extensions_config.json - DEER_FLOW_SKILL_EVOLUTION_CONFIG_PATH: /app/skill_evolution.json - DEER_FLOW_SKILLS_PATH: /app/backend/skills - DEER_FLOW_CHANNELS_LANGGRAPH_URL: ${DEER_FLOW_CHANNELS_LANGGRAPH_URL:-http://gateway:8001/api} - DEER_FLOW_CHANNELS_GATEWAY_URL: ${DEER_FLOW_CHANNELS_GATEWAY_URL:-http://gateway:8001} - UV_LINK_MODE: copy - volumes: - - ./config.yaml:/app/config.yaml:ro - - ./skill_evolution.json:/app/skill_evolution.json:ro - - ./backend/app:/app/backend/app - - ./backend/packages:/app/backend/packages - - ./backend/scripts:/app/backend/scripts - - ./backend/extensions_config.json:/app/backend/extensions_config.json:ro - - ./backend/skills:/app/backend/skills:ro - - ./backend/wheelhouse:/app/wheelhouse:ro - - ./backend/pyproject.toml:/app/backend/pyproject.toml:ro - - ./backend/uv.lock:/app/backend/uv.lock:ro - - ./backend/langgraph.json:/app/backend/langgraph.json:ro - - ./scripts/container-entrypoint.sh:/app/scripts/container-entrypoint.sh:ro - - ./data:/app/backend/.deer-flow - - ${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - working_dir: /app - extra_hosts: - - "host.docker.internal:host-gateway" - restart: unless-stopped - command: ["sh", "/app/scripts/container-entrypoint.sh"] diff --git a/offline-backend-20260512/scripts/container-entrypoint.sh b/offline-backend-20260512/scripts/container-entrypoint.sh deleted file mode 100644 index f6eda632..00000000 --- a/offline-backend-20260512/scripts/container-entrypoint.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -# Container startup: install optional offline deps, then launch Gateway. -set -eu - -cd /app/backend - -VENDOR_DIR="/app/backend/.deer-flow/python-vendor" -WHEELHOUSE="/app/wheelhouse" -mkdir -p "${VENDOR_DIR}" - -export PYTHONPATH=".:${VENDOR_DIR}" - -pip_install_offline() { - local packages="$1" - if [ -d "${WHEELHOUSE}" ]; then - uv pip install \ - --target "${VENDOR_DIR}" \ - --no-index \ - --find-links "${WHEELHOUSE}" \ - ${packages} - elif [ -n "${UV_INDEX_URL:-}" ] || [ -n "${PIP_INDEX_URL:-}" ]; then - uv pip install --target "${VENDOR_DIR}" ${packages} - else - uv pip install --target "${VENDOR_DIR}" ${packages} - fi -} - -pip_install_online() { - local packages="$1" - if [ -n "${UV_INDEX_URL:-}" ]; then - uv pip install --target "${VENDOR_DIR}" ${packages} - elif [ -n "${PIP_INDEX_URL:-}" ]; then - uv pip install --target "${VENDOR_DIR}" --index-url "${PIP_INDEX_URL}" ${packages} - else - uv pip install --target "${VENDOR_DIR}" ${packages} - fi -} - -install_if_missing() { - local module="$1" - local packages="$2" - if uv run --no-sync python -c "import ${module}" >/dev/null 2>&1; then - return 0 - fi - echo "Installing ${packages} into ${VENDOR_DIR}" - if [ -d "${WHEELHOUSE}" ] && ls "${WHEELHOUSE}"/*.whl >/dev/null 2>&1; then - pip_install_offline "${packages}" - else - pip_install_online "${packages}" - fi -} - -if [ -n "${MYSQL_DATABASE_URL:-}" ]; then - install_if_missing "asyncmy" "${MYSQL_PIP_PACKAGES:-asyncmy==0.2.10}" -fi - -if [ "${SCHEDULER_INSTALL_DEPS:-1}" = "1" ]; then - install_if_missing "croniter" "${SCHEDULER_PIP_PACKAGES:-croniter==6.2.2}" -fi - -WORKERS="${GATEWAY_WORKERS:-2}" -echo "Starting Gateway on 0.0.0.0:8001 (workers=${WORKERS})" - -exec env PYTHONPATH=".:${VENDOR_DIR}" uv run --no-sync uvicorn app.gateway.app:app \ - --host 0.0.0.0 \ - --port 8001 \ - --workers "${WORKERS}" diff --git a/offline-backend-20260512/scripts/export-offline-image.sh b/offline-backend-20260512/scripts/export-offline-image.sh deleted file mode 100644 index d9af23d1..00000000 --- a/offline-backend-20260512/scripts/export-offline-image.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# 在有公网的环境构建并导出离线镜像 tar,供内网服务器 docker load 使用。 -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "${SCRIPT_DIR}" - -IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}" -OUTPUT="${OUTPUT:-${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar}" - -echo "构建镜像: ${IMAGE_TAG}" -IMAGE_TAG="${IMAGE_TAG}" docker compose -p deerflow-backend -f docker-compose.yaml build gateway - -loaded="$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -F "${IMAGE_TAG}" | head -n1 || true)" -if [ -z "${loaded}" ]; then - # compose build 可能产出 deerflow-backend:latest,再打离线标签 - docker tag deerflow-backend:latest "${IMAGE_TAG}" 2>/dev/null || true -fi - -echo "导出到: ${OUTPUT}" -docker save -o "${OUTPUT}" "${IMAGE_TAG}" -ls -lh "${OUTPUT}" -echo -echo "上传到内网服务器后执行:" -echo " DEPLOY_MODE=offline IMAGE_TAR=${OUTPUT##*/} IMAGE_TAG=${IMAGE_TAG} bash deploy.sh up"