主要改动: fix(docker/dify-deploy): 修复脚本核心功能 - heredoc 单引号 bug: 'ENVEOF' 改为 ENVEOF,变量正确展开 - 端口默认值 8083/8084/8085 对齐 .env.prod 生产配置 - 加 dc_cmd() 兼容 docker-compose v1/v2 plugin - openssl rand 生成强随机密码与 SECRET_KEY(42 字符) - install 跳过已存在 .env,保护用户配置(管理员密码/SECRET_KEY) - read -p < /dev/tty 兼容非 tty 环境(CI/CD) - show-config 改用 DIFY_NGINX_PORT(nginx 入口)而非 APP_WEB_PORT docs(mvp-design): 修正 §3.2 workflow inputs 描述 - 实际只有 query,删除错误的 user_id input 声明 - 节点序列图同步更新 feat(aiChatService): 新增 Dify 客户端与适配器 - service/dify_client.go: Dify Workflow 调用 + SSE 解析 - service/dify_adapter.go: 与现有 chat_service 桥接 - provider/ai_chat_provider.go: Dubbo 入口简化 - main.go: 装配 ConversationRepository + DifyClient feat(migrations): 新增 AI 搭子会话表 ai_chat.sql - ai_conversations / ai_messages 表 + 索引 docs: 新增 Dify 集成设计文档 - 2026-06-29-ai-chat-dify-mvp-design.md (MVP 实施级) - 2026-06-29-ai-chat-dify-integration-v2-design.md (V2 演进路线图) - docs/dify/角角.yml (Workflow DSL 导出) config: 更新 env 模板与 docker 配置 - backend/.env.example: DIFY_* 环境变量声明 - docker/.env.prod: DIFY_API_BASE 对齐 8083 - docker/build.sh: 微调 - CLAUDE.md: 项目规范补充 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
391 lines
13 KiB
Bash
391 lines
13 KiB
Bash
#!/bin/bash
|
||
# ===================================================================
|
||
# TopFans Dify 部署脚本
|
||
# 功能:服务器上安装 Dify + 配置环境变量
|
||
# ===================================================================
|
||
#
|
||
# 使用前提:
|
||
# 1. 服务器已配置 SSH 免密登录
|
||
# 2. 服务器有足够的内存(推荐 4GB+)
|
||
#
|
||
# 使用方式:
|
||
# ./dify-deploy.sh install # 安装 Dify 到服务器
|
||
# ./dify-deploy.sh status # 查看 Dify 服务状态
|
||
# ./dify-deploy.sh logs # 查看 Dify 日志
|
||
# ./dify-deploy.sh restart # 重启 Dify
|
||
# ./dify-deploy.sh upgrade # 升级 Dify 版本
|
||
# ./dify-deploy.sh uninstall # 卸载 Dify
|
||
# ./dify-deploy.sh show-config # 显示需要配置到 TopFans 的环境变量
|
||
#
|
||
# ===================================================================
|
||
|
||
set -e
|
||
|
||
# ==================== 颜色定义 ====================
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
MAGENTA='\033[0;35m'
|
||
NC='\033[0m'
|
||
|
||
# ==================== 路径配置 ====================
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
cd "$SCRIPT_DIR"
|
||
|
||
# ==================== 服务器配置 ====================
|
||
SERVER_HOST="101.132.250.62"
|
||
SERVER_PORT="22"
|
||
SERVER_USER="root"
|
||
SERVER_PATH="/opt/dify/docker"
|
||
|
||
# ==================== Dify 配置 ====================
|
||
# 端口说明:
|
||
# EXPOSE_NGINX_PORT: 对外统一入口(nginx),TopFans DIFY_API_BASE 走这个
|
||
# CONSOLE_WEB_PORT: Dify 管理控制台(浏览器手动访问)
|
||
# APP_WEB_PORT: Dify API 容器端口(5001),内部用,外部不直接连
|
||
# 默认值对齐 docker/.env.prod 中 DIFY_API_BASE=http://101.132.250.62:8083/v1
|
||
DIFY_NGINX_PORT="8083" # Dify Web/API 统一入口(nginx)
|
||
DIFY_CONSOLE_PORT="8084" # Dify 控制台
|
||
DIFY_API_PORT="8085" # Dify API 容器直连(内部)
|
||
|
||
# 密码与密钥:留空,install 时由 openssl rand 生成强随机值
|
||
# (避免硬编码弱密码,所有 Dify 实例使用不同密钥)
|
||
DIFY_DB_PASSWORD=""
|
||
DIFY_REDIS_PASSWORD=""
|
||
|
||
# ==================== SSH 命令 ====================
|
||
ssh_cmd() {
|
||
ssh -o StrictHostKeyChecking=no -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "$@"
|
||
}
|
||
|
||
# ==================== docker-compose 命令检测 ====================
|
||
# 兼容 v1 二进制与 v2 插件(v2 在新版 Docker 默认安装,但 v1 命令不存在)
|
||
# 优先用 v1;没有则检查 v2 plugin 并建软链伪装成 v1(参考 deploy.sh:341)
|
||
dc_cmd() {
|
||
if ssh_cmd "command -v docker-compose" &>/dev/null; then
|
||
echo "docker-compose"
|
||
return 0
|
||
fi
|
||
if ssh_cmd "docker compose version" &>/dev/null; then
|
||
# v2 插件存在,建软链伪装成 v1
|
||
ssh_cmd "ln -sf /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose 2>/dev/null" || true
|
||
fi
|
||
if ! ssh_cmd "command -v docker-compose" &>/dev/null; then
|
||
print_msg "$RED" "❌ docker-compose 未安装且 v2 插件建软链失败"
|
||
return 1
|
||
fi
|
||
echo "docker-compose"
|
||
}
|
||
|
||
# ==================== 打印函数 ====================
|
||
print_step() {
|
||
echo ""
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE} $1${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
}
|
||
|
||
print_msg() {
|
||
local color=$1
|
||
local msg=$2
|
||
echo -e "${color}${msg}${NC}"
|
||
}
|
||
|
||
# ==================== 帮助信息 ====================
|
||
show_help() {
|
||
cat << EOF
|
||
${MAGENTA}TopFans Dify 部署脚本${NC}
|
||
|
||
${YELLOW}命令:${NC}
|
||
${GREEN}install${NC} 安装 Dify 到服务器(首次部署)
|
||
${GREEN}status${NC} 查看 Dify 服务状态
|
||
${GREEN}logs${NC} 查看 Dify 日志
|
||
${GREEN}restart${NC} 重启 Dify
|
||
${GREEN}upgrade${NC} 升级 Dify 版本
|
||
${GREEN}uninstall${NC} 卸载 Dify
|
||
${GREEN}show-config${NC} 显示需要配置到 TopFans 的环境变量
|
||
|
||
${YELLOW}前提准备:${NC}
|
||
1. 服务器已配置 SSH 密钥登录
|
||
2. 服务器内存 >= 4GB
|
||
|
||
${YELLOW}注意事项:${NC}
|
||
- Dify 使用独立端口(不与 TopFans 冲突)
|
||
- Dify 使用独立的 PostgreSQL 和 Redis
|
||
- Workflow 需要在 Dify 控制台手动导入
|
||
|
||
EOF
|
||
}
|
||
|
||
# ==================== 1. 安装 Dify ====================
|
||
do_install() {
|
||
print_step "🚀 安装 Dify 到服务器"
|
||
|
||
print_msg "$YELLOW" "目标服务器: ${SERVER_USER}@${SERVER_HOST}:${SERVER_PORT}"
|
||
print_msg "$YELLOW" "安装路径: ${SERVER_PATH}"
|
||
echo ""
|
||
|
||
# 检查服务器 Docker 环境
|
||
print_msg "$YELLOW" "检查 Docker 环境..."
|
||
local docker_version=$(ssh_cmd "docker --version 2>/dev/null || echo 'NOT_INSTALLED'")
|
||
print_msg "$GREEN" "Docker: ${docker_version}"
|
||
|
||
# 如果 Docker 未安装,先安装
|
||
if [ "$docker_version" = "NOT_INSTALLED" ]; then
|
||
print_msg "$YELLOW" "Docker 未安装,开始安装..."
|
||
ssh_cmd "curl -fsSL https://get.docker.com | sh"
|
||
ssh_cmd "systemctl start docker && systemctl enable docker"
|
||
print_msg "$GREEN" "✅ Docker 安装完成"
|
||
fi
|
||
|
||
# 检查 docker-compose
|
||
local compose_version=$(ssh_cmd "docker-compose --version 2>/dev/null || docker compose version 2>/dev/null || echo 'NOT_INSTALLED'")
|
||
print_msg "$GREEN" "docker-compose: ${compose_version}"
|
||
|
||
# 创建服务器目录
|
||
print_msg "$YELLOW" "创建目录..."
|
||
ssh_cmd "mkdir -p ${SERVER_PATH}"
|
||
print_msg "$GREEN" "✅ 目录创建完成"
|
||
|
||
# 检测并设置 docker-compose 命令(v1/v2 兼容)
|
||
local DC
|
||
DC=$(dc_cmd) || { print_msg "$RED" "❌ docker-compose 不可用,请先安装 Docker"; return 1; }
|
||
|
||
# 保护已存在的 .env:重跑 install 时保留用户配置(SECRET_KEY/管理员密码等)
|
||
if ssh_cmd "[ -f ${SERVER_PATH}/.env ]"; then
|
||
print_msg "$YELLOW" "⚠️ 检测到已存在的 .env,跳过下载和配置"
|
||
print_msg "$YELLOW" " 如需重新配置,请先备份 ${SERVER_PATH}/.env 后删除"
|
||
else
|
||
# 下载 Dify docker-compose
|
||
print_msg "$YELLOW" "下载 Dify docker-compose..."
|
||
ssh_cmd "cd ${SERVER_PATH} && \
|
||
curl -L https://raw.githubusercontent.com/langgenius/dify/main/docker/docker-compose.yaml \
|
||
-o docker-compose.yml 2>/dev/null || \
|
||
curl -L https://raw.githubusercontent.com/langgenius/dify/v0.15.0/docker/docker-compose.yaml \
|
||
-o docker-compose.yml"
|
||
print_msg "$GREEN" "✅ docker-compose.yml 下载完成"
|
||
|
||
# 下载 .env 示例文件
|
||
print_msg "$YELLOW" "下载 .env 配置..."
|
||
ssh_cmd "cd ${SERVER_PATH} && \
|
||
curl -L https://raw.githubusercontent.com/langgenius/dify/main/docker/.env.example \
|
||
-o .env 2>/dev/null || \
|
||
curl -L https://raw.githubusercontent.com/langgenius/dify/v0.15.0/docker/.env.example \
|
||
-o .env"
|
||
print_msg "$GREEN" "✅ .env 下载完成"
|
||
|
||
# 修改 .env 配置(端口、密码等)
|
||
# ★ heredoc delimiter 不可加单引号,否则 \$VAR 不展开
|
||
print_msg "$YELLOW" "配置 Dify 环境变量..."
|
||
|
||
ssh_cmd "cat > ${SERVER_PATH}/.env.custom << ENVEOF
|
||
# 端口配置(避免与 TopFans 冲突)
|
||
EXPOSE_NGINX_PORT=${DIFY_NGINX_PORT}
|
||
CONSOLE_WEB_PORT=${DIFY_CONSOLE_PORT}
|
||
APP_WEB_PORT=${DIFY_API_PORT}
|
||
|
||
# 数据库配置
|
||
DB_USERNAME=postgres
|
||
DB_HOST=localhost
|
||
DB_PORT=5432
|
||
DB_DATABASE=dify
|
||
POSTGRES_PASSWORD=\$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)
|
||
|
||
# Redis 配置
|
||
REDIS_PASSWORD=\$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)
|
||
|
||
# API 配置(Dify 官方要求 SECRET_KEY 至少 42 字符)
|
||
SECRET_KEY=\$(openssl rand -base64 42)
|
||
INIT_SECRET_KEY=\$(openssl rand -base64 42)
|
||
ENVEOF
|
||
"
|
||
ssh_cmd "cd ${SERVER_PATH} && cat .env .env.custom > .env.tmp && mv .env.tmp .env && rm -f .env.custom"
|
||
print_msg "$GREEN" "✅ 环境变量配置完成"
|
||
fi
|
||
|
||
# 启动 Dify
|
||
print_msg "$YELLOW" "启动 Dify 服务(首次启动需要几分钟)..."
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} up -d"
|
||
|
||
# 等待服务启动
|
||
print_msg "$YELLOW" "等待服务启动(最多 120 秒)..."
|
||
local count=0
|
||
while [ $count -lt 120 ]; do
|
||
local api_status=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://localhost:${DIFY_API_PORT}/health 2>/dev/null || echo '000'")
|
||
if [ "$api_status" = "200" ]; then
|
||
print_msg "$GREEN" "✅ Dify API 就绪"
|
||
break
|
||
fi
|
||
sleep 5
|
||
count=$((count + 5))
|
||
echo -n "."
|
||
done
|
||
echo ""
|
||
|
||
print_step "📊 Dify 安装结果"
|
||
print_msg "$GREEN" "✅ Dify 安装完成!"
|
||
echo ""
|
||
print_msg "$CYAN" "访问地址:"
|
||
print_msg "$YELLOW" " 控制台: http://${SERVER_HOST}:${DIFY_CONSOLE_PORT}"
|
||
print_msg "$YELLOW" " Web: http://${SERVER_HOST}:${DIFY_NGINX_PORT}"
|
||
print_msg "$YELLOW" " API: http://${SERVER_HOST}:${DIFY_API_PORT}"
|
||
echo ""
|
||
print_msg "$RED" "⚠️ 首次访问需要创建管理员账号!"
|
||
echo ""
|
||
print_msg "$CYAN" "下一步:"
|
||
print_msg "$YELLOW" " 1. 访问控制台 http://${SERVER_HOST}:${DIFY_CONSOLE_PORT},创建管理员账号"
|
||
print_msg "$YELLOW" " 2. 在控制台导入 Workflow(docs/dify/*.yml)"
|
||
print_msg "$YELLOW" " 3. 运行 ./dify-deploy.sh show-config 查看 TopFans 配置"
|
||
}
|
||
|
||
# ==================== 2. 查看 Dify 状态 ====================
|
||
do_status() {
|
||
print_step "📊 Dify 服务状态"
|
||
|
||
local DC
|
||
DC=$(dc_cmd) || return 1
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} ps 2>/dev/null || echo 'Dify 未安装或未启动'"
|
||
echo ""
|
||
|
||
# 健康检查
|
||
local api_status=$(ssh_cmd "curl -s -o /dev/null -w '%{http_code}' http://localhost:${DIFY_API_PORT}/health 2>/dev/null || echo '000'")
|
||
if [ "$api_status" = "200" ]; then
|
||
print_msg "$GREEN" "✅ Dify API 健康"
|
||
else
|
||
print_msg "$RED" "❌ Dify API 异常 (HTTP ${api_status})"
|
||
fi
|
||
}
|
||
|
||
# ==================== 3. 查看日志 ====================
|
||
do_logs() {
|
||
print_step "📋 Dify 日志"
|
||
print_msg "$YELLOW" "按 Ctrl+C 退出日志"
|
||
echo ""
|
||
local DC
|
||
DC=$(dc_cmd) || return 1
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} logs -f 2>/dev/null || echo 'Dify 未安装'"
|
||
}
|
||
|
||
# ==================== 4. 重启 ====================
|
||
do_restart() {
|
||
print_step "🔄 重启 Dify"
|
||
print_msg "$YELLOW" "正在重启..."
|
||
local DC
|
||
DC=$(dc_cmd) || return 1
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} restart 2>/dev/null"
|
||
sleep 10
|
||
do_status
|
||
}
|
||
|
||
# ==================== 5. 升级 ====================
|
||
do_upgrade() {
|
||
print_step "⬆️ 升级 Dify"
|
||
|
||
print_msg "$RED" "警告: 升级前建议备份数据!"
|
||
read -p "确认升级? (y/N): " confirm < /dev/tty
|
||
if [ "$confirm" != "y" ]; then
|
||
print_msg "$YELLOW" "已取消"
|
||
exit 0
|
||
fi
|
||
|
||
local DC
|
||
DC=$(dc_cmd) || return 1
|
||
|
||
print_msg "$YELLOW" "下载最新 docker-compose..."
|
||
ssh_cmd "cd ${SERVER_PATH} && \
|
||
curl -L https://raw.githubusercontent.com/langgenius/dify/main/docker/docker-compose.yaml \
|
||
-o docker-compose.yml"
|
||
|
||
print_msg "$YELLOW" "执行升级..."
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} up -d"
|
||
|
||
print_msg "$GREEN" "✅ 升级完成"
|
||
}
|
||
|
||
# ==================== 6. 卸载 ====================
|
||
do_uninstall() {
|
||
print_step "🗑️ 卸载 Dify"
|
||
|
||
print_msg "$RED" "警告: 此操作将删除所有 Dify 数据!"
|
||
read -p "确认卸载? (输入 'YES' 确认): " confirm < /dev/tty
|
||
if [ "$confirm" != "YES" ]; then
|
||
print_msg "$YELLOW" "已取消"
|
||
exit 0
|
||
fi
|
||
|
||
local DC
|
||
DC=$(dc_cmd) || return 1
|
||
|
||
print_msg "$YELLOW" "停止并删除容器..."
|
||
ssh_cmd "cd ${SERVER_PATH} && ${DC} down -v 2>/dev/null || true"
|
||
|
||
print_msg "$YELLOW" "删除数据卷..."
|
||
ssh_cmd "docker volume rm \$(docker volume list -q -f name=dify) 2>/dev/null || true"
|
||
|
||
print_msg "$GREEN" "✅ Dify 卸载完成"
|
||
}
|
||
|
||
# ==================== 7. 显示 TopFans 配置 ====================
|
||
show_topfans_config() {
|
||
print_step "📝 TopFans 后端配置"
|
||
|
||
print_msg "$CYAN" "需要在 /opt/topfans/docker/.env.prod 中配置以下环境变量:"
|
||
echo ""
|
||
print_msg "$YELLOW" "# ========== Dify 配置 =========="
|
||
print_msg "$YELLOW" "# Dify API 地址(走 nginx 统一入口 ${DIFY_NGINX_PORT},与 Dify 容器内 APP_WEB_PORT 不同)"
|
||
print_msg "$CYAN" "DIFY_API_BASE=http://${SERVER_HOST}:${DIFY_NGINX_PORT}/v1"
|
||
echo ""
|
||
print_msg "$YELLOW" "# Dify API Key(从 Dify 控制台获取)"
|
||
print_msg "$CYAN" "DIFY_API_KEY=app-你的APIKey"
|
||
echo ""
|
||
|
||
print_msg "$RED" "⚠️ 配置完成后需要重启 TopFans 服务:"
|
||
print_msg "$RED" " ./deploy.sh restart --server ${SERVER_HOST}"
|
||
}
|
||
|
||
# ==================== 主函数 ====================
|
||
main() {
|
||
if [ $# -eq 0 ]; then
|
||
show_help
|
||
exit 0
|
||
fi
|
||
|
||
local command=$1
|
||
shift
|
||
|
||
case $command in
|
||
install)
|
||
do_install
|
||
;;
|
||
status)
|
||
do_status
|
||
;;
|
||
logs)
|
||
do_logs
|
||
;;
|
||
restart)
|
||
do_restart
|
||
;;
|
||
upgrade)
|
||
do_upgrade
|
||
;;
|
||
uninstall)
|
||
do_uninstall
|
||
;;
|
||
show-config)
|
||
show_topfans_config
|
||
;;
|
||
-h|--help|help)
|
||
show_help
|
||
;;
|
||
*)
|
||
print_msg "$RED" "错误: 未知命令 '$command'"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@" |