From d5ce3ac7ef195311e33800ce4c87f1ad2881b4cb Mon Sep 17 00:00:00 2001 From: Petrus Han Date: Thu, 20 Nov 2025 18:19:37 +0800 Subject: [PATCH] Initial commit: Dify Helm Chart Values Generator - Add interactive Python script for generating values-prd.yaml - Add comprehensive documentation in docs/ - Add pyproject.toml and requirements.txt - Add .gitignore to exclude sensitive files - Add README.md with project overview and usage instructions --- .gitignore | 45 ++ .helmignore | 3 + README.md | 154 +++++ docs/CHANGELOG.md | 119 ++++ docs/DOCUMENTATION-UPDATE.md | 149 ++++ docs/FLOWCHART.md | 332 +++++++++ docs/IMPROVEMENTS.md | 90 +++ docs/KIND-NETWORKING.md | 210 ++++++ docs/MODULES.md | 207 ++++++ docs/README-GENERATOR.md | 301 ++++++++ generate-values-prd.py | 1244 ++++++++++++++++++++++++++++++++++ pyproject.toml | 40 ++ requirements.txt | 11 + values.yaml | 981 +++++++++++++++++++++++++++ 14 files changed, 3886 insertions(+) create mode 100644 .gitignore create mode 100644 .helmignore create mode 100644 README.md create mode 100644 docs/CHANGELOG.md create mode 100644 docs/DOCUMENTATION-UPDATE.md create mode 100644 docs/FLOWCHART.md create mode 100644 docs/IMPROVEMENTS.md create mode 100644 docs/KIND-NETWORKING.md create mode 100644 docs/MODULES.md create mode 100644 docs/README-GENERATOR.md create mode 100755 generate-values-prd.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 values.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..feb0576 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ + +# Virtual environments +venv/ +.venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Test outputs +values-test*.yaml +*.log + +# Generated configuration files (may contain sensitive data) +values-prd.yaml + +# Sensitive files +email-server.txt +*password* +*secret* +*.key +*.pem + +# Temporary files +*.tmp +*.bak +values-prd.yaml diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..59a4cac --- /dev/null +++ b/.helmignore @@ -0,0 +1,3 @@ +.idea +output.yaml +values/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..36aa22f --- /dev/null +++ b/README.md @@ -0,0 +1,154 @@ +# Dify Helm Chart Values Generator + +一个交互式工具,用于生成 Dify Enterprise Edition 的 Helm Chart 生产环境配置文件。 + +## 📋 项目简介 + +本项目提供了一个 Python 脚本 `generate-values-prd.py`,通过交互式引导帮助用户生成 `values-prd.yaml` 配置文件。脚本采用模块化设计,自动处理配置项之间的联动关系,确保配置的一致性和正确性。 + +## ✨ 功能特点 + +- ✅ **模块化配置**: 将配置分为5个主要模块,逻辑清晰 +- ✅ **自动处理联动**: 自动处理互斥选择和依赖关系 +- ✅ **密钥自动生成**: 所有密钥按注释要求自动生成(使用 openssl) + - `appSecretKey`: 42字节 + - `innerApiKey`: 42字节 + - `enterprise.appSecretKey`: 42字节 + - `enterprise.adminAPIsSecretKeySalt`: 42字节 + - `enterprise.passwordEncryptionKey`: 32字节(AES-256) +- ✅ **TLS联动检查**: TLS配置与Ingress联动,自动检查一致性避免CORS问题 +- ✅ **RAG联动**: 自动处理RAG类型与unstructured模块的联动关系 +- ✅ **交互式引导**: 友好的命令行交互界面,详细配置每个数据库和Redis连接 +- ✅ **进度保存**: 支持中断后保存部分配置 + +## 🚀 快速开始 + +### 前置要求 + +- Python 3.6+ +- PyYAML 库 +- openssl(用于生成密钥,通常系统已自带) +- ruamel.yaml(推荐):用于保留 YAML 文件的格式、注释和引号 + +### 安装依赖 + +**使用 uv(推荐,更快):** + +```bash +# 1. 安装 uv(如果未安装) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# 2. 创建虚拟环境 +uv venv + +# 3. 激活虚拟环境(可选,uv 会自动检测) +source .venv/bin/activate + +# 4. 安装依赖 +uv pip install -r requirements.txt +``` + +**或使用 pip:** + +```bash +pip install -r requirements.txt +``` + +### 使用方法 + +```bash +python generate-values-prd.py +``` + +脚本会引导你完成以下配置模块: + +1. **全局配置模块** - 影响所有服务 +2. **基础设施模块** - 数据库、存储、缓存(互斥选择) +3. **服务模块** - 应用服务配置 +4. **网络模块** - Ingress配置 +5. **邮件模块** - 邮件服务配置 + +生成的配置文件将保存为 `values-prd.yaml`。 + +## 📁 项目结构 + +``` +. +├── generate-values-prd.py # 主脚本文件 +├── values.yaml # 基础配置文件模板 +├── values-prd.yaml # 生成的生产环境配置(gitignore) +├── pyproject.toml # Python 项目配置 +├── requirements.txt # Python 依赖列表 +├── .gitignore # Git 忽略文件配置 +└── docs/ # 文档目录 + ├── README-GENERATOR.md # 详细使用说明 + ├── MODULES.md # 模块划分说明 + ├── FLOWCHART.md # 流程图 + ├── KIND-NETWORKING.md # Kind 网络配置说明 + ├── IMPROVEMENTS.md # 改进记录 + └── CHANGELOG.md # 更新日志 +``` + +## 📚 文档 + +详细的文档请参考 `docs/` 目录: + +- [README-GENERATOR.md](docs/README-GENERATOR.md) - 完整的使用说明和示例 +- [MODULES.md](docs/MODULES.md) - 模块划分与联动关系说明 +- [FLOWCHART.md](docs/FLOWCHART.md) - 配置流程图 +- [KIND-NETWORKING.md](docs/KIND-NETWORKING.md) - Kind 集群网络配置说明 + +## 🔧 配置说明 + +### 模块划分 + +1. **全局配置模块 (global)** + - 影响所有服务 + - 包括密钥、域名、RAG配置等 + +2. **基础设施模块** + - 数据库选择(PostgreSQL/MySQL) + - 存储选择(MinIO/S3) + - 缓存选择(Redis) + +3. **服务模块** + - 应用服务配置 + - 资源限制 + - 副本数量 + +4. **网络模块** + - Ingress配置 + - TLS设置 + +5. **邮件模块** + - SMTP服务器配置 + - 邮件服务设置 + +### 联动关系 + +脚本会自动处理以下联动关系: + +- **RAG联动**: `rag.etlType = "dify"` → `unstructured.enabled = false` +- **RAG联动**: `rag.etlType = "Unstructured"` → `unstructured.enabled = true` +- **TLS联动**: TLS配置与Ingress自动同步,避免CORS问题 +- **基础设施互斥**: 数据库、存储、缓存的选择互斥 + +## 🔒 安全注意事项 + +- 生成的 `values-prd.yaml` 包含敏感信息,已添加到 `.gitignore` +- `email-server.txt` 等敏感文件不会被提交到仓库 +- 所有密钥使用 `openssl` 自动生成,确保安全性 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 📝 许可证 + +请查看项目根目录的 LICENSE 文件(如有)。 + +## 🔗 相关链接 + +- [Dify 官方文档](https://docs.dify.ai/) +- [Helm Chart 文档](https://helm.sh/docs/) + diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..8b04f0a --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,119 @@ +# 脚本更新日志 + +## 最新更新 (2024) + +### 全局配置模块改进 + +1. **密钥自动生成** + - ✅ 所有密钥(`appSecretKey`, `innerApiKey`)都按照注释要求自动生成 + - ✅ 使用 `openssl rand -base64 42` 生成42字节密钥 + - ✅ 移除了手动输入选项,统一使用自动生成 + +2. **TLS 配置联动** + - ✅ TLS 配置从全局配置模块移到网络配置模块 + - ✅ TLS 配置与 Ingress 配置联动,避免 CORS 跨域问题 + - ✅ 自动检查 TLS 一致性,提供警告和建议 + +3. **RAG 联动关系** + - ✅ 当 RAG ETL 类型选择 `dify` 时,自动关闭 `unstructured` 模块 + - ✅ 当选择 `Unstructured` 时,自动启用 `unstructured` 模块 + +### 基础设施配置模块改进 + +1. **PostgreSQL 配置增强** + - ✅ 每个数据库(dify, plugin_daemon, enterprise, audit)的配置信息完全交互式获取 + - ✅ 包括:数据库名、用户名、密码、SSL模式、额外参数、字符集、URI方案 + - ✅ 清晰的数据库描述和配置提示 + +2. **Redis 配置增强** + - ✅ 完整的交互式配置,包括所有连接参数 + - ✅ Sentinel 和 Cluster 配置互斥选择 + - ✅ Sentinel 配置包括:节点列表、服务名、用户名、密码、socket超时 + - ✅ Cluster 配置包括:节点列表、密码 + +3. **存储配置改进** + - ✅ `persistence.type = s3` 时也可以配置 MinIO + - ✅ S3 服务提供商选择(AWS S3 / MinIO / Cloudflare R2 / 其他兼容S3服务) + - ✅ `useAwsS3` 自动设置: + - AWS S3 → `useAwsS3 = true` + - MinIO 或其他兼容S3服务 → `useAwsS3 = false` + - ✅ 支持 S3 地址类型配置(path-style/virtual-hosted-style) + +### 网络配置模块新增 + +1. **TLS 配置** + - ✅ TLS 配置移至网络配置模块 + - ✅ 与 Ingress 配置联动 + - ✅ 自动检查 TLS 一致性 + - ✅ 支持 cert-manager 自动证书管理 + - ✅ 提供 TLS 主机列表配置 + +2. **Ingress 配置增强** + - ✅ 完整的 TLS 配置支持 + - ✅ cert-manager 集成 + - ✅ useIpAsHost 配置 + +### Enterprise 配置改进 + +1. **密钥自动生成** + - ✅ `appSecretKey` 自动生成(42字节) + - ✅ `adminAPIsSecretKeySalt` 自动生成(42字节) + - ✅ `passwordEncryptionKey` 自动生成(32字节,AES-256密钥) + +## 配置联动关系总结 + +### 1. TLS 联动 +``` +全局 TLS (global.useTLS) ↔ Ingress TLS +- 必须保持一致,否则会出现 CORS 跨域问题 +- 脚本会自动检查并提示 +``` + +### 2. RAG 联动 +``` +RAG ETL 类型 = "dify" → unstructured.enabled = false +RAG ETL 类型 = "Unstructured" → unstructured.enabled = true +``` + +### 3. PostgreSQL 联动 +``` +externalPostgres.enabled = true ↔ postgresql.enabled = false (互斥) +``` + +### 4. Redis 联动 +``` +externalRedis.enabled = true ↔ redis.enabled = false (互斥) +Sentinel 和 Cluster 互斥 +``` + +### 5. VectorDB 联动 +``` +vectorDB.useExternal = true ↔ qdrant/weaviate.enabled = false (互斥) +``` + +### 6. 存储联动 +``` +persistence.type = "s3" + 选择 MinIO → 可以同时配置 MinIO 服务 +persistence.type != "s3" → 可以配置 MinIO 作为对象存储 +useAwsS3 = true (仅当选择 AWS S3) +useAwsS3 = false (MinIO 或其他兼容S3服务) +``` + +## 使用建议 + +1. **密钥管理** + - 所有密钥自动生成,请妥善保管 + - 建议将密钥存储在密钥管理系统中 + +2. **TLS 配置** + - 生产环境建议启用 TLS + - 确保全局 TLS 和 Ingress TLS 配置一致 + +3. **存储配置** + - 如果使用 S3 兼容存储,注意 `useAwsS3` 设置 + - MinIO 可以作为独立服务或 S3 兼容存储使用 + +4. **数据库配置** + - 外部数据库需要完整配置所有4个数据库的凭证 + - 注意 SSL 模式设置,生产环境建议使用 `require` 或更高 + diff --git a/docs/DOCUMENTATION-UPDATE.md b/docs/DOCUMENTATION-UPDATE.md new file mode 100644 index 0000000..9f3c0eb --- /dev/null +++ b/docs/DOCUMENTATION-UPDATE.md @@ -0,0 +1,149 @@ +# 文档更新总结 + +## 更新日期 +2024年 + +## 更新内容 + +### 1. FLOWCHART.md - 流程图文档 + +#### 模块1流程图更新 +- ✅ 移除TLS配置(已移至模块3) +- ✅ 更新密钥生成流程:所有密钥自动生成 +- ✅ 添加RAG联动关系: + - `rag.etlType = "dify"` → `unstructured.enabled = false` + - `rag.etlType = "Unstructured"` → `unstructured.enabled = true` +- ✅ 添加edition默认值说明 + +#### 模块2流程图更新 +- ✅ 更新PostgreSQL配置:交互式配置4个数据库的完整信息 +- ✅ 更新Redis配置:添加Sentinel/Cluster互斥选择 +- ✅ 更新存储配置: + - S3提供商选择(AWS S3/MinIO/Cloudflare R2/其他) + - `useAwsS3` 自动设置逻辑 + - S3类型也可以配置MinIO服务 +- ✅ 更新MinIO配置:自动生成密码 + +#### 模块3流程图更新 +- ✅ 添加全局TLS配置(`global.useTLS`) +- ✅ 添加TLS一致性检查流程 +- ✅ 添加cert-manager支持 +- ✅ 添加TLS联动警告和处理流程 + +#### 模块5流程图更新 +- ✅ 更新Enterprise密钥生成:所有密钥自动生成 +- ✅ 添加passwordEncryptionKey生成说明 + +### 2. MODULES.md - 模块说明文档 + +#### 全局配置模块更新 +- ✅ 更新密钥说明:所有密钥自动生成 +- ✅ 移除TLS配置说明(已移至网络模块) +- ✅ 添加RAG联动关系说明 +- ✅ 添加edition默认值说明 + +#### 基础设施配置模块更新 +- ✅ 更新PostgreSQL配置说明:交互式配置详细信息 +- ✅ 更新Redis配置说明:Sentinel/Cluster互斥,完整配置项 +- ✅ 更新存储配置说明: + - S3提供商选择 + - `useAwsS3` 自动设置逻辑 + - S3类型也可以配置MinIO + +#### 网络配置模块更新 +- ✅ 添加全局TLS配置说明 +- ✅ 添加TLS联动关系说明(重要) +- ✅ 添加cert-manager支持说明 +- ✅ 添加TLS一致性检查说明 + +#### 服务配置模块更新 +- ✅ 更新Enterprise配置说明:所有密钥自动生成 +- ✅ 添加passwordEncryptionKey说明 + +#### 关键联动点检查清单更新 +- ✅ 添加密钥自动生成标记 +- ✅ 添加RAG联动检查项 +- ✅ 添加Redis Sentinel/Cluster互斥检查 +- ✅ 添加S3存储useAwsS3检查 +- ✅ 添加TLS联动检查(重要) + +### 3. FLOWCHART.md - 流程图(Mermaid 格式) + +- ✅ 更新模块1流程图 +- ✅ 更新模块2流程图(详细配置说明) +- ✅ 更新模块3流程图(TLS配置) + +> 注:已移除 flowchart-text.txt 和 generate-flowchart.py,统一使用 FLOWCHART.md(Mermaid 格式) +- ✅ 更新模块5流程图文本(密钥自动生成) +- ✅ 更新关键决策点和联动关系说明 + +### 4. README-GENERATOR.md - 使用说明 + +#### 功能特点更新 +- ✅ 更新密钥生成说明:所有密钥自动生成 +- ✅ 添加TLS联动检查说明 +- ✅ 添加RAG联动说明 + +#### 配置流程更新 +- ✅ 更新各模块配置说明 +- ✅ 添加详细配置项说明 +- ✅ 添加自动生成和联动说明 + +#### 关键联动点更新 +- ✅ 添加密钥自动生成说明 +- ✅ 添加RAG联动说明 +- ✅ 添加TLS联动说明(重要) +- ✅ 添加存储配置useAwsS3说明 + +#### 注意事项更新 +- ✅ 添加TLS配置注意事项 +- ✅ 添加RAG配置注意事项 +- ✅ 添加存储配置注意事项 + +## 主要变化总结 + +### 1. 密钥管理 +- **之前**: 可选择自动生成或手动输入 +- **现在**: 所有密钥自动生成,按注释要求使用正确的长度 + +### 2. TLS配置 +- **之前**: 在全局配置模块 +- **现在**: 移至网络配置模块,与Ingress TLS联动 + +### 3. RAG配置 +- **之前**: 仅配置RAG参数 +- **现在**: 自动联动unstructured模块的启用状态 + +### 4. PostgreSQL配置 +- **之前**: 简单配置4个数据库凭证 +- **现在**: 交互式配置每个数据库的完整信息(数据库名、用户名、密码、SSL、参数、字符集、URI方案) + +### 5. Redis配置 +- **之前**: 简单配置连接信息 +- **现在**: 完整交互式配置,支持Sentinel/Cluster互斥选择 + +### 6. 存储配置 +- **之前**: S3类型简单配置 +- **现在**: 选择S3提供商,自动设置useAwsS3,可同时配置MinIO服务 + +### 7. Enterprise配置 +- **之前**: 可选择生成或手动输入密钥 +- **现在**: 所有密钥自动生成,包括passwordEncryptionKey + +## 文档一致性 + +所有文档已更新,确保: +- ✅ 流程图与实际脚本逻辑一致 +- ✅ 模块说明与实际功能一致 +- ✅ 联动关系说明准确 +- ✅ 使用说明清晰完整 + +## 相关文件 + +- `generate-values-prd.py` - 主脚本(已更新) +- `FLOWCHART.md` - Mermaid流程图(已更新) +- `MODULES.md` - 模块说明(已更新) +- `FLOWCHART.md` - 流程图(Mermaid 格式,已更新) +- `README-GENERATOR.md` - 使用说明(已更新) +- `CHANGELOG.md` - 更新日志(已创建) + diff --git a/docs/FLOWCHART.md b/docs/FLOWCHART.md new file mode 100644 index 0000000..c50d261 --- /dev/null +++ b/docs/FLOWCHART.md @@ -0,0 +1,332 @@ +# Dify Helm Chart Values 生成器流程图 + +## 主流程图 + +```mermaid +flowchart TD + Start([开始]) --> LoadTemplate[加载 values.yaml 模板] + LoadTemplate --> ShowHeader[显示欢迎信息] + ShowHeader --> Module1[模块1: 全局配置] + + Module1 --> Module2[模块2: 基础设施配置] + Module2 --> Module3[模块3: 网络配置] + Module3 --> Module4[模块4: 邮件配置] + Module4 --> Module5[模块5: 服务配置] + + Module5 --> CheckFile{文件是否存在?} + CheckFile -->|是| AskOverwrite{是否覆盖?} + CheckFile -->|否| SaveFile[保存 values-prd.yaml] + AskOverwrite -->|是| SaveFile + AskOverwrite -->|否| NewFileName[输入新文件名] + NewFileName --> SaveFile + + SaveFile --> Success([配置完成]) + + Start -.->|Ctrl+C| Interrupt[用户中断] + Interrupt --> AskSave{保存进度?} + AskSave -->|是| SavePartial[保存部分配置] + AskSave -->|否| Exit([退出]) + SavePartial --> Exit + + style Start fill:#90EE90 + style Success fill:#90EE90 + style Exit fill:#FFB6C1 + style Interrupt fill:#FFB6C1 +``` + +## 模块1: 全局配置流程图 + +```mermaid +flowchart TD + Start([模块1开始]) --> GenAppSecret[自动生成 appSecretKey
openssl rand -base64 42] + GenAppSecret --> GenInnerSecret[自动生成 innerApiKey
openssl rand -base64 42] + + GenInnerSecret --> Domains[配置域名
consoleApiDomain
consoleWebDomain
serviceApiDomain
appApiDomain
appWebDomain
filesDomain
enterpriseDomain] + + Domains --> DBMigration[数据库迁移开关] + DBMigration --> RAGType[选择RAG ETL类型
dify/Unstructured] + + RAGType --> RAGCheck{RAG类型?} + RAGCheck -->|dify| DisableUnstructured[关闭unstructured模块
unstructured.enabled = false] + RAGCheck -->|Unstructured| EnableUnstructured[启用unstructured模块
unstructured.enabled = true] + + DisableUnstructured --> RAGConfig + EnableUnstructured --> RAGConfig + + RAGConfig[配置RAG参数
keywordDataSourceType
topKMaxValue
indexingMaxSegmentationTokensLength] + + RAGConfig --> End([模块1完成]) + + style Start fill:#E6F3FF + style End fill:#E6F3FF + style RAGCheck fill:#FFF4E6 + style DisableUnstructured fill:#FFE6E6 + style EnableUnstructured fill:#E6FFE6 +``` + +## 模块2: 基础设施配置流程图 + +```mermaid +flowchart TD + Start([模块2开始]) --> PostgresChoice{使用外部
PostgreSQL?} + + PostgresChoice -->|是| ExtPostgres[配置外部PostgreSQL
address, port
交互式配置4个数据库] + PostgresChoice -->|否| IntPostgres[使用内置PostgreSQL
自动生成root密码] + + ExtPostgres --> PostgresDBs[配置每个数据库
dify: 数据库名/用户名/密码/SSL
plugin_daemon: 完整配置
enterprise: 完整配置
audit: 完整配置] + PostgresDBs --> RedisChoice + IntPostgres --> RedisChoice + + RedisChoice{使用外部
Redis?} -->|是| ExtRedis[配置外部Redis
host, port, password
username, db, useSSL] + RedisChoice -->|否| IntRedis[使用内置Redis
自动生成密码] + + ExtRedis --> RedisMode{Redis模式?} + RedisMode -->|Sentinel| RedisSentinel[配置Sentinel
nodes, serviceName
username, password
socketTimeout] + RedisMode -->|Cluster| RedisCluster[配置Cluster
nodes, password] + RedisMode -->|标准| VectorDBChoice + + RedisSentinel --> VectorDBChoice + RedisCluster --> VectorDBChoice + IntRedis --> VectorDBChoice + + VectorDBChoice{使用外部
向量数据库?} -->|是| ExtVectorDB[选择类型
qdrant/weaviate/milvus等
配置连接信息] + VectorDBChoice -->|否| IntVectorDB[选择内置类型
qdrant/weaviate
配置API Key] + + ExtVectorDB --> StorageType + IntVectorDB --> StorageType + + StorageType[选择存储类型
local/s3/azure-blob等] --> StorageConfig{存储类型?} + + StorageConfig -->|local| LocalStorage[配置本地存储
mountPath
storageClass
size] + StorageConfig -->|s3| S3Provider[选择S3提供商
AWS S3/MinIO/Cloudflare R2/其他] + StorageConfig -->|其他| CloudStorage[配置云存储
对应凭证] + + S3Provider --> S3Config{提供商?} + S3Config -->|AWS S3| S3AWS[配置AWS S3
useAwsS3 = true
endpoint/credentials/bucket
minio.enabled = false] + S3Config -->|MinIO/其他| S3Compatible[配置S3兼容
useAwsS3 = false
endpoint/credentials/bucket
minio.enabled = true] + + S3AWS --> End + S3Compatible --> ConfigMinIO[配置MinIO
自动生成rootPassword
rootUser] + + LocalStorage --> MinIOCheck + CloudStorage --> MinIOCheck + + MinIOCheck[存储类型不是s3
minio.enabled = true] --> ConfigMinIO + + ConfigMinIO --> End([模块2完成]) + + style Start fill:#E6F3FF + style End fill:#E6F3FF + style PostgresChoice fill:#FFF4E6 + style RedisChoice fill:#FFF4E6 + style RedisMode fill:#FFF4E6 + style StorageConfig fill:#FFF4E6 + style S3Provider fill:#FFF4E6 + style S3Config fill:#FFF4E6 + style S3MinIO fill:#FFF4E6 + style MinIO fill:#FFF4E6 + style RedisChoice fill:#FFF4E6 + style VectorDBChoice fill:#FFF4E6 + style StorageConfig fill:#FFF4E6 + style MinIO fill:#FFF4E6 +``` + +## 模块3: 网络配置流程图 + +```mermaid +flowchart TD + Start([模块3开始]) --> TLSConfig[配置全局TLS
global.useTLS
影响内部服务通信和CORS] + + TLSConfig --> EnableIngress{启用
Ingress?} + + EnableIngress -->|是| ConfigIngress[配置Ingress
className] + EnableIngress -->|否| End + + ConfigIngress --> IngressTLS{配置Ingress
TLS?} + + IngressTLS -->|是| TLSDetails[配置TLS详情
hosts列表
secretName] + IngressTLS -->|否| TLSCheck + + TLSDetails --> CertManager{使用cert-manager?} + CertManager -->|是| CertConfig[配置ClusterIssuer
添加annotations] + CertManager -->|否| TLSCheck + + CertConfig --> TLSCheck + TLSCheck{检查TLS
一致性} + + TLSCheck -->|不一致| TLSWarning[警告: CORS问题
提示修复] + TLSWarning --> TLSAutoFix{自动修复?} + TLSAutoFix -->|是| TLSDetails + TLSAutoFix -->|否| End + + TLSCheck -->|一致| UseIpHost[配置useIpAsHost
仅非企业版] + UseIpHost --> End([模块3完成]) + + style Start fill:#E6F3FF + style End fill:#E6F3FF + style TLSConfig fill:#FFE6E6 + style EnableIngress fill:#FFF4E6 + style IngressTLS fill:#FFF4E6 + style TLSCheck fill:#FFE6E6 + style TLSWarning fill:#FFB6C1 +``` + +## 模块4: 邮件配置流程图 + +```mermaid +flowchart TD + Start([模块4开始]) --> MailType[选择邮件类型
空/resend/smtp] + + MailType --> TypeCheck{邮件类型?} + + TypeCheck -->|空| End + TypeCheck -->|resend| ResendConfig[配置Resend
apiKey
apiUrl] + TypeCheck -->|smtp| SMTPConfig[配置SMTP
server, port
username, password
useTLS] + + ResendConfig --> DefaultSender + SMTPConfig --> DefaultSender + + DefaultSender[配置默认发件人] --> End([模块4完成]) + + style Start fill:#E6F3FF + style End fill:#E6F3FF +``` + +## 模块5: 服务配置流程图 + +```mermaid +flowchart TD + Start([模块5开始]) --> CheckEnterprise{Enterprise
已启用?} + + CheckEnterprise -->|是| GenEnterpriseKeys[自动生成Enterprise密钥
appSecretKey: 42字节
adminAPIsSecretKeySalt: 42字节
passwordEncryptionKey: 32字节] + + GenEnterpriseKeys --> LicenseMode[配置License模式
online/offline] + + LicenseMode --> LicenseCheck{License模式?} + LicenseCheck -->|online| LicenseServer[配置License服务器
licenseServer URL] + LicenseCheck -->|offline| ServiceToggle + + LicenseServer --> ServiceToggle + CheckEnterprise -->|否| ServiceToggle + + ServiceToggle{配置服务
启用状态?} + + ServiceToggle -->|是| ToggleServices[逐个配置服务
api, worker, web等
enterprise, enterpriseAudit等] + ServiceToggle -->|否| End + + ToggleServices --> End([模块5完成]) + + style Start fill:#E6F3FF + style End fill:#E6F3FF + style GenEnterpriseKeys fill:#E6FFE6 + style LicenseCheck fill:#FFF4E6 +``` + +## 完整交互流程图 + +```mermaid +flowchart TD + Start([脚本启动]) --> Init[初始化ValuesGenerator] + Init --> LoadYAML[加载values.yaml模板] + LoadYAML --> MainFlow[主流程开始] + + MainFlow --> M1[模块1: 全局配置] + M1 --> M1Q2[自动生成密钥] + M1Q2 --> M1Q3[配置域名] + M1Q3 --> M1Q4[配置RAG] + + M1Q4 --> M2[模块2: 基础设施] + M2 --> M2Q1{PostgreSQL选择} + M2Q1 -->|外部| M2Q1A[配置外部连接] + M2Q1 -->|内置| M2Q1B[配置内置密码] + + M2Q1A --> M2Q2 + M2Q1B --> M2Q2{Redis选择} + M2Q2 -->|外部| M2Q2A[配置外部连接] + M2Q2 -->|内置| M2Q2B[配置内置密码] + + M2Q2A --> M2Q3 + M2Q2B --> M2Q3{VectorDB选择} + M2Q3 -->|外部| M2Q3A[配置外部连接] + M2Q3 -->|内置| M2Q3B[配置内置] + + M2Q3A --> M2Q4 + M2Q3B --> M2Q4{存储类型} + M2Q4 --> M2Q4A[配置存储] + M2Q4A --> M2Q5{配置MinIO?} + M2Q5 --> M3 + + M3[模块3: 网络] --> M3Q1{启用Ingress?} + M3Q1 --> M4 + + M4[模块4: 邮件] --> M4Q1{邮件类型} + M4Q1 --> M5 + + M5[模块5: 服务] --> M5Q1{Enterprise配置} + M5Q1 --> M5Q2{服务状态} + M5Q2 --> Save + + Save[保存文件] --> Validate[验证配置] + Validate --> Success([完成]) + + Start -.->|异常| Error[错误处理] + Error --> Exit([退出]) + + style Start fill:#90EE90 + style Success fill:#90EE90 + style Error fill:#FFB6C1 + style Exit fill:#FFB6C1 +``` + +## 决策点说明 + +### 关键决策点 + +1. **PostgreSQL选择** (互斥) + - 外部: `externalPostgres.enabled = true` + - 内置: `postgresql.enabled = true` + +2. **Redis选择** (互斥) + - 外部: `externalRedis.enabled = true` + - 内置: `redis.enabled = true` + +3. **VectorDB选择** (互斥) + - 外部: `vectorDB.useExternal = true` + - 内置: `qdrant.enabled` 或 `weaviate.enabled = true` + +4. **存储类型** (单选) + - local, s3, azure-blob, aliyun-oss, google-storage, tencent-cos, volcengine-tos, huawei-obs + +5. **邮件类型** (单选) + - 空(不配置), resend, smtp + +### 联动关系 + +- **存储类型 → MinIO**: 如果 `persistence.type != "s3"`,需要配置MinIO +- **外部服务 → 连接信息**: 选择外部服务后,必须配置对应的连接信息 +- **Enterprise → License**: Enterprise服务需要License配置 + +## 使用示例流程 + +```mermaid +sequenceDiagram + participant User as 用户 + participant Script as 生成脚本 + participant File as values-prd.yaml + + User->>Script: 运行脚本 + Script->>User: 显示欢迎信息 + Script->>Script: 自动生成密钥 + Script->>User: 显示已生成密钥 + Script->>User: 配置域名 + User->>Script: 输入域名 + Script->>User: 模块2: PostgreSQL选择 + User->>Script: 外部 + Script->>User: 输入连接信息 + User->>Script: 输入信息 + Script->>User: 模块3-5: 继续配置 + User->>Script: 完成所有配置 + Script->>File: 保存配置 + Script->>User: 显示完成信息 +``` + diff --git a/docs/IMPROVEMENTS.md b/docs/IMPROVEMENTS.md new file mode 100644 index 0000000..34630f4 --- /dev/null +++ b/docs/IMPROVEMENTS.md @@ -0,0 +1,90 @@ +# 脚本改进说明 + +## 问题描述 + +用户在使用 `generate-values-prd.py` 生成 `values-prd.yaml` 时发现了以下问题: + +1. **双引号丢失**:YAML 中的字符串引号在生成后丢失 +2. **注释丢失**:`extraEnv` 等重要注释在生成后丢失 +3. **格式错误**:多行字符串(如 `squidConf`)被错误转义 + +## 解决方案 + +### 方案 1:使用 ruamel.yaml(推荐) + +**优点:** +- 完美保留原始格式、注释和引号 +- 支持多行字符串(`|` 格式) +- 保留 YAML 的原始风格 + +**使用方法:** +```bash +pip install ruamel.yaml +``` + +脚本会自动检测并使用 ruamel.yaml,如果未安装会回退到标准 yaml 库。 + +### 方案 2:文本替换方式(回退方案) + +如果 ruamel.yaml 未安装,脚本会使用标准 yaml 库,但会丢失注释和格式。 + +## 改进内容 + +### 1. 加载阶段 + +- 尝试使用 `ruamel.yaml` 加载原始文件(保留注释和格式) +- 同时加载为标准字典用于配置逻辑 +- 如果 ruamel.yaml 不可用,回退到标准 yaml + +### 2. 保存阶段 + +- 重新加载原始文件(使用 ruamel.yaml) +- 递归更新修改的值 +- 保存时保留所有格式、注释和引号 + +### 3. 关键改进 + +```python +# 保存时重新加载原始文件 +with open(self.source_file, 'r', encoding='utf-8') as f: + data = yaml_loader.load(f) + +# 只更新修改的值,保留其他所有内容 +self._update_dict_recursive(data, self.values) + +# 保存 +yaml_loader.dump(data, f) +``` + +## 验证方法 + +运行测试脚本验证 ruamel.yaml 是否正常工作: + +```bash +python3 test-ruamel.py +``` + +然后检查生成的 `values-test-ruamel.yaml` 文件,确认: +1. 注释是否保留 +2. 引号是否保留 +3. 多行字符串格式是否正确(如 `squidConf`) +4. `extraEnv` 的注释是否保留 + +## 注意事项 + +1. **推荐安装 ruamel.yaml**:为了获得最佳体验,建议安装 `ruamel.yaml` +2. **回退机制**:如果未安装 ruamel.yaml,脚本仍可使用,但会丢失注释和格式 +3. **兼容性**:脚本与标准 yaml 库完全兼容 + +## 安装 ruamel.yaml + +```bash +pip install ruamel.yaml +``` + +或使用 requirements.txt: + +``` +ruamel.yaml>=0.18.0 +``` + diff --git a/docs/KIND-NETWORKING.md b/docs/KIND-NETWORKING.md new file mode 100644 index 0000000..d348c3c --- /dev/null +++ b/docs/KIND-NETWORKING.md @@ -0,0 +1,210 @@ +# Kind 集群网络配置指南 + +## 概述 + +在 kind (Kubernetes in Docker) 集群中运行时,访问集群外部(同一主机上)的服务需要特殊配置。 + +## 访问集群外服务 + +### 从集群内访问宿主机服务 + +在 kind 集群中,Pod 需要访问运行在宿主机上的服务时,可以使用以下方式: + +#### 1. 使用 `host.docker.internal`(推荐) + +`host.docker.internal` 是 Docker 提供的特殊 DNS 名称,指向宿主机。 + +**示例配置:** +- PostgreSQL: `host.docker.internal` +- Redis: `host.docker.internal` +- Qdrant: `http://host.docker.internal:6333` + +**优点:** +- 跨平台兼容(Windows、macOS、Linux) +- 不需要知道宿主机 IP 地址 +- 配置简单 + +#### 2. 使用宿主机 IP 地址 + +如果 `host.docker.internal` 不可用,可以使用宿主机 IP。 + +**获取宿主机 IP:** +```bash +# Linux/macOS +ip route show default | awk '/default/ {print $3}' + +# 或使用 +docker network inspect bridge | grep Gateway +``` + +**示例配置:** +- PostgreSQL: `172.17.0.1`(Docker 默认网关) +- Redis: `172.17.0.1` + +**注意:** +- IP 地址可能因网络配置而变化 +- 需要确保防火墙允许访问 + +#### 3. 使用 `localhost`(不推荐) + +`localhost` 在 Pod 内指向 Pod 自身,**不能**用于访问宿主机服务。 + +**仅在以下情况可以使用:** +- 使用 NodePort 服务并配置了端口映射 +- 使用端口转发(port-forward) + +## 常见服务配置示例 + +### PostgreSQL + +**集群外(宿主机)PostgreSQL:** +```yaml +externalPostgres: + enabled: true + address: host.docker.internal # 或宿主机IP + port: 5432 +``` + +**集群内 PostgreSQL:** +```yaml +externalPostgres: + enabled: true + address: postgresql.default.svc.cluster.local # 或 postgresql + port: 5432 +``` + +### Redis + +**集群外(宿主机)Redis:** +```yaml +externalRedis: + enabled: true + host: host.docker.internal # 或宿主机IP + port: 6379 +``` + +**集群内 Redis:** +```yaml +externalRedis: + enabled: true + host: redis.default.svc.cluster.local # 或 redis + port: 6379 +``` + +### Qdrant + +**集群外(宿主机)Qdrant:** +```yaml +vectorDB: + useExternal: true + externalType: qdrant + externalQdrant: + endpoint: http://host.docker.internal:6333 +``` + +**集群内 Qdrant:** +```yaml +vectorDB: + useExternal: true + externalType: qdrant + externalQdrant: + endpoint: http://qdrant.default.svc.cluster.local:6333 +``` + +## Kind 特殊配置 + +### 启用 host.docker.internal 支持 + +如果 kind 集群不支持 `host.docker.internal`,可以在创建集群时添加配置: + +```yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +networking: + apiServerAddress: "127.0.0.1" + apiServerPort: 6443 +containers: + - name: kind-control-plane + extraMounts: + - hostPath: /var/run/docker.sock + containerPath: /var/run/docker.sock +``` + +### 端口映射 + +如果需要从宿主机访问集群内服务,可以使用: + +```bash +# NodePort 服务 +kubectl port-forward service/postgresql 5432:5432 + +# 或使用 Ingress +# 配置 Ingress 并设置端口映射 +``` + +## 验证连接 + +### 测试 host.docker.internal + +在 Pod 内测试: +```bash +kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup host.docker.internal +``` + +### 测试服务连接 + +```bash +# 测试 PostgreSQL +kubectl run -it --rm psql-test --image=postgres:15 --restart=Never -- \ + psql -h host.docker.internal -U postgres -d test + +# 测试 Redis +kubectl run -it --rm redis-test --image=redis:7 --restart=Never -- \ + redis-cli -h host.docker.internal ping +``` + +## 故障排查 + +### 问题1: 无法解析 host.docker.internal + +**解决方案:** +1. 检查 Docker 版本(需要 Docker Desktop 或较新版本) +2. 使用宿主机 IP 地址替代 +3. 检查 kind 集群网络配置 + +### 问题2: 连接被拒绝 + +**可能原因:** +1. 宿主机服务未监听在 `0.0.0.0`(应监听在 `0.0.0.0` 而不是 `127.0.0.1`) +2. 防火墙阻止连接 +3. 端口未正确映射 + +**解决方案:** +```bash +# 检查服务监听地址 +netstat -tlnp | grep 5432 + +# 确保服务监听在 0.0.0.0 +# PostgreSQL: listen_addresses = '*' in postgresql.conf +# Redis: bind 0.0.0.0 in redis.conf +``` + +### 问题3: 使用 localhost 无法连接 + +**原因:** `localhost` 在 Pod 内指向 Pod 自身,不是宿主机。 + +**解决方案:** 使用 `host.docker.internal` 或宿主机 IP。 + +## 最佳实践 + +1. **优先使用 `host.docker.internal`**:跨平台兼容,配置简单 +2. **服务监听地址**:确保宿主机服务监听在 `0.0.0.0` 而不是 `127.0.0.1` +3. **使用服务名访问集群内服务**:更稳定,不依赖 IP +4. **测试连接**:配置后使用测试 Pod 验证连接 + +## 相关资源 + +- [Kind 官方文档](https://kind.sigs.k8s.io/) +- [Docker 网络文档](https://docs.docker.com/network/) +- [Kubernetes 服务发现](https://kubernetes.io/docs/concepts/services-networking/service/) + diff --git a/docs/MODULES.md b/docs/MODULES.md new file mode 100644 index 0000000..8d36322 --- /dev/null +++ b/docs/MODULES.md @@ -0,0 +1,207 @@ +# Dify Helm Chart Values 配置模块划分与联动关系 + +## 模块划分 + +### 模块 1: 全局配置 (Global Configuration) +**影响范围**: 所有服务 + +**配置项**: +- `global.appSecretKey`: 会话签名和数据库加密密钥,**自动生成** (`openssl rand -base64 42`) +- `global.innerApiKey`: 内部API调用密钥,**自动生成** (`openssl rand -base64 42`) +- `global.*Domain`: 各种域名配置(影响CORS和API端点) +- `global.dbMigrationEnabled`: 数据库迁移开关 +- `global.rag`: RAG相关配置 + - `rag.etlType`: RAG ETL类型 (dify/Unstructured) + - `rag.keywordDataSourceType`: 关键词数据源类型 + - **说明**: RAG 关键词检索(Keyword Search)时的关键词存储位置 + - `object_storage`: 存储在对象存储中(如 MinIO、S3) + - `database`: 存储在数据库中 + - `rag.topKMaxValue`: Top-K最大值 + - `rag.indexingMaxSegmentationTokensLength`: 文档分块最大token长度 +- `global.integrations`: 第三方集成配置(如Notion) +- `global.marketplace`: 市场配置 + +**联动关系**: +- `global.appSecretKey` 与 `enterprise.appSecretKey` 可以相同或不同(都自动生成) +- **RAG联动**: `rag.etlType = "dify"` → `unstructured.enabled = false` +- **RAG联动**: `rag.etlType = "Unstructured"` → `unstructured.enabled = true` +- 域名配置影响 Ingress 和 CORS 设置 +- **注意**: `global.useTLS` 已移至网络配置模块,与 Ingress TLS 联动 + +--- + +### 模块 2: 基础设施配置 (Infrastructure) +**影响范围**: 数据持久化和服务依赖 + +#### 2.1 PostgreSQL 数据库 +**互斥选择**: +- `externalPostgres.enabled = true` → 使用外部PostgreSQL +- `postgresql.enabled = true` → 使用Helm Chart内置PostgreSQL + +**联动关系**: +- 如果使用外部PostgreSQL,需要**交互式配置**4个数据库的完整信息: + - `dify`: 主数据库(数据库名、用户名、密码、SSL模式、额外参数、字符集、URI方案) + - `plugin_daemon`: 插件守护进程数据库(完整配置) + - `enterprise`: 企业版数据库(完整配置) + - `audit`: 审计数据库(完整配置) +- 如果使用内置PostgreSQL,会自动创建这些数据库,root密码自动生成 + +#### 2.2 Redis 缓存 +**互斥选择**: +- `externalRedis.enabled = true` → 使用外部Redis +- `redis.enabled = true` → 使用Helm Chart内置Redis + +**联动关系**: +- Redis Sentinel 和 Cluster 配置**互斥** +- 如果使用外部Redis,需要**交互式配置**完整连接信息: + - 基础配置:host, port, password, username, db, useSSL + - Sentinel模式(可选):nodes, serviceName, username, password, socketTimeout + - Cluster模式(可选):nodes, password +- 如果使用内置Redis,密码自动生成 + +#### 2.3 向量数据库 (VectorDB) +**互斥选择**: +- `vectorDB.useExternal = true` → 使用外部向量数据库 +- `vectorDB.useExternal = false` → 使用内置向量数据库(qdrant/weaviate) + +**联动关系**: +- 如果使用外部向量数据库: + - `vectorDB.externalType` 决定配置哪个外部服务 + - 根据类型配置对应的连接信息(如 `externalQdrant`, `externalWeaviate`) +- 如果使用内置向量数据库: + - `qdrant.enabled = true` 或 `weaviate.enabled = true`(互斥) + +#### 2.4 存储配置 (Persistence) +**选择**: `persistence.type` 决定存储类型 +- `local`: 本地存储(需要PVC) +- `s3`: S3兼容存储(AWS S3, Cloudflare R2等) +- `azure-blob`: Azure Blob Storage +- `aliyun-oss`: 阿里云OSS +- `google-storage`: Google Cloud Storage +- `tencent-cos`: 腾讯云COS +- `volcengine-tos`: 火山引擎TOS +- `huawei-obs`: 华为云OBS + +**联动关系**: +- 如果 `persistence.type = "s3"`: + - 需要选择S3服务提供商(AWS S3 / MinIO / Cloudflare R2 / 其他兼容S3服务) + - **`useAwsS3` 自动设置**: + - AWS S3 → `useAwsS3 = true` + - MinIO 或其他兼容S3服务 → `useAwsS3 = false` +- 如果使用 `local` 存储,需要配置 StorageClass 和 PVC 大小 +- 如果使用云存储,需要配置对应的访问凭证 + +#### 2.5 MinIO 对象存储 +**联动关系(重要)**: +- **内置 MinIO 的启用逻辑**: + - `persistence.type = "s3"` && `useAwsS3 = true` (AWS S3) → `minio.enabled = false`(不启用) + - `persistence.type = "s3"` && `useAwsS3 = false` (MinIO或其他S3兼容) → `minio.enabled = true`(启用) + - `persistence.type != "s3"` (local/azure-blob等) → `minio.enabled = true`(启用) +- **注意**: 对象存储不能使用内置的 MinIO,内置 MinIO 仅在 persistence 不是 AWS S3 时需要开启 +- MinIO的root密码自动生成 + +--- + +### 模块 3: 网络配置 (Networking) +**配置项**: +- `global.useTLS`: **TLS配置已移至此处**,影响内部服务通信和CORS设置 +- `ingress.enabled`: 是否启用Ingress +- `ingress.className`: Ingress Controller类名 +- `ingress.annotations`: Ingress注解(支持cert-manager) +- `ingress.tls`: Ingress TLS配置(hosts列表、secretName) +- `ingress.useIpAsHost`: 是否使用IP地址作为主机名(仅非企业版) + +**联动关系**: +- **TLS联动(重要)**: `global.useTLS` 与 `ingress.tls` **必须保持一致**,否则会出现CORS跨域问题 +- 脚本会自动检查TLS一致性,并提供警告和建议 +- Ingress配置影响域名访问 +- 与 `global.*Domain` 配置联动 +- 支持cert-manager自动证书管理(通过annotations配置ClusterIssuer) + +--- + +### 模块 4: 邮件配置 (Mail Configuration) +**选择**: `mail.type` 决定邮件服务类型 +- `""`: 不配置邮件 +- `resend`: 使用Resend服务 +- `smtp`: 使用SMTP服务器 + +**联动关系**: +- 根据类型配置对应的凭证和端点 +- 影响用户注册、密码重置等功能的邮件发送 + +--- + +### 模块 5: 服务配置 (Services Configuration) +**服务列表**: +- `api`: API服务 +- `worker`: 工作进程 +- `workerBeat`: 定时任务 +- `web`: Web前端 +- `sandbox`: 沙箱环境 +- `enterprise`: 企业版服务 +- `enterpriseAudit`: 企业版审计服务 +- `enterpriseFrontend`: 企业版前端 +- `ssrfProxy`: SSRF代理 +- `unstructured`: 非结构化数据处理 +- `plugin_daemon`: 插件守护进程 +- `plugin_manager`: 插件管理器 +- `plugin_controller`: 插件控制器 +- `plugin_connector`: 插件连接器 +- `gateway`: 网关服务 + +**联动关系**: +- Enterprise相关服务需要License配置 +- **所有Enterprise密钥自动生成**: + - `enterprise.appSecretKey`: 42字节 (`openssl rand -base64 42`) + - `enterprise.adminAPIsSecretKeySalt`: 42字节 (`openssl rand -base64 42`) + - `enterprise.passwordEncryptionKey`: 32字节 (`openssl rand -base64 32`,AES-256密钥) +- License模式选择(online/offline): + - online: 需要配置licenseServer URL + - offline: 不需要licenseServer +- 服务之间的依赖关系: + - API → PostgreSQL, Redis, VectorDB + - Worker → PostgreSQL, Redis + - Enterprise → PostgreSQL, Redis + - Sandbox → SSRF Proxy + +--- + +## 配置流程建议 + +### 推荐顺序 +1. **全局配置** → 设置基础密钥和域名 +2. **基础设施配置** → 配置数据库、缓存、存储 +3. **网络配置** → 配置Ingress和TLS +4. **邮件配置** → 配置邮件服务(可选) +5. **服务配置** → 调整服务启用状态和Enterprise配置 + +### 关键联动点检查清单 + +- [x] `global.appSecretKey` 和 `enterprise.appSecretKey` **自动生成** +- [x] `global.innerApiKey` **自动生成** +- [x] `enterprise.passwordEncryptionKey` **自动生成** +- [ ] PostgreSQL: `externalPostgres.enabled` 和 `postgresql.enabled` 是否互斥 +- [ ] Redis: `externalRedis.enabled` 和 `redis.enabled` 是否互斥 +- [ ] Redis: Sentinel 和 Cluster 是否互斥 +- [ ] VectorDB: `vectorDB.useExternal` 与 `qdrant.enabled`/`weaviate.enabled` 是否一致 +- [ ] RAG联动: `rag.etlType = "dify"` → `unstructured.enabled = false` +- [ ] RAG联动: `rag.etlType = "Unstructured"` → `unstructured.enabled = true` +- [ ] 存储: `persistence.type` 与对应的存储配置是否匹配 +- [ ] S3存储: `useAwsS3` 是否正确设置(AWS S3 = true,其他 = false) +- [ ] MinIO: 如果 `persistence.type != "s3"` 或选择MinIO作为S3提供商,是否配置了MinIO +- [ ] **TLS联动(重要)**: `global.useTLS` 与 `ingress.tls` 是否一致(避免CORS问题) +- [ ] Ingress: 如果启用,域名配置是否完整 +- [ ] Enterprise: 如果启用,License配置是否完整(online模式需要licenseServer) + +--- + +## 使用脚本生成配置 + +运行交互式脚本: +```bash +python3 generate-values-prd.py +``` + +脚本会按模块顺序引导您完成配置,并自动处理模块间的联动关系。 + diff --git a/docs/README-GENERATOR.md b/docs/README-GENERATOR.md new file mode 100644 index 0000000..46cd0f0 --- /dev/null +++ b/docs/README-GENERATOR.md @@ -0,0 +1,301 @@ +# Dify Helm Chart Values 生成器使用说明 + +## 概述 + +`generate-values-prd.py` 是一个交互式脚本,用于生成生产环境的 `values-prd.yaml` 配置文件。脚本将配置过程分解为多个模块,并自动处理模块间的联动关系。 + +## 功能特点 + +- ✅ **模块化配置**: 将配置分为5个主要模块,逻辑清晰 +- ✅ **自动处理联动**: 自动处理互斥选择和依赖关系 +- ✅ **密钥自动生成**: 所有密钥按注释要求自动生成(使用 openssl) + - `appSecretKey`: 42字节 + - `innerApiKey`: 42字节 + - `enterprise.appSecretKey`: 42字节 + - `enterprise.adminAPIsSecretKeySalt`: 42字节 + - `enterprise.passwordEncryptionKey`: 32字节(AES-256) +- ✅ **TLS联动检查**: TLS配置与Ingress联动,自动检查一致性避免CORS问题 +- ✅ **RAG联动**: 自动处理RAG类型与unstructured模块的联动关系 +- ✅ **交互式引导**: 友好的命令行交互界面,详细配置每个数据库和Redis连接 +- ✅ **进度保存**: 支持中断后保存部分配置 + +## 前置要求 + +- Python 3.6+ +- PyYAML 库(通常已包含在Python中) +- openssl(用于生成密钥,可选) +- **ruamel.yaml(推荐)**:用于保留 YAML 文件的格式、注释和引号 + +### 安装依赖 + +**使用 uv(推荐,更快):** + +uv 默认使用虚拟环境来管理依赖,推荐的工作流程: + +```bash +# 1. 安装 uv(如果未安装) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# 2. 创建虚拟环境(如果还没有) +uv venv + +# 3. 激活虚拟环境(可选,uv 会自动检测) +# macOS/Linux: +source .venv/bin/activate +# Windows: +# .venv\Scripts\activate + +# 4. 使用 uv 安装依赖(会自动使用 .venv) +uv pip install -r requirements.txt + +# 或者从 pyproject.toml 安装 +uv pip install -e . + +# 或者使用 uv 的项目管理功能(推荐) +uv sync # 会自动创建 venv 并安装依赖 +``` + +**不使用虚拟环境(不推荐):** + +如果要在系统 Python 中安装,使用 `--system` 标志: + +```bash +uv pip install --system -r requirements.txt +``` + +**使用 pip(传统方式):** + +```bash +# 创建虚拟环境(推荐) +python3 -m venv venv +source venv/bin/activate # macOS/Linux +# venv\Scripts\activate # Windows + +# 安装依赖 +pip3 install -r requirements.txt +``` + +> **注意**:uv 默认会查找并使用项目目录下的 `.venv` 虚拟环境。如果没有虚拟环境,`uv pip install` 会报错,提示您创建虚拟环境或使用 `--system` 标志。 + +> **注意**:如果不安装 `ruamel.yaml`,脚本仍可使用,但生成的 YAML 文件可能会丢失: +> - 注释(如 `extraEnv` 的注释) +> - 字符串引号 +> - 多行字符串格式(如 `squidConf`) +> +> 强烈建议安装 `ruamel.yaml` 以获得最佳体验。 + +## 使用方法 + +### 基本使用 + +```bash +python3 generate-values-prd.py +``` + +或直接运行(已设置可执行权限): +```bash +./generate-values-prd.py +``` + +### 配置流程 + +脚本会按以下顺序引导您完成配置: + +1. **模块 1: 全局配置** + - **密钥自动生成**(appSecretKey, innerApiKey) + - 域名配置(7个域名) + - RAG配置(自动联动unstructured模块) + +2. **模块 2: 基础设施配置** + - PostgreSQL(外部/内置) + - 外部:交互式配置4个数据库的完整信息 + - 内置:自动生成root密码 + - Redis(外部/内置) + - 外部:交互式配置,支持Sentinel/Cluster(互斥) + - 内置:自动生成密码 + - 向量数据库(外部/内置) + - 存储配置(local/s3/云存储) + - S3:选择提供商(AWS S3/MinIO/其他),自动设置useAwsS3 + - 可同时配置MinIO服务 + - MinIO配置(自动生成root密码) + +3. **模块 3: 网络配置** + - **TLS配置**(全局TLS,影响CORS) + - Ingress配置 + - Ingress TLS配置(与全局TLS联动检查) + - cert-manager支持 + +4. **模块 4: 邮件配置** + - 邮件服务类型选择 + - 邮件服务凭证配置 + +5. **模块 5: 服务配置** + - Enterprise服务配置(所有密钥自动生成) + - License配置(online/offline) + - 各服务启用状态 + +### 交互说明 + +- **文本输入**: 直接输入值,按回车确认 +- **默认值**: 显示在 `[默认值]` 中,直接回车使用默认值 +- **是/否选择**: 输入 `y`/`yes` 或 `n`/`no`,直接回车使用默认值 +- **多选**: 输入数字选择对应选项 +- **中断**: 按 `Ctrl+C` 可以中断,可选择保存部分配置 + +## 模块联动关系 + +详细的模块划分和联动关系请参考 [MODULES.md](./MODULES.md) + +### 关键联动点 + +1. **密钥生成**: 所有密钥自动生成,无需手动输入 +2. **RAG联动**: + - `rag.etlType = "dify"` → `unstructured.enabled = false` + - `rag.etlType = "Unstructured"` → `unstructured.enabled = true` +3. **PostgreSQL**: `externalPostgres.enabled` 和 `postgresql.enabled` 互斥 +4. **Redis**: `externalRedis.enabled` 和 `redis.enabled` 互斥,Sentinel和Cluster互斥 +5. **VectorDB**: `vectorDB.useExternal` 决定使用外部还是内置 +6. **存储**: `persistence.type` 决定存储类型和相关配置 + - S3类型:选择提供商自动设置 `useAwsS3` + - 可同时配置MinIO服务 +7. **TLS联动(重要)**: `global.useTLS` 与 `ingress.tls` 必须一致,避免CORS问题 +8. **Enterprise**: 所有密钥自动生成,需要配置 License + +## 示例 + +### 示例1: 使用外部数据库和Redis + +```bash +$ python3 generate-values-prd.py + +============================================================ + Dify Helm Chart Values 生成器 +============================================================ + +>>> 模块 1: 全局配置 +ℹ 全局配置影响所有服务的运行 + +是否自动生成 appSecretKey? [Y/n]: [回车] +✓ 已生成 appSecretKey: xK9mP2nQ8rS5tU7vW... + +>>> 模块 2: 基础设施配置 +是否使用外部 PostgreSQL? [y/N]: y +PostgreSQL 地址: postgres.example.com +PostgreSQL 端口 [5432]: [回车] +... +``` + +### 示例2: 使用内置数据库 + +```bash +>>> 模块 2: 基础设施配置 +是否使用外部 PostgreSQL? [y/N]: [回车] # 使用默认值 N +将使用 Helm Chart 内置的 PostgreSQL +是否配置 PostgreSQL 密码? [Y/n]: [回车] +是否自动生成密码? [Y/n]: [回车] +✓ 已生成 PostgreSQL 密码 +``` + +## 输出文件 + +脚本会生成 `values-prd.yaml` 文件,包含所有配置项。 + +### 使用生成的配置 + +```bash +# 检查配置 +helm template . -f values-prd.yaml --debug + +# 安装 +helm install dify-prd . -f values-prd.yaml + +# 升级 +helm upgrade dify-prd . -f values-prd.yaml +``` + +## 注意事项 + +1. **密钥安全**: + - 所有密钥自动生成,会显示在终端,请妥善保管 + - 建议将密钥存储在密钥管理系统中 + - 密钥生成使用 `openssl rand -base64`,安全可靠 + +2. **TLS配置**: + - **重要**: 全局TLS (`global.useTLS`) 与 Ingress TLS 必须保持一致 + - 不一致会导致CORS跨域问题 + - 脚本会自动检查并提供警告 + +3. **RAG配置**: + - 选择 `dify` 类型会自动关闭 `unstructured` 模块 + - 选择 `Unstructured` 类型会自动启用 `unstructured` 模块 + +4. **存储配置**: + - S3存储需要选择提供商,脚本会自动设置 `useAwsS3` + - AWS S3 → `useAwsS3 = true` + - MinIO或其他兼容S3服务 → `useAwsS3 = false` + +5. **配置验证**: + - 生成后请仔细检查配置文件 + - 特别是数据库连接信息、存储配置等 + +6. **部分配置**: + - 某些高级配置项可能需要在生成后手动调整 + - 如资源限制、节点选择器等 + +7. **环境差异**: + - 开发、测试、生产环境可能需要不同的配置 + - 建议为每个环境生成独立的配置文件 + +## 故障排除 + +### 问题1: 无法生成密钥 + +如果 openssl 不可用,脚本会使用 Python 的 secrets 模块作为备选。 + +### 问题2: YAML格式错误或丢失注释/引号 + +**问题**:生成的 YAML 文件丢失了注释、引号或格式不正确。 + +**解决方案**: +1. 安装 `ruamel.yaml` 以获得更好的格式保留: + ```bash + pip3 install ruamel.yaml + ``` +2. 重新运行脚本生成配置文件 + +**验证**:运行测试脚本检查 ruamel.yaml 是否正常工作: +```bash +python3 test-ruamel.py +``` + +如果未安装 `ruamel.yaml`,脚本会回退到标准 yaml 库,但会丢失注释和格式。 + +**基础依赖**:确保 Python 版本 >= 3.6,并安装了 PyYAML: +```bash +pip3 install --upgrade pyyaml +``` + +### 问题3: 编码问题 + +脚本使用 UTF-8 编码,如果遇到编码问题,请检查终端设置。 + +## 扩展开发 + +脚本采用模块化设计,可以轻松扩展: + +1. **添加新模块**: 在 `ValuesGenerator` 类中添加新的 `configure_*` 方法 +2. **添加验证**: 在配置方法中添加验证逻辑 +3. **添加联动**: 在相关模块中添加联动处理 + +## 相关文档 + +- [MODULES.md](./MODULES.md) - 模块划分和联动关系详细说明 +- [KIND-NETWORKING.md](./KIND-NETWORKING.md) - Kind 集群网络配置指南 +- [IMPROVEMENTS.md](./IMPROVEMENTS.md) - 脚本改进说明(格式保留、注释保留等) +- [values.yaml](./values.yaml) - 原始模板文件 +- [Chart.yaml](./Chart.yaml) - Helm Chart 元数据 + +## 贡献 + +欢迎提交 Issue 和 Pull Request 来改进这个工具! + diff --git a/generate-values-prd.py b/generate-values-prd.py new file mode 100755 index 0000000..8d0189a --- /dev/null +++ b/generate-values-prd.py @@ -0,0 +1,1244 @@ +#!/usr/bin/env python3 +""" +Dify Helm Chart Values Generator +交互式生成 values-prd.yaml 配置文件 + +模块划分和联动关系: +1. 全局配置模块 (global) - 影响所有服务 +2. 基础设施模块 - 数据库、存储、缓存(互斥选择) +3. 服务模块 - 应用服务配置 +4. 网络模块 - Ingress配置 +5. 邮件模块 - 邮件服务配置 +""" + +import yaml +import os +import subprocess +import sys +import re +import shutil +from typing import Dict, Any, Optional +from copy import deepcopy + + +class Colors: + """终端颜色""" + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +def print_header(text: str): + """打印标题""" + print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}{text:^60}{Colors.ENDC}") + print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n") + + +def print_section(text: str): + """打印章节""" + print(f"\n{Colors.OKCYAN}{Colors.BOLD}>>> {text}{Colors.ENDC}") + + +def print_info(text: str): + """打印信息""" + print(f"{Colors.OKBLUE}ℹ {text}{Colors.ENDC}") + + +def print_success(text: str): + """打印成功信息""" + print(f"{Colors.OKGREEN}✓ {text}{Colors.ENDC}") + + +def print_warning(text: str): + """打印警告""" + print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}") + + +def print_error(text: str): + """打印错误""" + print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}") + + +def prompt(prompt_text: str, default: Optional[str] = None, required: bool = True) -> str: + """提示用户输入""" + if default: + prompt_str = f"{Colors.BOLD}{prompt_text}{Colors.ENDC} [{default}]: " + else: + prompt_str = f"{Colors.BOLD}{prompt_text}{Colors.ENDC}: " + + while True: + value = input(prompt_str).strip() + if value: + return value + elif default: + return default + elif not required: + return "" + else: + print_error("此字段为必填项,请重新输入") + + +def prompt_yes_no(prompt_text: str, default: bool = True) -> bool: + """提示是/否选择""" + default_str = "Y/n" if default else "y/N" + prompt_str = f"{Colors.BOLD}{prompt_text}{Colors.ENDC} [{default_str}]: " + + while True: + value = input(prompt_str).strip().lower() + if not value: + return default + if value in ['y', 'yes']: + return True + elif value in ['n', 'no']: + return False + else: + print_error("请输入 y 或 n") + + +def prompt_choice(prompt_text: str, choices: list, default: Optional[str] = None) -> str: + """提示选择""" + print(f"\n{Colors.BOLD}{prompt_text}{Colors.ENDC}") + for i, choice in enumerate(choices, 1): + marker = " [默认]" if choice == default else "" + print(f" {i}. {choice}{marker}") + + while True: + if default: + prompt_str = f"请选择 [1-{len(choices)}] (默认: {default}): " + else: + prompt_str = f"请选择 [1-{len(choices)}]: " + + value = input(prompt_str).strip() + if not value and default: + return default + + try: + idx = int(value) - 1 + if 0 <= idx < len(choices): + return choices[idx] + except ValueError: + pass + + print_error(f"请输入 1-{len(choices)} 之间的数字") + + +def generate_secret(length: int = 42) -> str: + """生成密钥""" + try: + result = subprocess.run( + ['openssl', 'rand', '-base64', str(length)], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except (subprocess.CalledProcessError, FileNotFoundError): + print_warning("无法使用 openssl 生成密钥,将使用随机字符串") + import secrets + return secrets.token_urlsafe(length) + + +class ValuesGenerator: + """Values 生成器""" + + def __init__(self, source_file: str): + """初始化""" + self.source_file = source_file + self.values = {} + self.yaml_data = None # ruamel.yaml 的数据对象(保留注释和格式) + self.load_template() + + def load_template(self): + """加载模板文件""" + try: + # 尝试使用 ruamel.yaml(推荐方式) + try: + from ruamel.yaml import YAML + + yaml_loader = YAML() + yaml_loader.preserve_quotes = True + yaml_loader.width = 120 + yaml_loader.indent(mapping=2, sequence=4, offset=2) + + # 读取原始文件(保留注释和格式) + with open(self.source_file, 'r', encoding='utf-8') as f: + self.yaml_data = yaml_loader.load(f) + + # 同时加载为标准字典用于配置逻辑 + with open(self.source_file, 'r', encoding='utf-8') as f: + self.values = yaml.safe_load(f) + + print_success(f"已加载模板文件: {self.source_file} (使用 ruamel.yaml,保留注释和格式)") + + except ImportError: + # 回退到标准 yaml + with open(self.source_file, 'r', encoding='utf-8') as f: + self.values = yaml.safe_load(f) + print_success(f"已加载模板文件: {self.source_file}") + print_warning("ruamel.yaml 未安装,建议安装以获得更好的格式保留: pip install ruamel.yaml") + + except Exception as e: + print_error(f"加载模板文件失败: {e}") + sys.exit(1) + + def set_value(self, key_path: str, value: Any): + """ + 设置值(同时更新 yaml_data 和 values) + + Args: + key_path: 键路径,如 'global.appSecretKey' + value: 新值 + """ + keys = key_path.split('.') + + # 更新标准字典 + current = self.values + for key in keys[:-1]: + if key not in current: + current[key] = {} + current = current[key] + current[keys[-1]] = value + + # 更新 ruamel.yaml 数据对象 + if self.yaml_data is not None: + try: + from ruamel.yaml.comments import CommentedMap + current = self.yaml_data + for key in keys[:-1]: + if key not in current: + current[key] = CommentedMap() + current = current[key] + current[keys[-1]] = value + except Exception: + # 如果更新失败,至少标准字典已更新 + pass + + def save(self, output_file: str): + """ + 保存到文件 - 优先使用 ruamel.yaml 保留注释和格式,否则使用标准 yaml + """ + try: + # 尝试使用 ruamel.yaml(推荐方式) + try: + from ruamel.yaml import YAML + + yaml_loader = YAML() + yaml_loader.preserve_quotes = True + yaml_loader.width = 120 + yaml_loader.indent(mapping=2, sequence=4, offset=2) + yaml_loader.default_flow_style = False + + # 重新加载原始文件(保留注释和格式) + with open(self.source_file, 'r', encoding='utf-8') as f: + data = yaml_loader.load(f) + + # 递归更新值(将 self.values 的更改应用到 data) + self._update_dict_recursive(data, self.values) + + # 保存 + with open(output_file, 'w', encoding='utf-8') as f: + yaml_loader.dump(data, f) + + print_success(f"配置已保存到: {output_file}") + print_info("✓ 已保留原始格式、注释和引号(使用 ruamel.yaml)") + + except ImportError: + # 回退到标准 yaml(会丢失注释和格式) + print_warning("ruamel.yaml 未安装,使用标准 yaml 库") + print_info("建议安装以获得更好的格式保留: pip install ruamel.yaml") + self._save_with_standard_yaml(output_file) + except Exception as e: + print_warning(f"使用 ruamel.yaml 保存失败: {e}") + print_info("回退到标准 yaml 库") + self._save_with_standard_yaml(output_file) + + except Exception as e: + print_error(f"保存文件失败: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + def _update_dict_recursive(self, target: dict, source: dict): + """递归更新字典,保留 ruamel.yaml 的格式和注释""" + for key, value in source.items(): + if key in target: + if isinstance(value, dict) and isinstance(target[key], dict): + self._update_dict_recursive(target[key], value) + else: + target[key] = value + + def _save_with_standard_yaml(self, output_file: str): + """使用标准 yaml 库保存(会丢失注释和格式)""" + print_warning("使用标准 yaml 库,可能丢失注释和格式") + print_info("建议安装 ruamel.yaml: pip install ruamel.yaml") + + with open(output_file, 'w', encoding='utf-8') as f: + yaml.dump(self.values, f, default_flow_style=False, + allow_unicode=True, sort_keys=False, + width=120, indent=2) + + print_success(f"配置已保存到: {output_file}") + + def _update_dict_recursive(self, target: dict, source: dict): + """递归更新字典,保留 ruamel.yaml 的格式和注释""" + for key, value in source.items(): + if key in target: + if isinstance(value, dict) and isinstance(target[key], dict): + self._update_dict_recursive(target[key], value) + else: + target[key] = value + + def _save_with_text_replacement(self, output_file: str): + """使用文本替换方式保存,保留注释和格式""" + # 先复制模板文件 + content = self.template_content + + # 获取原始值用于比较 + with open(self.source_file, 'r', encoding='utf-8') as f: + original_data = yaml.safe_load(f) + + # 找出所有需要更新的值 + def find_changes(new_dict: dict, old_dict: dict, path: str = ""): + changes = [] + for k, v in new_dict.items(): + current_path = f"{path}.{k}" if path else k + if k not in old_dict: + changes.append((current_path, v)) + elif isinstance(v, dict) and isinstance(old_dict[k], dict): + changes.extend(find_changes(v, old_dict[k], current_path)) + elif v != old_dict[k]: + changes.append((current_path, v)) + return changes + + changes = find_changes(self.values, original_data) + + # 对每个变更进行文本替换 + for path, new_value in changes: + keys = path.split('.') + + # 构建正则表达式匹配模式 + if len(keys) == 1: + # 简单键:匹配 "key: value" 格式 + # 需要处理多行值(如注释、多行字符串) + pattern = rf'^(\s*){re.escape(keys[0])}\s*:(.*?)(?=\n\s*\w+\s*:|\n\s*$|\Z)' + + def replace_func(match): + indent = match.group(1) + old_value_part = match.group(2) + + # 生成新值 + if new_value is None: + return f"{indent}{keys[0]}:" + elif isinstance(new_value, str): + # 检查原值是否有引号 + old_stripped = old_value_part.strip() + has_quotes = old_stripped.startswith('"') or old_stripped.startswith("'") + # 检查是否需要引号 + needs_quotes = (has_quotes or new_value == '' or + ':' in new_value or new_value.startswith('*') or + new_value.startswith('#') or ' ' in new_value) + if needs_quotes: + return f"{indent}{keys[0]}: \"{new_value}\"" + else: + return f"{indent}{keys[0]}: {new_value}" + elif isinstance(new_value, bool): + return f"{indent}{keys[0]}: {str(new_value).lower()}" + elif isinstance(new_value, (int, float)): + return f"{indent}{keys[0]}: {new_value}" + elif isinstance(new_value, list): + if len(new_value) == 0: + return f"{indent}{keys[0]}: []" + else: + result = f"{indent}{keys[0]}:\n" + for item in new_value: + if isinstance(item, dict): + for k, v in item.items(): + result += f"{indent} {k}: {v}\n" + else: + result += f"{indent} - {item}\n" + return result.rstrip() + else: + return f"{indent}{keys[0]}: {new_value}" + + # 使用多行模式匹配 + content = re.sub(pattern, replace_func, content, flags=re.MULTILINE | re.DOTALL) + + # 写入文件 + with open(output_file, 'w', encoding='utf-8') as f: + f.write(content) + + print_success(f"配置已保存到: {output_file}") + print_warning("⚠ 文本替换方式可能无法完美保留所有格式,建议安装 ruamel.yaml") + + # ==================== 模块 1: 全局配置 ==================== + def configure_global(self): + """配置全局设置""" + print_header("模块 1: 全局配置 (Global Configuration)") + + print_info("全局配置影响所有服务的运行") + + # Secret Keys - 所有密钥都按注释自动生成 + print_section("密钥配置") + print_info("appSecretKey 用于安全签名会话cookie和加密数据库敏感信息") + print_info("将使用 openssl rand -base64 42 自动生成") + self.values['global']['appSecretKey'] = generate_secret(42) + print_success(f"已生成 appSecretKey: {self.values['global']['appSecretKey'][:20]}...") + + print_info("innerApiKey 用于内部API调用的密钥") + print_info("将使用 openssl rand -base64 42 自动生成") + self.values['global']['innerApiKey'] = generate_secret(42) + print_success(f"已生成 innerApiKey: {self.values['global']['innerApiKey'][:20]}...") + + # 域名配置 + print_section("域名配置") + print_info("如果为空,将使用相同域名") + + self.values['global']['consoleApiDomain'] = prompt( + "Console API 域名", + default="console.dify.local", + required=False + ) + + self.values['global']['consoleWebDomain'] = prompt( + "Console Web 域名", + default="console.dify.local", + required=False + ) + + self.values['global']['serviceApiDomain'] = prompt( + "Service API 域名", + default="api.dify.local", + required=False + ) + + self.values['global']['appApiDomain'] = prompt( + "WebApp API 后端域名", + default="app.dify.local", + required=False + ) + + self.values['global']['appWebDomain'] = prompt( + "WebApp 域名", + default="app.dify.local", + required=False + ) + + self.values['global']['filesDomain'] = prompt( + "文件预览/下载域名", + default="files.dify.local", + required=False + ) + + self.values['global']['enterpriseDomain'] = prompt( + "Enterprise 服务域名", + default="enterprise.dify.local", + required=False + ) + + # 数据库迁移 + self.values['global']['dbMigrationEnabled'] = prompt_yes_no( + "是否启用数据库迁移?", + default=True + ) + + # RAG配置 + print_section("RAG 配置") + rag_etl_type = prompt_choice( + "RAG ETL 类型", + ["dify", "Unstructured"], + default="dify" + ) + self.values['global']['rag']['etlType'] = rag_etl_type + + # 联动关系: 如果选择 dify,则关闭 unstructured 模块 + if rag_etl_type == "dify": + self.values['unstructured']['enabled'] = False + print_info("已自动关闭 unstructured 模块(RAG ETL 类型为 dify)") + else: + self.values['unstructured']['enabled'] = True + print_info("已自动启用 unstructured 模块(RAG ETL 类型为 Unstructured)") + + # 关键词数据源类型配置 - 添加详细说明 + print_section("关键词数据源类型配置") + print_info("=" * 60) + print_info("重要说明:关键词数据源类型") + print_info("=" * 60) + print_info("此配置决定 RAG 关键词检索(Keyword Search)时的关键词存储位置。") + print_info("") + print_info("选项说明:") + print_info(" • object_storage: 将关键词存储在对象存储中(如 MinIO、S3)") + print_info(" - 适合大规模关键词存储") + print_info(" - 需要配置对象存储服务") + print_info("") + print_info(" • database: 将关键词存储在数据库中") + print_info(" - 适合中小规模关键词存储") + print_info(" - 使用已配置的 PostgreSQL 数据库") + print_info("=" * 60) + print_info("") + self.values['global']['rag']['keywordDataSourceType'] = prompt_choice( + "请选择关键词数据源类型", + ["object_storage", "database"], + default="object_storage" + ) + + top_k = prompt("RAG Top-K 最大值", default="10", required=False) + try: + self.values['global']['rag']['topKMaxValue'] = int(top_k) + except ValueError: + print_warning("无效的数字,使用默认值 10") + + seg_tokens = prompt("文档分块最大token长度", default="4000", required=False) + try: + self.values['global']['rag']['indexingMaxSegmentationTokensLength'] = int(seg_tokens) + except ValueError: + print_warning("无效的数字,使用默认值 4000") + + # ==================== 模块 2: 基础设施配置 ==================== + def configure_infrastructure(self): + """配置基础设施""" + print_header("模块 2: 基础设施配置 (Infrastructure)") + + # PostgreSQL + print_section("PostgreSQL 数据库配置") + print_info("=" * 60) + print_info("网络地址说明(重要)") + print_info("=" * 60) + print_info("如果在 kind 集群中运行,访问集群外(同一主机)的服务:") + print_info(" • 使用 'host.docker.internal' 访问宿主机服务") + print_info(" • 或使用宿主机 IP 地址") + print_info(" • 或使用 'localhost'(如果配置了端口映射)") + print_info("") + print_info("如果服务在集群内:") + print_info(" • 使用服务名,例如: postgresql.default.svc.cluster.local") + print_info(" • 或使用短服务名,例如: postgresql") + print_info("=" * 60) + print_info("") + # 默认使用外部 PostgreSQL(企业版推荐) + use_external_postgres = True + print_info("默认使用外部 PostgreSQL(企业版推荐配置)") + + if use_external_postgres: + self.values['externalPostgres']['enabled'] = True + self.values['postgresql']['enabled'] = False + + print_info("配置外部 PostgreSQL 连接信息") + print_warning("提示: 如果在 kind 集群中访问宿主机服务,使用 'host.docker.internal'") + self.values['externalPostgres']['address'] = prompt( + "PostgreSQL 地址 (kind集群外使用 host.docker.internal 或宿主机IP)", + default="host.docker.internal", + required=True + ) + + port = prompt("PostgreSQL 端口", default="5432", required=False) + try: + self.values['externalPostgres']['port'] = int(port) + except ValueError: + self.values['externalPostgres']['port'] = 5432 + + # 配置各个数据库的凭证 - 交互式获取每个数据库的配置信息 + databases_config = [ + {'key': 'dify', 'name': 'dify', 'desc': '主数据库'}, + {'key': 'plugin_daemon', 'name': 'plugin_daemon', 'desc': '插件守护进程数据库'}, + {'key': 'enterprise', 'name': 'enterprise', 'desc': '企业版数据库'}, + {'key': 'audit', 'name': 'audit', 'desc': '审计数据库'} + ] + + for db_config in databases_config: + print(f"\n{'='*60}") + print(f"配置数据库: {db_config['name']} ({db_config['desc']})") + print(f"{'='*60}") + + db_key = db_config['key'] + + self.values['externalPostgres']['credentials'][db_key]['database'] = prompt( + f"{db_config['name']} 数据库名", + default=db_config['name'], + required=False + ) + + self.values['externalPostgres']['credentials'][db_key]['username'] = prompt( + f"{db_config['name']} 用户名", + default="postgres", + required=False + ) + + self.values['externalPostgres']['credentials'][db_key]['password'] = prompt( + f"{db_config['name']} 密码", + required=True + ) + + self.values['externalPostgres']['credentials'][db_key]['sslmode'] = prompt_choice( + f"{db_config['name']} SSL 模式", + ["disable", "require", "verify-ca", "verify-full"], + default="require" + ) + + # 设置默认值(不再询问) + self.values['externalPostgres']['credentials'][db_key]['extras'] = '' + self.values['externalPostgres']['credentials'][db_key]['charset'] = '' + self.values['externalPostgres']['credentials'][db_key]['uriScheme'] = 'postgresql' + else: + self.values['externalPostgres']['enabled'] = False + self.values['postgresql']['enabled'] = True + + print_info("将使用 Helm Chart 内置的 PostgreSQL") + if prompt_yes_no("是否配置 PostgreSQL 密码?", default=True): + if prompt_yes_no("是否自动生成密码?", default=True): + self.values['postgresql']['global']['postgresql']['auth']['postgresPassword'] = generate_secret(32) + print_success("已生成 PostgreSQL 密码") + else: + self.values['postgresql']['global']['postgresql']['auth']['postgresPassword'] = prompt( + "PostgreSQL root 密码", + required=True + ) + + # Redis + print_section("Redis 缓存配置") + use_external_redis = prompt_yes_no("是否使用外部 Redis?", default=True) + + if use_external_redis: + self.values['externalRedis']['enabled'] = True + self.values['redis']['enabled'] = False + + print_info("配置外部 Redis 连接信息") + print_warning("提示: 如果在 kind 集群中访问宿主机服务,使用 'host.docker.internal'") + self.values['externalRedis']['host'] = prompt( + "Redis 主机地址 (kind集群外使用 host.docker.internal 或宿主机IP)", + default="host.docker.internal", + required=True + ) + + port = prompt("Redis 端口", default="6379", required=False) + try: + self.values['externalRedis']['port'] = int(port) + except ValueError: + self.values['externalRedis']['port'] = 6379 + + self.values['externalRedis']['useSSL'] = prompt_yes_no("是否使用 SSL?", default=False) + + self.values['externalRedis']['username'] = prompt( + "Redis 用户名 (可选)", + default="", + required=False + ) + + self.values['externalRedis']['password'] = prompt( + "Redis 密码", + required=True + ) + + db_num = prompt("Redis 数据库编号", default="0", required=False) + try: + self.values['externalRedis']['db'] = int(db_num) + except ValueError: + self.values['externalRedis']['db'] = 0 + + # Sentinel/Cluster 配置 - 互斥选择 + use_sentinel = prompt_yes_no("是否使用 Redis Sentinel?", default=False) + use_cluster = False + + if use_sentinel: + self.values['externalRedis']['sentinel']['enabled'] = True + self.values['externalRedis']['cluster']['enabled'] = False + + self.values['externalRedis']['sentinel']['nodes'] = prompt( + "Sentinel 节点列表 (host:port,host:port)", + required=True + ) + self.values['externalRedis']['sentinel']['serviceName'] = prompt( + "Sentinel 服务名", + required=True + ) + self.values['externalRedis']['sentinel']['username'] = prompt( + "Sentinel 用户名 (可选)", + default="", + required=False + ) + self.values['externalRedis']['sentinel']['password'] = prompt( + "Sentinel 密码", + required=True + ) + socket_timeout = prompt( + "Socket 超时时间(秒)", + default="0.1", + required=False + ) + try: + self.values['externalRedis']['sentinel']['socketTimeout'] = float(socket_timeout) + except ValueError: + self.values['externalRedis']['sentinel']['socketTimeout'] = 0.1 + else: + self.values['externalRedis']['sentinel']['enabled'] = False + use_cluster = prompt_yes_no("是否使用 Redis Cluster?", default=False) + + if use_cluster: + self.values['externalRedis']['cluster']['enabled'] = True + self.values['externalRedis']['cluster']['nodes'] = prompt( + "Cluster 节点列表 (host:port,host:port)", + required=True + ) + self.values['externalRedis']['cluster']['password'] = prompt( + "Cluster 密码", + required=True + ) + else: + self.values['externalRedis']['cluster']['enabled'] = False + else: + self.values['externalRedis']['enabled'] = False + self.values['redis']['enabled'] = True + + print_info("将使用 Helm Chart 内置的 Redis") + if prompt_yes_no("是否配置 Redis 密码?", default=True): + if prompt_yes_no("是否自动生成密码?", default=True): + self.values['redis']['global']['redis']['password'] = generate_secret(32) + print_success("已生成 Redis 密码") + else: + self.values['redis']['global']['redis']['password'] = prompt( + "Redis 密码", + required=True + ) + + # VectorDB + print_section("向量数据库配置") + use_external_vectordb = prompt_yes_no("是否使用外部向量数据库?", default=True) + + self.values['vectorDB']['useExternal'] = use_external_vectordb + + if use_external_vectordb: + vectordb_type = prompt_choice( + "选择向量数据库类型", + ["qdrant", "weaviate", "milvus", "relyt", "pgvecto-rs", + "tencent", "opensearch", "elasticsearch", "analyticdb", "lindorm"], + default="qdrant" + ) + self.values['vectorDB']['externalType'] = vectordb_type + + print_info(f"配置外部 {vectordb_type} 连接信息") + print_warning("提示: 如果在 kind 集群中访问宿主机服务,使用 'host.docker.internal'") + + if vectordb_type == "qdrant": + self.values['vectorDB']['externalQdrant']['endpoint'] = prompt( + "Qdrant 端点 URL (kind集群外使用 http://host.docker.internal:6333)", + default="http://host.docker.internal:6333", + required=True + ) + self.values['vectorDB']['externalQdrant']['apiKey'] = prompt( + "Qdrant API Key", + required=False + ) + elif vectordb_type == "weaviate": + self.values['vectorDB']['externalWeaviate']['endpoint'] = prompt( + "Weaviate 端点 URL", + default="http://weaviate:8080", + required=True + ) + self.values['vectorDB']['externalWeaviate']['apiKey'] = prompt( + "Weaviate API Key", + required=False + ) + # 其他类型可以类似扩展 + else: + # 使用内置向量数据库 + vectordb_choice = prompt_choice( + "选择内置向量数据库", + ["qdrant", "weaviate"], + default="qdrant" + ) + + if vectordb_choice == "qdrant": + self.values['qdrant']['enabled'] = True + self.values['weaviate']['enabled'] = False + + api_key = prompt("Qdrant API Key", default="dify123456", required=False) + self.values['qdrant']['apiKey'] = api_key + + replica_count = prompt("Qdrant 副本数", default="3", required=False) + try: + self.values['qdrant']['replicaCount'] = int(replica_count) + except ValueError: + self.values['qdrant']['replicaCount'] = 3 + else: + self.values['qdrant']['enabled'] = False + self.values['weaviate']['enabled'] = True + + # 存储配置 + print_section("存储配置 (Persistence)") + storage_type = prompt_choice( + "选择存储类型", + ["local", "s3 (AWS S3 和 S3 兼容协议)", "azure-blob", "aliyun-oss", "google-storage", + "tencent-cos", "volcengine-tos", "huawei-obs"], + default="local" + ) + + # 处理存储类型选择,将显示名称转换为实际值 + if storage_type == "s3 (AWS S3 和 S3 兼容协议)": + storage_type = "s3" + self.values['persistence']['type'] = storage_type + + if storage_type == "local": + print_info("配置本地存储") + self.values['persistence']['local']['mountPath'] = prompt( + "挂载路径", + default="/app/api/storage", + required=False + ) + + storage_class = prompt( + "StorageClass 名称 (留空使用默认)", + default="", + required=False + ) + if storage_class: + self.values['persistence']['local']['persistentVolumeClaim']['storageClass'] = storage_class + + size = prompt("存储大小", default="5Gi", required=False) + self.values['persistence']['local']['persistentVolumeClaim']['size'] = size + + elif storage_type == "s3": + print_info("配置 S3 兼容存储(AWS S3 和 S3 兼容协议)") + + # 判断是 AWS S3 还是其他兼容 S3 的服务(如 MinIO) + s3_provider = prompt_choice( + "S3 服务提供商", + ["AWS S3", "MinIO", "Cloudflare R2", "其他兼容S3服务"], + default="AWS S3" + ) + + if s3_provider == "AWS S3": + self.values['persistence']['s3']['useAwsS3'] = True + print_info("配置 AWS S3") + # AWS S3 不需要内置 MinIO + self.values['minio']['enabled'] = False + print_info("已自动关闭内置 MinIO(使用 AWS S3)") + else: + self.values['persistence']['s3']['useAwsS3'] = False + print_info(f"配置 {s3_provider} (S3兼容)") + # 非 AWS S3 需要内置 MinIO + self.values['minio']['enabled'] = True + print_info("已自动启用内置 MinIO(useAwsS3=false)") + + # MinIO 特殊配置说明 + if s3_provider == "MinIO": + print_info("") + print_info("=" * 60) + print_info("外部 MinIO 配置说明(对象存储)") + print_info("=" * 60) + print_info("配置您自建的外部 MinIO 服务作为对象存储:") + print_info(" • Access Key = MINIO_ROOT_USER(例如: minioadmin)") + print_info(" • Secret Key = MINIO_ROOT_PASSWORD(例如: minioadmin123)") + print_info("=" * 60) + print_info("") + default_endpoint = "http://host.docker.internal:9000" + default_access_key = "minioadmin" + default_secret_key = "minioadmin123" + else: + default_endpoint = "https://xxx.r2.cloudflarestorage.com" if s3_provider != "AWS S3" else "" + default_access_key = "" + default_secret_key = "" + + self.values['persistence']['s3']['endpoint'] = prompt( + "S3 端点 URL", + default=default_endpoint, + required=(s3_provider != "AWS S3") + ) + + if s3_provider == "MinIO": + print_info("") + print_info("MinIO 认证信息:") + print_info(" • Access Key = MINIO_ROOT_USER") + print_info(" • Secret Key = MINIO_ROOT_PASSWORD") + print_info("") + + self.values['persistence']['s3']['accessKey'] = prompt( + f"Access Key{' (MinIO: MINIO_ROOT_USER)' if s3_provider == 'MinIO' else ''}", + default=default_access_key, + required=True + ) + self.values['persistence']['s3']['secretKey'] = prompt( + f"Secret Key{' (MinIO: MINIO_ROOT_PASSWORD)' if s3_provider == 'MinIO' else ''}", + default=default_secret_key, + required=True + ) + self.values['persistence']['s3']['region'] = prompt( + "区域", + default="us-east-1", + required=False + ) + self.values['persistence']['s3']['bucketName'] = prompt( + "Bucket 名称", + default="your-bucket-name", + required=True + ) + + address_type = prompt( + "地址类型 (path-style/virtual-hosted-style, 留空使用默认)", + default="", + required=False + ) + if address_type: + self.values['persistence']['s3']['addressType'] = address_type + + # 如果启用了 MinIO,配置 MinIO + if self.values['minio'].get('enabled', False): + print_section("配置内置 MinIO") + print_info("=" * 60) + print_info("重要说明:内置 MinIO") + print_info("=" * 60) + print_info("此 MinIO 服务用于 plugin 构建,不用于业务对象存储。") + print_info("业务对象存储请使用上面配置的外部 S3 服务。") + print_info("=" * 60) + print_info("") + if prompt_yes_no("是否自动生成 MinIO root 密码?", default=True): + self.values['minio']['rootPassword'] = generate_secret(32) + print_success("已生成 MinIO root 密码") + else: + self.values['minio']['rootPassword'] = prompt( + "MinIO root 密码", + required=True + ) + + self.values['minio']['rootUser'] = prompt( + "MinIO root 用户", + default="minioadmin", + required=False + ) + + # MinIO 配置 - 如果存储类型不是 s3,需要启用内置 MinIO + if storage_type != "s3": + print_section("内置 MinIO 配置") + print_info("=" * 60) + print_info("重要说明:内置 MinIO") + print_info("=" * 60) + print_info("此 MinIO 服务用于 plugin 构建,不用于业务对象存储。") + print_info("业务对象存储请使用 persistence.type 配置的外部存储服务。") + print_info("=" * 60) + print_info("") + self.values['minio']['enabled'] = True + + if prompt_yes_no("是否自动生成 MinIO root 密码?", default=True): + self.values['minio']['rootPassword'] = generate_secret(32) + print_success("已生成 MinIO root 密码") + else: + self.values['minio']['rootPassword'] = prompt( + "MinIO root 密码", + required=True + ) + + self.values['minio']['rootUser'] = prompt( + "MinIO root 用户", + default="minioadmin", + required=False + ) + + # ==================== 模块 3: 网络配置 ==================== + def configure_networking(self): + """配置网络""" + print_header("模块 3: 网络配置 (Networking)") + + # TLS 配置 - 放在网络配置模块,与 Ingress 联动 + print_section("TLS 配置") + print_info("TLS 配置影响内部服务通信和 CORS 设置") + print_warning("注意: TLS 配置必须与 Ingress 配置一致,否则会出现 CORS 跨域问题") + + use_tls = prompt_yes_no("是否启用 TLS (内部服务)?", default=False) + self.values['global']['useTLS'] = use_tls + + if use_tls: + print_info("已启用 TLS,后续 Ingress 配置也需要启用 TLS") + + # Ingress 配置 + print_section("Ingress 配置") + # 企业版默认启用 Ingress + self.values['ingress']['enabled'] = True + print_info("已自动启用 Ingress(企业版默认配置)") + + # Ingress Class 选择 + print_info("") + print_info("请选择 Ingress Controller 类型:") + ingress_class_choice = prompt_choice( + "Ingress Class 名称", + ["nginx", "alb", "traefik", "istio", "其他"], + default="nginx" + ) + + if ingress_class_choice == "nginx": + self.values['ingress']['className'] = "nginx" + print_info("") + print_warning("提示: 请确保已安装 NGINX Ingress Controller") + print_info("安装方法: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml") + elif ingress_class_choice == "alb": + self.values['ingress']['className'] = "alb" + print_info("") + print_warning("提示: 请确保已安装 AWS Load Balancer Controller") + print_info("安装方法: 参考 AWS EKS 文档配置 ALB Ingress Controller") + elif ingress_class_choice == "traefik": + self.values['ingress']['className'] = "traefik" + print_info("") + print_warning("提示: 请确保已安装 Traefik Ingress Controller") + elif ingress_class_choice == "istio": + self.values['ingress']['className'] = "istio" + print_info("") + print_warning("提示: 请确保已安装 Istio Gateway") + else: + # 其他选项,手动输入 + self.values['ingress']['className'] = prompt( + "请输入 Ingress Class 名称", + default="", + required=False + ) + + # Ingress TLS 配置 - 与全局 TLS 联动 + if use_tls: + print_info("由于已启用全局 TLS,建议 Ingress 也启用 TLS") + ingress_tls = prompt_yes_no("是否在 Ingress 中配置 TLS?", default=True) + else: + ingress_tls = prompt_yes_no("是否在 Ingress 中配置 TLS?", default=False) + + if ingress_tls: + print_info("TLS 证书配置:") + print_info(" 1. 可以通过 cert-manager 自动管理 (使用 annotations)") + print_info(" 2. 或者手动配置 TLS Secret") + + # TLS 配置示例 + tls_hosts = prompt( + "TLS 主机列表 (逗号分隔, 例如: dify.example.com,api.dify.example.com)", + default="", + required=False + ) + + if tls_hosts: + hosts_list = [h.strip() for h in tls_hosts.split(',') if h.strip()] + if hosts_list: + # 创建 TLS 配置 + if 'tls' not in self.values['ingress'] or not isinstance(self.values['ingress']['tls'], list): + self.values['ingress']['tls'] = [] + + self.values['ingress']['tls'].append({ + 'hosts': hosts_list, + 'secretName': prompt( + "TLS Secret 名称", + default=f"{hosts_list[0]}-tls", + required=False + ) or f"{hosts_list[0]}-tls" + }) + + # 添加 cert-manager 注解示例 + if prompt_yes_no("是否使用 cert-manager 自动管理证书?", default=False): + if 'annotations' not in self.values['ingress']: + self.values['ingress']['annotations'] = {} + + cluster_issuer = prompt( + "ClusterIssuer 名称 (例如: letsencrypt-prod)", + default="", + required=False + ) + if cluster_issuer: + self.values['ingress']['annotations']['cert-manager.io/cluster-issuer'] = cluster_issuer + print_success(f"已配置 cert-manager ClusterIssuer: {cluster_issuer}") + + # 检查 TLS 一致性 + if use_tls and not ingress_tls: + print_warning("警告: 全局 TLS 已启用,但 Ingress TLS 未启用,可能导致 CORS 问题") + if prompt_yes_no("是否现在启用 Ingress TLS?", default=True): + ingress_tls = True + # 重新配置 TLS + tls_hosts = prompt( + "TLS 主机列表 (逗号分隔)", + default="", + required=False + ) + if tls_hosts: + hosts_list = [h.strip() for h in tls_hosts.split(',') if h.strip()] + if hosts_list: + if 'tls' not in self.values['ingress'] or not isinstance(self.values['ingress']['tls'], list): + self.values['ingress']['tls'] = [] + self.values['ingress']['tls'].append({ + 'hosts': hosts_list, + 'secretName': prompt( + "TLS Secret 名称", + default=f"{hosts_list[0]}-tls", + required=False + ) or f"{hosts_list[0]}-tls" + }) + + if not use_tls and ingress_tls: + print_warning("警告: Ingress TLS 已启用,但全局 TLS 未启用,建议保持一致") + if prompt_yes_no("是否启用全局 TLS?", default=True): + self.values['global']['useTLS'] = True + use_tls = True + + # useIpAsHost 配置 - 企业版不支持,固定为 False + self.values['ingress']['useIpAsHost'] = False + + # ==================== 模块 4: 邮件配置 ==================== + def configure_mail(self): + """配置邮件""" + print_header("模块 4: 邮件配置 (Mail Configuration)") + + mail_type = prompt_choice( + "选择邮件服务类型", + ["", "resend", "smtp"], + default="" + ) + + self.values['mail']['type'] = mail_type + + if mail_type: + self.values['mail']['defaultSender'] = prompt( + "默认发件人地址 (例如: no-reply )", + default="YOUR EMAIL FROM (eg: no-reply )", + required=False + ) + + if mail_type == "resend": + self.values['mail']['resend']['apiKey'] = prompt( + "Resend API Key", + required=True + ) + self.values['mail']['resend']['apiUrl'] = prompt( + "Resend API URL", + default="https://api.resend.com", + required=False + ) + elif mail_type == "smtp": + self.values['mail']['smtp']['server'] = prompt( + "SMTP 服务器", + required=True + ) + port = prompt("SMTP 端口", default="587", required=False) + try: + self.values['mail']['smtp']['port'] = int(port) + except ValueError: + self.values['mail']['smtp']['port'] = 587 + + self.values['mail']['smtp']['username'] = prompt( + "SMTP 用户名", + required=True + ) + self.values['mail']['smtp']['password'] = prompt( + "SMTP 密码", + required=True + ) + self.values['mail']['smtp']['useTLS'] = prompt_yes_no( + "是否使用 TLS?", + default=False + ) + + # ==================== 模块 5: 服务配置 ==================== + def configure_services(self): + """配置服务""" + print_header("模块 5: 服务配置 (Services Configuration)") + + # Enterprise 相关配置 + if self.values.get('enterprise', {}).get('enabled', True): + print_section("Enterprise 服务配置") + + # 所有密钥都按注释自动生成 + print_info("Enterprise appSecretKey 将使用 openssl rand -base64 42 自动生成") + self.values['enterprise']['appSecretKey'] = generate_secret(42) + print_success(f"已生成 Enterprise appSecretKey: {self.values['enterprise']['appSecretKey'][:20]}...") + + print_info("adminAPIsSecretKeySalt 将使用 openssl rand -base64 42 自动生成") + self.values['enterprise']['adminAPIsSecretKeySalt'] = generate_secret(42) + print_success(f"已生成 adminAPIsSecretKeySalt: {self.values['enterprise']['adminAPIsSecretKeySalt'][:20]}...") + + print_info("passwordEncryptionKey 将使用 openssl rand -base64 32 自动生成 (32-byte AES-256 key)") + self.values['enterprise']['passwordEncryptionKey'] = generate_secret(32) + print_success(f"已生成 passwordEncryptionKey: {self.values['enterprise']['passwordEncryptionKey'][:20]}...") + + license_mode = prompt_choice( + "License 模式", + ["online", "offline"], + default="online" + ) + self.values['enterprise']['licenseMode'] = license_mode + + if license_mode == "online": + # 使用默认 License 服务器 URL,不询问用户 + self.values['enterprise']['licenseServer'] = "https://licenses.dify.ai/server" + print_info(f"License 服务器 URL: {self.values['enterprise']['licenseServer']}") + + print_section("服务启用状态") + print_info("可以跳过此部分使用默认值,或根据需要调整") + + if prompt_yes_no("是否配置服务启用状态?", default=False): + services = ['api', 'worker', 'workerBeat', 'web', 'sandbox', + 'enterprise', 'enterpriseAudit', 'enterpriseFrontend', + 'ssrfProxy', 'unstructured', 'plugin_daemon', 'plugin_manager'] + + for service in services: + if service in self.values: + current = self.values[service].get('enabled', True) + self.values[service]['enabled'] = prompt_yes_no( + f"是否启用 {service}?", + default=current + ) + + # ==================== 主流程 ==================== + def generate(self): + """生成配置""" + print_header("Dify Helm Chart Values 生成器") + print_info("此工具将引导您完成 values-prd.yaml 的配置") + print_info("您可以随时按 Ctrl+C 退出") + + try: + # 按顺序配置各个模块 + self.configure_global() + self.configure_infrastructure() + self.configure_networking() + self.configure_mail() + self.configure_services() + + # 保存文件 + output_file = "values-prd.yaml" + if os.path.exists(output_file): + if not prompt_yes_no(f"{output_file} 已存在,是否覆盖?", default=False): + output_file = prompt("请输入新的文件名", default="values-prd.yaml", required=False) + if not output_file: + output_file = "values-prd.yaml" + + self.save(output_file) + + print_header("配置完成!") + print_success(f"配置文件已保存到: {output_file}") + print_info("请检查配置文件并根据需要进行调整") + print_info("然后可以使用: helm install . -f values-prd.yaml") + + except KeyboardInterrupt: + print("\n\n") + print_warning("用户中断操作") + if prompt_yes_no("是否保存当前进度?", default=False): + output_file = prompt("请输入文件名", default="values-prd-partial.yaml", required=False) + if output_file: + self.save(output_file) + print_success(f"部分配置已保存到: {output_file}") + sys.exit(0) + except Exception as e: + print_error(f"生成配置时出错: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + + +def main(): + """主函数""" + source_file = "values.yaml" + + if not os.path.exists(source_file): + print_error(f"模板文件不存在: {source_file}") + sys.exit(1) + + generator = ValuesGenerator(source_file) + generator.generate() + + +if __name__ == "__main__": + main() + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..996bb8c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[project] +name = "dify-helm-chart-generator" +version = "1.0.0" +description = "Dify Helm Chart Values Generator - Interactive script to generate values-prd.yaml" +readme = "README-GENERATOR.md" +requires-python = ">=3.6" +authors = [ + {name = "Dify Team"} +] +keywords = ["dify", "helm", "kubernetes", "yaml", "generator"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "pyyaml>=5.4.0", + "ruamel.yaml>=0.18.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", +] + +[project.scripts] +generate-values-prd = "generate-values-prd:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd9abb0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +# Python dependencies for Dify Helm Chart Values Generator +# Generated by uv: uv pip compile pyproject.toml -o requirements.txt +# Install with: uv pip install -r requirements.txt +# Or: pip install -r requirements.txt + +# Core dependencies +pyyaml>=5.4.0 +ruamel.yaml>=0.18.0 + +# Note: ruamel.yaml-clib is automatically installed as a dependency of ruamel.yaml +# It provides C extensions for better performance diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..ffb4ea6 --- /dev/null +++ b/values.yaml @@ -0,0 +1,981 @@ +# Default values for dify. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +global: + # The edition of the application, SELF_HOSTED or CLOUD + edition: "SELF_HOSTED" + # A secret key that is used for securely signing the session cookie and encrypting sensitive information on the database. You can generate a strong key using `openssl rand -base64 42`. + appSecretKey: "#REPLACE_ME#" + # secret key for inner api calls. You can generate a strong key using `openssl rand -base64 42`. + innerApiKey: "dddd" + useTLS: false + # The backend domain of the console API, used to concatenate the authorization callback. + # If empty, it is the same domain. Example: console.dify.ai + consoleApiDomain: "" + # The front-end domain of the console web, used to concatenate some front-end addresses and for CORS configuration use. + # If empty, it is the same domain. Example: console.dify.ai + consoleWebDomain: "" + # Service API domain, used to display Service API Base Url to the front-end. + # If empty, it is the same domain. Example: api.dify.ai + serviceApiDomain: "" + # WebApp API backend domain, used to declare the back-end URL for the front-end API. + # If empty, it is the same domain. Example: app.dify.ai + appApiDomain: "" + # WebApp domain, used to display WebAPP API Base Url to the front-end. If empty, it is the same domain. Example: app.dify.ai + appWebDomain: "" + # File preview or download domain, used to display the file preview + # or download URL to the front-end or as a multi-modal model input; + # In order to prevent others from forging, the image preview URL is signed and has a 5-minute expiration time. + filesDomain: "" + # Enterprise service domain, used to declare the back-end URL for the front-end API. + enterpriseDomain: "" + # When enabled, migrations will be executed prior to application startup and the application will start after the migrations have completed. + dbMigrationEnabled: true + rag: + # RAG ETL type, support: dify or Unstructured + etlType: "dify" + keywordDataSourceType: "object_storage" + # The maximum number of top-k value for RAG. + topKMaxValue: 10 + # Configuration for document chunk length. It is used to control the size of text segments when processing long documents. + indexingMaxSegmentationTokensLength: 4000 + integrations: + notion: + # Notion import configuration, support public and internal + integrationType: "internal" + clientId: "" + clientSecret: "" + internalSecret: "" + marketplace: + enabled: true + url: "https://marketplace.dify.ai" + apiUrl: "https://marketplace.dify.ai" + +ingress: + enabled: false + # Use the IP address as the host name, which is used to access the service through the IP address. + # ! Only support non-enterprise version ! + useIpAsHost: false + className: "" + annotations: {} + # kubernetes.io/tls-acme: "true" + tls: [] + +api: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-api + repository: langgenius/dify-api + # override appVersion in Chart.yaml if not empty + tag: "c1bf06ae635b404ed24646ef65f046de53bae06f" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + failureThreshold: 3 + httpGet: + path: /health + port: 5001 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /health + port: 5001 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + deployEnv: "PRODUCTION" + webApiCorsAllowOrigins: "*" + consoleCorsAllowOrigins: "*" + serverWorkerAmount: 1 + checkUpdateUrl: "https://updates.dify.ai" + innerApi: + enabled: true + oauth: + redirectPath: "/console/api/oauth/authorize" + github: + clientId: "" + clientSecret: "" + google: + clientId: "" + clientSecret: "" + db: + poolSize: 250 + poolRecycle: 3600 + limits: + # upload file size limit in MB + uploadFileSize: 15 + # upload file batch count limit + uploadFileBatchCount: 5 + # upload image file size limit in MB + uploadImageFileSize: 5 + sentry: + enabled: false + dsn: "" + tracesSampleRate: 1.0 + profilesSampleRate: 1.0 + positionTool: + pins: "" + includes: "" + excludes: "" + positionProvider: + pins: "" + includes: "" + excludes: "" + +worker: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-api + repository: langgenius/dify-api + # override appVersion in Chart.yaml if not empty + tag: "c1bf06ae635b404ed24646ef65f046de53bae06f" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: {} + livenessProbe: {} + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + celeryWorkerAmount: 1 + db: + poolSize: 250 + poolRecycle: 3600 + sentry: + enabled: false + dsn: "" + tracesSampleRate: 1.0 + profilesSampleRate: 1.0 + +workerBeat: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-api + repository: langgenius/dify-api + # override appVersion in Chart.yaml if not empty + tag: "c1bf06ae635b404ed24646ef65f046de53bae06f" + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: {} + livenessProbe: {} + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + db: + poolSize: 30 + poolRecycle: 3600 + enableWorkflowLogCleanup: false + +web: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-web + repository: langgenius/dify-web + # override appVersion in Chart.yaml if not empty + tag: "c1bf06ae635b404ed24646ef65f046de53bae06f" + logoConfig: + enabled: false + image: + repository: busybox + tag: "1.36.1" + faviconIcoBase64: "" + logoSitePngBase64: "" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + initialDelaySeconds: 3 + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 3000 + timeoutSeconds: 1 + livenessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 3000 + timeoutSeconds: 1 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + sentry: + enabled: false + dsn: "" + tracesSampleRate: 1.0 + profilesSampleRate: 1.0 + +sandbox: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-sandbox + repository: langgenius/dify-sandbox + tag: "0.2.12" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + failureThreshold: 60 + initialDelaySeconds: 3 + periodSeconds: 3 + successThreshold: 1 + tcpSocket: + port: 8194 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + periodSeconds: 3 + successThreshold: 1 + tcpSocket: + port: 8194 + timeoutSeconds: 1 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + maxWorkers: 4 + maxRequests: 50 + workerTimeout: 15 + pythonRequirements: | + # The requirements.txt file content + # For example: + # fastapi==0.104.1 + # pydantic==2.5.1 + +enterprise: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-enterprise + repository: langgenius/dify-enterprise + tag: "0.13.0-rc.3" + replicas: 1 + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + failureThreshold: 3 + httpGet: + path: /v1/healthz + port: 8082 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /v1/healthz + port: 8082 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + appSecretKey: "#REPLACE_ME#" + adminAPIsSecretKeySalt: "#REPLACE_ME#" + innerApi: + enabled: true + serviceAccountName: "dify-enterprise" + licenseMode: "online" + licenseServer: "https://licenses.dify.ai/server" + consoleSSOSkipCertVerify: false + webSSOSkipCertVerify: false + dashboardSSOSkipCertVerify: false + serverTimeout: "60s" + grpcServerTimeout: "60s" + workspaceSyncCron: "*/5 * * * *" + workspaceSyncTimeout: "30s" + # Base64 encoded 32-byte AES-256 key for encrypting password policy. You can generate a strong key using `openssl rand -base64 32`. + passwordEncryptionKey: "xHBzZKP288fiIUdCkYJW5Px/16kCqkJucXue6x/97eo=" + +enterpriseAudit: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-audit + repository: langgenius/dify-audit + tag: "0.13.0-rc.3" + replicas: 1 + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + failureThreshold: 3 + httpGet: + path: /v1/healthz + port: 8083 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /v1/healthz + port: 8083 + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 3 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + scheduler: + solidifyCheckCron: "0 0 */1 * *" # minutes hours day-of-month month day-of-week + solidifyDelayDays: 90 + timeout: "600s" + mq: + checkInterval: "5s" + syncThreshold: 200 + maxWait: "10s" + serverTimeout: "60s" + grpcServerTimeout: "60s" + downloadTimeout: "60s" + +enterpriseFrontend: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-enterprise-frontend + repository: langgenius/dify-enterprise-frontend + tag: "0.13.0-rc.3" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: + initialDelaySeconds: 3 + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 3000 + timeoutSeconds: 1 + livenessProbe: + failureThreshold: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 3000 + timeoutSeconds: 1 + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + +ssrfProxy: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/squid + repository: ubuntu/squid + tag: "6.13-25.04_beta" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: {} + livenessProbe: {} + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + squidConf: | + acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN) + acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN) + acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN) + acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines + acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN) + acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN) + acl localnet src fc00::/7 # RFC 4193 local private network range + acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines + acl SSL_ports port 443 + acl Safe_ports port 80 # http + acl Safe_ports port 21 # ftp + acl Safe_ports port 443 # https + acl Safe_ports port 70 # gopher + acl Safe_ports port 210 # wais + acl Safe_ports port 1025-65535 # unregistered ports + acl Safe_ports port 280 # http-mgmt + acl Safe_ports port 488 # gss-http + acl Safe_ports port 591 # filemaker + acl Safe_ports port 777 # multiling http + acl CONNECT method CONNECT + http_access deny !Safe_ports + http_access deny CONNECT !SSL_ports + http_access allow localhost manager + http_access deny manager + http_access allow localhost + include /etc/squid/conf.d/*.conf + http_access deny all + + ################################## Proxy Server ################################ + http_port 3128 + coredump_dir /var/spool/squid + refresh_pattern ^ftp: 1440 20% 10080 + refresh_pattern ^gopher: 1440 0% 1440 + refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 + refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims + refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims + refresh_pattern \/InRelease$ 0 0% 0 refresh-ims + refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims + refresh_pattern . 0 20% 4320 + + # upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks + # ====== using the upstream proxy to access the internet without any authentication ====== + # cache_peer 127.0.0.1 parent 3128 0 no-query default + # never_direct allow all + # ====== using the upstream proxy to access the internet with authentication ====== + # cache_peer 172.1.1.1 parent 3128 0 no-query default login=your-username:your-password + # never_direct allow all + + ################################## Reverse Proxy To Sandbox ################################ + http_port 8194 accel vhost + cache_peer sandbox parent 8194 0 no-query originserver + acl src_all src all + http_access allow src_all + +unstructured: + enabled: true + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/unstructured-api + repository: downloads.unstructured.io/unstructured-io/unstructured-api + tag: "0.0.70" + replicas: 1 + resources: {} + serviceAccountName: "" + nodeSelector: {} + affinity: {} + tolerations: [] + readinessProbe: {} + livenessProbe: {} + # unstructured_api_key:{ } + # Use the api key only if you are trying to access .ppt document analyze service provided by unstructured.io + extraEnv: + # Apply your own Environment Variables if necessary. Below is demo. + # extraEnv: + # - name: ENV_FROM_COMMUNITY1 + # value: env123 + # - name: ENV_FROM_COMMUNITY2 + # value: env123 + +plugin_daemon: + enabled: true + replicas: 1 + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-plugin-daemon + repository: langgenius/dify-plugin-daemon + tag: 0.3.3-serverless + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + maxLaunchSeconds: 3600 + forceVerifyingSignature: false + # NodePort / LoadBalancer + serviceType: "NodePort" + # loadbalancer or node ip + remoteInstallHost: "127.0.0.1" + remoteInstallPort: "5003" + pluginExecuteTimeout: "360" + adminApiEnabled: true + +plugin_controller: + replicas: 1 + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/enterprise_plugin-crd + repository: langgenius/enterprise_plugin-crd + tag: 0.13.0-rc.3 + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + +plugin_connector: + replicas: 1 + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/enterprise_plugin-connector + repository: langgenius/enterprise_plugin-connector + tag: 0.13.0-rc.3 + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + securityContext: {} + labels: {} + maxWaitSeconds: 3600 + customServiceAccount: "" + runnerServiceAccount: "" + # If your image repo is not using https protocol, set this to true. + insecureImageRepo: false + imageRepoSecret: "image-repo-secret" + imageRepoPrefix: "docker.io/your-image-repo-prefix" + # imageRepoType: docker / ecr + imageRepoType: docker + ecrRegion: "us-east-1" + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/nginx:1.27.3 + gatewayImage: "nginx:1.27.3" + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/executor:latest + shaderImage: "gcr.io/kaniko-project/executor:latest" + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/busybox:latest + busyBoxImage: "busybox:latest" + awsCliImage: "amazon/aws-cli:latest" + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/plugin-build-base-python:3.13 + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/plugin-build-base-python:3.12 + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/plugin-build-base-python:3.11 + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/plugin-build-base-python:3.10 + builderName: "kaniko" + pluginResourceLimits: + cpu: 1000m + memory: 3000Mi + ephemeralStorage: 5Gi + pluginResourceRequests: + cpu: 100m + memory: 50Mi + ephemeralStorage: 5Gi + # Enable secure mode for plugin connector + secure: false + # GitHub builder configuration + github: + authMethod: "auto" + accessToken: "" + repoName: "your-repo-name" + actionFileName: "build.yml" + branch: "main" + imagePrefix: "docker.io/your-image-repo-prefix" + orgName: "your-org-name" + shaderImage: "gcr.io/kaniko-project/executor:latest" + app: + enabled: false + appId: "0" + installationId: "0" + # Name of the file inside the pre-created Secret + privateKeySecretKey: "app.pem" + generatorConf: | + generator: + repo: langgenius + python: + pipMirror: "" + preCompile: true + versions: + python3.13: + langgenius: docker.io/langgenius/plugin-build-base-python:3.13 + python3.12: + langgenius: docker.io/langgenius/plugin-build-base-python:3.12 + python3.11: + langgenius: docker.io/langgenius/plugin-build-base-python:3.11 + python3.10: + langgenius: docker.io/langgenius/plugin-build-base-python:3.10 + +gateway: + replicas: 1 + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/enterprise_gateway + repository: langgenius/enterprise_gateway + tag: 0.13.0-rc.3 + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + + # additional origins to allow CORS requests from + # if left empty, only dify domains will be allowed + allowOrigins: + - http://localhost:3000 + - http://localhost:3001 + - http://localhost:5001 + - http://localhost:3002 + +plugin_manager: + enabled: true + replicas: 1 + image: + # g-hsod9681-docker.pkg.coding.net/dify-artifact/dify/dify-plugin-manager + repository: langgenius/dify-plugin-manager + tag: "0.13.0-rc.3" + resources: {} + nodeSelector: {} + affinity: {} + tolerations: [] + serverTimeout: "60s" + grpcServerTimeout: "60s" + +# You can visit https://github.com/minio/minio/blob/43a74029685512ce9b1b76c053d48b43fc8d64fc/helm/minio/values.yaml for more details about MinIO configuration. +minio: + rootUser: minioadmin + # The root password for the MinIO server. You can generate a strong password using `openssl rand -base64 32`. + rootPassword: NzGG+ngHuwz0xzMQ1XFt8JYCAxdrhnQ8/93LCggMt00= + replicas: 1 + mode: standalone + resources: + limits: + cpu: 1000m + memory: 2048Mi + requests: + cpu: 500m + memory: 1024Mi + persistence: + enabled: false + +################################### +# Persistence Configration +################################### +persistence: + # The storage type support: local, s3, azure-blob, aliyun-oss, google-storage, tencent-cos volcengine-tos huawei-obs + type: "local" + local: + mountPath: "/app/api/storage" + annotations: + helm.sh/resource-policy: keep + persistentVolumeClaim: + existingClaim: "" + hostPath: "/data/dify" + ## Dify App Data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + ## ReadWriteMany access mode required for `api` and `worker` + ## + storageClass: + accessModes: ReadWriteMany + size: 5Gi + subPath: "" + s3: + endpoint: "https://xxx.r2.cloudflarestorage.com" + accessKey: "#REPLACE_ME#" + secretKey: "#REPLACE_ME#" + region: "us-east-1" + bucketName: "your-bucket-name" + addressType: "" + useAwsManagedIam: false + useAwsS3: true + azureBlob: + accountName: "" + accountKey: "" + containerName: "" + accountUrl: "" + # Optional Azure EndpointSuffix, e.g. "core.windows.net" or "core.chinacloudapi.cn" + endpointSuffix: "" + aliyunOss: + endpoint: "your-endpoint" + bucketName: "your-bucket-name" + accessKey: "#REPLACE_ME#" + secretKey: "#REPLACE_ME#" + region: "ap-southeast-1" + googleStorage: + bucketName: "your-bucket-name" + # The service account JSON key file content, base64 encoded + serviceAccountJson: "" + tencentCos: + bucketName: "your-bucket-name" + region: "" + secretId: "#REPLACE_ME" + secretKey: "#REPLACE_ME" + scheme: "https" + huaweiObs: + server: "your-server-url" + bucketName: "your-bucket-name" + accessKey: "#REPLACE_ME#" + secretKey: "#REPLACE_ME#" + volcengineTos: + bucketName: "your-bucket-name" + accessKey: "#REPLACE_ME#" + secretKey: "#REPLACE_ME#" + endpoint: "tos-endpoint" + region: "your-region" + +################################### +# Mail Configuration +################################### +mail: + # Mail configuration, support: resend, smtp + type: "" + # default email sender from email address, if not not given specific address + defaultSender: "YOUR EMAIL FROM (eg: no-reply )" + # the api-key for resend (https://resend.com) + resend: + apiKey: "" + apiUrl: https://api.resend.com + smtp: + server: "" + port: 587 + username: "" + password: "" + useTLS: false + +################################### +# External postgres +################################### +externalPostgres: + enabled: false + address: localhost + port: 5432 + credentials: + dify: + database: "dify" + username: "postgres" + password: "#REPLACE_ME#" + sslmode: "require" + # extras: "options=-c search_path=your-schema -c your-other-option=xxx" + extras: "" + charset: "" + uriScheme: "postgresql" + plugin_daemon: + database: "dify_plugin_daemon" + username: "postgres" + password: "#REPLACE_ME#" + sslmode: "require" + # extras: "options=-c search_path=your-schema -c your-other-option=xxx" + extras: "" + charset: "" + uriScheme: "postgresql" + enterprise: + database: "enterprise" + username: "postgres" + password: "#REPLACE_ME#" + sslmode: "require" + # extras: "options=-c search_path=your-schema -c your-other-option=xxx" + extras: "" + charset: "" + uriScheme: "postgresql" + audit: + database: "audit" + username: "postgres" + password: "#REPLACE_ME#" + sslmode: "require" + # extras: "options=-c search_path=your-schema -c your-other-option=xxx" + extras: "" + charset: "" + uriScheme: "postgresql" +postgresql: + enabled: false + name: postgres + image: + repository: langgenius/postgresql + registry: docker.io + global: + storageClass: "" + postgresql: + auth: + postgresPassword: "#REPLACE_ME#" + database: "dify" + # username: "" + # password: "" + primary: + initdb: + scripts: + my_init_script.sh: | + #!/bin/bash + set -e + echo "Creating database..." + PGPASSWORD=$POSTGRES_PASSWORD psql -h localhost -U postgres << 'EOF' + SELECT 'CREATE DATABASE enterprise' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'enterprise')\gexec + SELECT 'CREATE DATABASE audit' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'audit')\gexec + EOF + +################################### +# External Redis +################################### +externalRedis: + enabled: true + useSSL: false + + host: "redis.example" + port: 6379 + username: "" + password: "#REPLACE_ME#" + db: 0 + + sentinel: + enabled: false + nodes: "" # Comma-separated list of Redis Sentinel nodes (host:port) + serviceName: "" # Name of the Redis Sentinel service to monitor + username: "" + password: "#REPLACE_ME#" + socketTimeout: 0.1 # Socket timeout in seconds for Redis Sentinel connections + + cluster: + enabled: false + nodes: "#REPLACE_ME#" # Comma-separated list of Redis Clusters nodes (host:port) + password: "#REPLACE_ME#" + +redis: + enabled: false + image: + repository: langgenius/redis + registry: docker.io + tag: 6.2.16-debian-12-r3 + global: + redis: + password: "#REPLACE_ME#" + +################################### +# vectorDB +################################### +vectorDB: + useExternal: true + # The type of vector database, support: weaviate, qdrant, milvus, relyt (pgvectors), pgvecto-rs, tencent, opensearch, elasticsearch, analyticdb, lindorm + # For more details, please refer to the documentation: https://github.com/langgenius/dify/blob/main/api/core/rag/datasource/vdb/vector_type.py + externalType: "qdrant" + externalWeaviate: + endpoint: "http://weaviate:8080" + apiKey: "#REPLACE_ME#" + # External Qdrant + externalQdrant: + endpoint: "http://your-qdrant-cluster-url.qdrant.tech/" + apiKey: "#REPLACE_ME#" + # External Milvus + externalMilvus: + # URI for connecting to the Milvus server (e.g., 'http://localhost:19530' or 'https://milvus-instance.example.com:19530') + uri: "http://127.0.0.1:19530" + token: "" + user: "" + password: "" + database: "default" + externalRelyt: + host: "your-relyt.domain" + port: 5431 + user: "postgres" + password: "#REPLACE_ME#" + database: "pgvectors" + externalPgVectoRS: + host: "your-pgvectors.domain" + port: 5432 + user: "postgres" + password: "#REPLACE_ME#" + database: "pgvectors" + externalTencentVectorDB: + host: "your-tencent-vector-db.domain" + apiKey: "#REPLACE_ME#" + timeout: 30 + username: "tencent" + password: "#REPLACE_ME#" + shard: 1 + replicas: 2 + database: "tencent" + externalOpenSearch: + host: "your-opensearch.domain" + port: 9200 + user: "" + password: "#REPLACE_ME#" + useTLS: false + externalElasticsearch: + host: "127.0.0.1" + port: 9200 + username: "elastic" + password: "#REPLACE_ME#" + externalAnalyticDB: + keyID: "#REPLACE_ME#" + keySecret: "#REPLACE_ME#" + regionID: "#REPLACE_ME#" + instanceID: "#REPLACE_ME#" + account: "#REPLACE_ME#" + password: "#REPLACE_ME#" + host: "#REPLACE_ME#" + port: 5432 + namespace: "" + namespacePassword: "" + minConnection: 1 + maxConnection: 5 + externalLindorm: + url: "http://localhost:30070" + username: "admin" + password: "admin" + indexType: "hnsw" + distanceType: "l2" + usingUgc: true + queryTimeout: 2.0 +qdrant: + enabled: false + replicaCount: 3 + image: + repository: langgenius/qdrant + pullPolicy: IfNotPresent + tag: "v1.7.3" + apiKey: "dify123456" + persistence: + accessModes: ["ReadWriteOnce"] + size: 10Gi + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi +weaviate: + enabled: false + +imagePullSecrets: []