Merge remote-tracking branch 'origin/master' into ji
This commit is contained in:
commit
43cbcd86be
10 files changed
+829
-4
No files matched your search
@@ -5,13 +5,15 @@
|
|||||||
# ├─────────────────┼──────────────────────────────────────────────────┤
|
# ├─────────────────┼──────────────────────────────────────────────────┤
|
||||||
# │ pnpm dev │ .env.development + .env.development.local │
|
# │ pnpm dev │ .env.development + .env.development.local │
|
||||||
# │ pnpm build │ .env.production + .env.production.local │
|
# │ pnpm build │ .env.production + .env.production.local │
|
||||||
|
# │ pnpm build:sit │ .env.sit + .env.sit.local │
|
||||||
# │ scripts/start-* │ 脚本注入的环境变量会覆盖上述文件(见下方说明) │
|
# │ scripts/start-* │ 脚本注入的环境变量会覆盖上述文件(见下方说明) │
|
||||||
# └─────────────────┴──────────────────────────────────────────────────┘
|
# └─────────────────┴──────────────────────────────────────────────────┘
|
||||||
#
|
#
|
||||||
# 推荐:
|
# 推荐:
|
||||||
# - 日常本地开发:改 .env.development(已提交,团队默认 UAT 地址)
|
# - 日常本地开发:改 .env.development(已提交,团队默认 UAT 地址)
|
||||||
# - 本机连 127.0.0.1:8001:复制 .env.development.local.example → .env.development.local
|
# - 本机连 127.0.0.1:8001:复制 .env.development.local.example → .env.development.local
|
||||||
# - 内网打包部署:改 .env.production 后再执行 pnpm build
|
# - 内网 UAT 打包部署:改 .env.production 后再执行 pnpm build
|
||||||
|
# - SIT 环境打包:改 .env.sit 后再执行 pnpm build:sit(地址为 UAT 的 sit 版本)
|
||||||
#
|
#
|
||||||
# 若浏览器直连后端报 CORS,请在 Gateway 配置 CORS_ORIGINS,包含前端源站,例如:
|
# 若浏览器直连后端报 CORS,请在 Gateway 配置 CORS_ORIGINS,包含前端源站,例如:
|
||||||
# CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:5174
|
# CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:5174
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# SIT 环境打包(pnpm build:sit / npm run build:sit)— 接口地址为 UAT 的 sit 版本
|
||||||
|
# 打包后变量会写入 dist,运行时不可再改
|
||||||
|
|
||||||
|
# Gateway / DeerFlow API
|
||||||
|
VITE_BACKEND_BASE_URL=https://ch1.b.sit.4.cn/deerflow
|
||||||
|
VITE_LANGGRAPH_BASE_URL=https://ch1.b.sit.4.cn/deerflow/api
|
||||||
|
|
||||||
|
# 轻应用「新建笔记」系列接口
|
||||||
|
VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api
|
||||||
|
# 佳来模型对话接口
|
||||||
|
VITE_AIPI_CHAT_URL=https://ch1.b.sit.4.cn/gpt
|
||||||
|
# 多轮检索接口
|
||||||
|
VITE_AIPI_GENERATE_CHAT_URL=https://ch1.b.sit.4.cn/jialai_b1_chat
|
||||||
|
VITE_AIPI_TASK_CHAT_URL=https://ch1.b.sit.4.cn/qq_b1_chat
|
||||||
|
VITE_AIPI_KNOWLEDGE_URL=https://chlib.b.sit.4.cn/knowlibrary
|
||||||
|
VITE_AIPI_B1COH_URL=https://ch1.b.sit.4.cn/web
|
||||||
|
VITE_AIPI_B2ACTOR_URL=https://ty.b.sit.4.cn
|
||||||
|
VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1
|
||||||
|
VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.sit.4.cn/knowes/api/iframeKnowledgeUrl
|
||||||
|
VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red
|
||||||
|
VITE_AIPI_MOPECHAT_URL=https://ch1.b.sit.4.cn/likun_b1_mope
|
||||||
|
VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.sit.4.cn
|
||||||
|
VITE_AIPI_CONSUMER_URL=https://ch1.b.sit.4.cn/consumer
|
||||||
|
VITE_AIPI_PGLINKTO_URL=https://pg.b.sit.4.cn
|
||||||
|
VITE_AIPI_XKLINKTO_URL=https://kz.b.sit.4.cn
|
||||||
|
|
||||||
|
# 可选:Canvas 独立 LangGraph 地址
|
||||||
|
# VITE_CANVAS_LANGGRAPH_BASE_URL=
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build",
|
"build": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build",
|
||||||
|
"build:sit": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode sit",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { listAgents, type Agent } from "@/core/agents";
|
import { listAgents, type Agent } from "@/core/agents";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -183,6 +184,11 @@ export function RecommendAgentsDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: RecommendAgentsDialogProps) {
|
}: RecommendAgentsDialogProps) {
|
||||||
|
// 弹窗经 Radix portal 渲染到 .roundtable-planning 之外,页面的深蓝 scoped 样式
|
||||||
|
// 够不着它。深色下手动挂上 .roundtable-planning--dark,让同一套深蓝 token + 兜底
|
||||||
|
// 映射也作用于弹窗;浅色不挂,弹窗浅色外观不变。
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === "dark";
|
||||||
const [state, setState] = useState<InternalState>(INITIAL_STATE);
|
const [state, setState] = useState<InternalState>(INITIAL_STATE);
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
@@ -516,7 +522,12 @@ export function RecommendAgentsDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent className="sm:max-w-3xl max-h-[85vh] flex flex-col overflow-hidden gap-3">
|
<DialogContent
|
||||||
|
className={cn(
|
||||||
|
"sm:max-w-3xl max-h-[85vh] flex flex-col overflow-hidden gap-3",
|
||||||
|
isDark && "roundtable-planning--dark",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<DialogHeader className="shrink-0">
|
<DialogHeader className="shrink-0">
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<SparklesIcon className="size-5 text-purple-500" />
|
<SparklesIcon className="size-5 text-purple-500" />
|
||||||
|
|||||||
@@ -55,6 +55,15 @@
|
|||||||
/* —— 功能语义色 —— */
|
/* —— 功能语义色 —— */
|
||||||
--destructive: #f5222d; /* zs-001-06 错误 */
|
--destructive: #f5222d; /* zs-001-06 错误 */
|
||||||
|
|
||||||
|
/* —— brand-* 别名改深蓝(关键):globals.css 只在 :root 把 --brand-* 定为浅红/红,
|
||||||
|
* .dark 未覆盖,导致组件里 var(--brand-200/900/950) 在深色下泛粉/泛红、与深蓝冲突。
|
||||||
|
* 这里改为渐蓝系 ZS-002,所有 bg-[var(--brand-200)] / text-[var(--brand-900)] 等
|
||||||
|
* 一并由红转蓝。 —— */
|
||||||
|
--workspace-brand: #1677ff; /* 与 primary 对齐 */
|
||||||
|
--brand-200: #16335f; /* 浅红 chip 底 → 深蓝软底(chip / 头像底) */
|
||||||
|
--brand-900: #4096ff; /* 红强调 / 按钮 hover → 蓝(zs-002-05) */
|
||||||
|
--brand-950: #91caff; /* 深红文字 → 浅蓝(zs-002-03,深底上可读) */
|
||||||
|
|
||||||
/* —— 圆桌局部别名(回落到上面已覆盖的 token) —— */
|
/* —— 圆桌局部别名(回落到上面已覆盖的 token) —— */
|
||||||
--rt-page-bg: var(--background);
|
--rt-page-bg: var(--background);
|
||||||
--rt-primary: var(--primary);
|
--rt-primary: var(--primary);
|
||||||
@@ -295,6 +304,33 @@
|
|||||||
background-color: color-mix(in oklch, #ec4899 18%, var(--card)) !important;
|
background-color: color-mix(in oklch, #ec4899 18%, var(--card)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
* 残留红色清理 —— 内联 SVG 红填充 + 红色发光阴影 → 深蓝系。
|
||||||
|
* (这些是写死在组件里的红:头像 fill 属性、按钮 rgba(245,34,45) 阴影,
|
||||||
|
* 不经 token,需在此单独改写;仅深色生效。)
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
/* 头像等内联 SVG 的红色 fill(CSS 可覆盖 SVG presentation 属性) */
|
||||||
|
:is(.dark .roundtable-planning, .roundtable-planning--dark) [fill="#f5222d"] {
|
||||||
|
fill: #1677ff !important;
|
||||||
|
}
|
||||||
|
:is(.dark .roundtable-planning, .roundtable-planning--dark) [fill="#cf1322"] {
|
||||||
|
fill: #0958d9 !important;
|
||||||
|
}
|
||||||
|
:is(.dark .roundtable-planning, .roundtable-planning--dark) [fill="#a8071a"] {
|
||||||
|
fill: #003eb3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 红色发光阴影 → 蓝色发光 */
|
||||||
|
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||||
|
.shadow-\[0_2px_8px_rgba\(245\2c 34\2c 45\2c 0\.15\)\] {
|
||||||
|
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.28) !important;
|
||||||
|
}
|
||||||
|
:is(.dark .roundtable-planning, .roundtable-planning--dark)
|
||||||
|
.shadow-\[0_8px_30px_rgba\(245\2c 34\2c 45\2c 0\.06\)\] {
|
||||||
|
box-shadow: 0 8px 30px rgba(22, 119, 255, 0.14) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* 结构件覆盖(顶栏 / 步骤条 / 次要按钮)—— 保留原有针对性规则。
|
* 结构件覆盖(顶栏 / 步骤条 / 次要按钮)—— 保留原有针对性规则。
|
||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# 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 离线镜像模式(无法构建时)
|
||||||
|
|
||||||
|
若已提供预构建镜像 tar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \
|
||||||
|
IMAGE_TAG=deerflow-backend-offline:20260505 \
|
||||||
|
DEPLOY_MODE=offline \
|
||||||
|
bash deploy.sh up
|
||||||
|
```
|
||||||
|
|
||||||
|
或分步:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash deploy.sh load
|
||||||
|
IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.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.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) — 配置项详解
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
# Linux Docker 版:业务库切换 MySQL 发版说明
|
# Linux Docker 版:业务库切换 MySQL 发版说明
|
||||||
|
|
||||||
适用场景:Linux 服务器上使用 `offline-backend-20260512/start_backend.sh` 启动 Docker 后端。
|
适用场景:Linux 服务器上使用 Docker 部署 DeerFlow 后端。
|
||||||
|
|
||||||
原启动脚本保持不变:
|
推荐一键部署:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
或使用兼容脚本:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash start_backend.sh
|
bash start_backend.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
详细步骤见 [DEPLOY_LINUX_DOCKER_ZH.md](./DEPLOY_LINUX_DOCKER_ZH.md)。
|
||||||
|
|
||||||
单独新增切换脚本:
|
单独新增切换脚本:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
#!/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}"
|
||||||
|
|
||||||
|
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
|
||||||
|
docker info >/dev/null 2>&1 || fail "Docker 未运行或无权限,请确认 docker 服务已启动且当前用户在 docker 组"
|
||||||
|
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},请设置 IMAGE_TAR 或先执行 bash deploy.sh build"
|
||||||
|
info "加载离线镜像: ${IMAGE_TAR}"
|
||||||
|
docker load -i "${IMAGE_TAR}"
|
||||||
|
ok "镜像加载完成: ${IMAGE_TAG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_image() {
|
||||||
|
info "构建 Docker 镜像: ${IMAGE_TAG}"
|
||||||
|
export IMAGE_TAG
|
||||||
|
"${COMPOSE[@]}" build gateway
|
||||||
|
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 "启动容器..."
|
||||||
|
"${COMPOSE[@]}" up -d --remove-orphans
|
||||||
|
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}"
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# 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"]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/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}"
|
||||||
Reference in new issue
Block a user