#!/bin/bash # # Qihu-AIFlow 智能自动化PaaS平台 - 一键启动脚本 # 支持 Docker Compose 环境和本地开发环境 # set -e # ============================================================ # 项目路径检测(兼容 Docker 和本地环境) # ============================================================ if [ -d "/app" ]; then PROJECT_DIR="/app" else PROJECT_DIR="/data/share/project" fi cd "$PROJECT_DIR" # >>> OUTBOUND PROXY START >>> export HTTP_PROXY=http://172.18.0.1:7890 export HTTPS_PROXY=http://172.18.0.1:7890 export NO_PROXY=localhost,127.0.0.1,::1,210.16.161.218,172.17.0.0/16,172.18.0.0/16,172.19.0.0/16,172.20.0.0/16,10.0.0.0/8,192.168.0.0/16,.svc,.local,casdoor,qihuaiflow-executor,qihu-aiflow-mysql,qihu-aiflow-redis,qihu-aiflow-pgvector,qihu-aiflow-casdoor,qihuyun.com,ai.qihuyun.com,docs.qihuyun.com,bot.qihuyun.com,siliconflow.cn,api.siliconflow.cn,deepseek.com,api.deepseek.com,volcengineapi.com,aliyun.com,aliyuncs.com,dysmsapi.aliyuncs.com,tencentcloudapi.com,baidubce.com,dashscope.com,qwen.ai,zhipuai.cn,moonshot.cn,minimax.chat,baichuan.com,chatglm.cn,xiaomimimo.com,token-plan-cn.xiaomimimo.com,mi.com,pypi.tuna.tsinghua.edu.cn,feishu.cn,open.feishu.cn,dingtalk.com,open.dingtalk.com,weixin.qq.com,developers.weixin.qq.com,qyapi.weixin.qq.com,ilinkai.weixin.qq.com,tencent.com # <<< OUTBOUND PROXY END <<< # 容器时区统一为北京时间(Asia/Shanghai), # 避免 Python datetime.now() 返回 UTC 写入 MySQL datetime 列导致 8 小时偏差。 export TZ=Asia/Shanghai # Python 后端目录(提前定义,供后续步骤使用) PYTHON_DIR="$PROJECT_DIR/backend/python" echo "========================================" echo " Qihu-AIFlow 智能自动化PaaS平台" echo " 版本: $(cat $PROJECT_DIR/VERSION 2>/dev/null || echo '1.0.0')" echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')" echo "========================================" # ============================================================ # Step 1: 加载配置 # ============================================================ echo "[1/5] 初始化配置..." # Docker 环境:直接用 docker-compose.yml 传入的环境变量,不加载 .env # 本地环境:加载 .env 兜底 if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then echo " - Docker 环境,使用容器环境变量" else echo " - 本地环境" if [ -f "$PROJECT_DIR/.env" ]; then set -a source "$PROJECT_DIR/.env" set +a echo " - 已加载 .env 配置文件" else echo " - 未找到 .env 文件,使用默认值" fi fi # 仅使用环境变量或默认值,不做.env与env的相互覆盖 MYSQL_HOST="${MYSQL_HOST:-localhost}" MYSQL_PORT="${MYSQL_PORT:-3306}" MYSQL_USER="${MYSQL_USER:-root}" MYSQL_PASSWORD="${MYSQL_PASSWORD:-pwd2026}" MYSQL_DATABASE="${MYSQL_DATABASE:-aiflow}" REDIS_HOST="${REDIS_HOST:-localhost}" REDIS_PORT="${REDIS_PORT:-6379}" REDIS_PASSWORD="${REDIS_PASSWORD:-pwd2026}" echo " MySQL : $MYSQL_HOST:$MYSQL_PORT/$MYSQL_DATABASE" echo " Redis : $REDIS_HOST:$REDIS_PORT" # ============================================================ # Step 1.5: Docker 环境额外等待(等待 MySQL 端口真正 ready) # ============================================================ if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then echo " - Docker 环境:等待 network/port 完全就绪 (10s)..." sleep 10 echo " - 等待完成" fi # 自动检测 MySQL 客户端 SSL 参数兼容性 # Docker环境(含MariaDB)使用 --ssl=0, 本地MySQL 8.x使用 --ssl-mode=DISABLED if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then MYSQL_SSL_FLAG="--ssl=0" else if mysql --ssl=0 -e "SELECT 1" &>/dev/null 2>&1; then MYSQL_SSL_FLAG="--ssl=0" else MYSQL_SSL_FLAG="--ssl-mode=DISABLED" fi fi echo " MySQL SSL 参数: $MYSQL_SSL_FLAG" # ============================================================ # Step 2: 等待 MySQL 就绪 # ============================================================ echo "[2/5] 等待 MySQL 就绪 (${MYSQL_HOST}:${MYSQL_PORT})..." echo -n " " MAX_RETRIES=60 RETRY_COUNT=0 while ! mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG -e "SELECT 1" &>/dev/null; do RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then echo "" echo " ================================================" echo " [错误] MySQL 连接超时" echo " ================================================" echo " 目标: ${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}" echo " 用户: ${MYSQL_USER}" echo " 已尝试: ${RETRY_COUNT} 次 (共等待约 $((RETRY_COUNT * 2)) 秒)" echo "" echo " --- 网络诊断 ---" echo "" # Step A1: DNS 解析测试 echo " [1] DNS 解析测试:" if command -v getent &>/dev/null; then getent hosts "$MYSQL_HOST" 2>&1 && echo " DNS 解析成功" || echo " DNS 解析失败: $MYSQL_HOST 无法解析" elif command -v nslookup &>/dev/null; then nslookup "$MYSQL_HOST" 2>&1 || echo " DNS 解析失败" else echo " (无可用的DNS诊断工具)" fi echo "" # Step A2: TCP 端口连通性测试 (nc) echo " [2] TCP 端口连通性测试 (nc -zv ${MYSQL_HOST} ${MYSQL_PORT}):" if command -v nc &>/dev/null; then nc -zv "$MYSQL_HOST" "$MYSQL_PORT" 2>&1 || echo " TCP 端口不可达" else echo " (nc 命令不可用,跳过)" fi echo "" # Step A3: TCP 端口连通性测试 (bash /dev/tcp) echo " [3] TCP 端口连通性测试 (bash /dev/tcp):" if timeout 3 bash -c "echo >/dev/tcp/${MYSQL_HOST}/${MYSQL_PORT}" 2>/dev/null; then echo " TCP 端口 ${MYSQL_HOST}:${MYSQL_PORT} 可达" else echo " TCP 端口 ${MYSQL_HOST}:${MYSQL_PORT} 不可达" fi echo "" # Step A4: MySQL 客户端连接测试(显示详细错误) echo " [4] MySQL 客户端连接测试 (详细错误信息):" mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG -e "SELECT 1" 2>&1 || true echo "" echo " --- 诊断结论 ---" echo " 可能原因:" echo " 1. MySQL 容器健康检查通过但 3306 端口尚未完全 ready" echo " 2. Docker 容器间网络不通(检查是否在同一 Docker 网络)" echo " 3. MySQL 用户名密码错误" echo " 4. MySQL 服务虽然运行但未监听 3306 端口" echo "" echo " 排查命令 (在宿主机执行):" echo " docker compose ps" echo " docker compose logs mysql --tail 50" echo " docker exec qihu-aiflow-mysql mysqladmin ping -h localhost -u root -ppwd2026" echo " docker inspect qihu-aiflow -f '{{.HostConfig.NetworkMode}}'" echo " docker network ls" echo " ================================================" exit 1 fi # 每5次或首次输出进度 if [ $((RETRY_COUNT % 5)) -eq 0 ]; then echo -n "." fi sleep 2 done echo "" echo " MySQL 已就绪 (尝试 $RETRY_COUNT 次)" # ============================================================ # Step 3: 数据库初始化(幂等执行) # ============================================================ echo "[3/5] 初始化数据库表结构..." mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG \ -e "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>/dev/null echo " 使用数据库: $MYSQL_DATABASE" MIGRATION_DIR="$PROJECT_DIR/migrations" if [ -d "$MIGRATION_DIR" ]; then for sql_file in $(ls "$MIGRATION_DIR"/*.sql 2>/dev/null | sort); do filename=$(basename "$sql_file") echo " - 执行: $filename" mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG \ "$MYSQL_DATABASE" < "$sql_file" 2>&1 | sed 's/^/ /' || echo " [警告] $filename 部分语句已跳过(可能已存在)" done echo " 数据库初始化完成" # ============================================================ # Step 3.5: 智能修复 admin 密码哈希 # 仅在密码哈希不可用(NULL/空/无效格式)时才重置为默认密码 # 如果用户已经修改过密码(有效bcrypt哈希),则保持不变 # 注意: 此步骤为可选操作,失败不影响服务启动 # ============================================================ echo " - 检查 admin 密码哈希有效性..." set +e # 临时允许错误 PYTHON_BIN="" # 查找可用的 Python(优先虚拟环境,其次系统 Python) if [ -f "$PROJECT_DIR/backend/python/.venv/bin/python3" ]; then PYTHON_BIN="$PROJECT_DIR/backend/python/.venv/bin/python3" elif command -v python3 &>/dev/null; then PYTHON_BIN="python3" fi if [ -n "$PYTHON_BIN" ]; then # 从数据库读取admin当前的密码哈希 CURRENT_HASH=$(mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG \ "$MYSQL_DATABASE" -sN -e "SELECT password_hash FROM users WHERE username = 'admin';" 2>/dev/null) # 用Python验证哈希是否有效(bcrypt $2b$格式) NEED_RESET=$($PYTHON_BIN -c " import sys hash_val = sys.argv[1] if len(sys.argv) > 1 else '' if not hash_val: print('YES') elif not hash_val.startswith('\$2b\$') and not hash_val.startswith('\$2a\$'): print('YES') else: # 哈希格式合法,不做强制覆盖(用户可能修改过密码) print('NO') " "$CURRENT_HASH" 2>/dev/null) || NEED_RESET="YES" if [ "$NEED_RESET" = "YES" ]; then ADMIN_HASH_FIX=$($PYTHON_BIN -c " import bcrypt valid_hash = bcrypt.hashpw(b'admin123', bcrypt.gensalt()).decode() print(valid_hash) " 2>/dev/null) || true if [ -n "$ADMIN_HASH_FIX" ]; then mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" $MYSQL_SSL_FLAG \ "$MYSQL_DATABASE" -e "UPDATE users SET password_hash = '$ADMIN_HASH_FIX' WHERE username = 'admin';" 2>/dev/null || true echo " admin 密码哈希已重置: admin123 (bcrypt) [原因: 旧哈希无效或缺失]" else echo " [警告] 无法生成 bcrypt 哈希,跳过 admin 密码修复" fi else echo " admin 密码哈希有效,保持用户当前密码不变" fi else echo " [警告] Python 不可用,跳过 admin 密码检查(不影响正常启动)" fi set -e # 恢复错误退出 else echo " [警告] 迁移脚本目录不存在: $MIGRATION_DIR" fi # ============================================================ # Step 4: 等待 Redis 就绪 # ============================================================ echo "[4/5] 等待 Redis 就绪 (${REDIS_HOST}:${REDIS_PORT})..." echo -n " " RETRY_COUNT=0 while ! redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" --no-auth-warning ping &>/dev/null; do RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -ge 30 ]; then echo "" echo " [警告] Redis 未能在 30 次重试内就绪,将无缓存继续启动" break fi if [ $((RETRY_COUNT % 5)) -eq 0 ]; then echo -n "." fi sleep 1 done if [ $RETRY_COUNT -lt 30 ]; then echo "" echo " Redis 已就绪" fi # ============================================================ # Step 5: Python 环境与启动 # ============================================================ echo "[5/5] 配置 Python 环境..." if [ -d "$PYTHON_DIR/.venv" ]; then echo " - 使用已存在的虚拟环境" export PATH="$PYTHON_DIR/.venv/bin:$PATH" elif [ -d "$PYTHON_DIR" ]; then echo " - 创建虚拟环境..." python3 -m venv "$PYTHON_DIR/.venv" export PATH="$PYTHON_DIR/.venv/bin:$PATH" echo " - 安装 Python 依赖..." pip install -r "$PYTHON_DIR/requirements.txt" -i https://pypi.tuna.tsinghua.edu.cn/simple -q 2>&1 | sed 's/^/ /' echo " - 依赖安装完成" else echo " [错误] Python 后端目录不存在: $PYTHON_DIR" exit 1 fi # 前端资源检查 if [ -d "$PROJECT_DIR/frontend/dist" ]; then echo " - 前端资源已就绪" else echo " [警告] 前端资源目录不存在,可能无法访问页面" fi # ============================================================ # Nginx & 后端启动 # ============================================================ # 如果 nginx 可用,则启动(Docker 环境) if command -v nginx &>/dev/null; then echo "" echo " Nginx 配置测试..." if nginx -t 2>&1; then echo " 启动 Nginx (端口 8080)..." nginx if [ $? -eq 0 ]; then echo " Nginx 已启动" else echo " [警告] Nginx 启动失败,前端页面可能无法访问" fi else echo " [警告] Nginx 配置有误,跳过启动" fi fi # 创建知识库文件存储目录 mkdir -p "$PROJECT_DIR/data/knowledge" echo " 知识库存储目录已就绪: $PROJECT_DIR/data/knowledge" # 启动后端服务(前台运行 - 容器必须保持前台进程) echo "" echo "========================================" echo " Qihu-AIFlow 启动完成" echo " 前端地址: http://localhost:12888" echo " 后端地址: http://localhost:18080" echo " 默认账号: admin / admin123" echo "========================================" echo "" # 防崩溃自检:确认 uvicorn 可导入,缺失则在 venv 内重装(避免依赖缺失导致崩溃循环) if ! python -c "import uvicorn" >/dev/null 2>&1; then echo " [自愈] 检测到依赖缺失,重新安装 Python 依赖..." pip install -r "$PYTHON_DIR/requirements.txt" -i https://pypi.tuna.tsinghua.edu.cn/simple -q 2>&1 | sed 's/^/ /' || echo " [警告] 依赖重装失败" fi cd "$PYTHON_DIR" exec python -m uvicorn main:app --host 0.0.0.0 --port 18080 --log-level info