#!/usr/bin/env bash # ============================================================================= # Fischer AgentKit — 本地开发环境一键启动 # ============================================================================= # # 启动:后端服务(+ 可选 Tauri 桌面客户端) # 停止:Ctrl+C 或运行 scripts/dev-stop.sh # # 用法: # bash scripts/dev-start.sh # Web 模式(agentkit gui) # bash scripts/dev-start.sh --tauri # Web + Tauri 桌面客户端 # bash scripts/dev-start.sh --serve # 仅后端 API # bash scripts/dev-start.sh --init # 首次运行(初始化 DB) # bash scripts/dev-start.sh --help # 帮助 # # 依赖: # Python >= 3.11, Node.js >= 18, Redis, PostgreSQL (均自动检查) # --tauri 需要:Rust 工具链(rustup / brew install rust) # # 端口(与 .env.dev 保持一致): # 18001 — AgentKit 后端 API # 18002 — Web GUI(前端 + 内置静态服务) # 15173 — Vite 开发服务器(--tauri 模式) # 15174 — Vite HMR websocket # 6381 — Redis(agentkit 专属容器,避免与 pms-redis:6379 / geo_redis:6380 冲突) # 5435 — PostgreSQL+pgvector(agentkit 专属容器,避免与 geo_db:5433 冲突) # # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_ROOT" # ── 颜色 ──────────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # ── 帮助 ──────────────────────────────────────────────────────────────────── show_help() { cat <<-'EOF' Fischer AgentKit — 本地开发环境启动 用法: bash scripts/dev-start.sh [选项] 选项: --tauri 启动 Tauri 桌面客户端(Vite 热重载 + 原生窗口) --serve 仅启动后端 API(无前端) --init 首次运行,初始化数据库和测试用户 --help 显示此帮助 模式说明: 默认 Web 模式:agentkit gui (前后端 + 内置静态服务) --tauri Tauri 模式:后端 API + Vite (:15173) + Tauri 桌面窗口 端口映射: 18001 — 后端 API 18002 — Web GUI / 前端静态服务 15173 — Vite 开发服务器(--tauri 模式) 6381 — Redis(agentkit 专属容器) 5435 — PostgreSQL+pgvector(agentkit 专属容器) EOF } # ── 参数解析 ──────────────────────────────────────────────────────────────── MODE="gui" INIT_DB=0 while [[ $# -gt 0 ]]; do case $1 in --tauri) MODE="tauri"; shift ;; --serve) MODE="serve"; shift ;; --init) INIT_DB=1; shift ;; --help|-h) show_help; exit 0 ;; *) shift ;; esac done # ── 日志函数 ──────────────────────────────────────────────────────────────── ok() { echo -e " ${GREEN}OK${NC} $*"; } info() { echo -e " ${CYAN}→${NC} $*"; } warn() { echo -e " ${YELLOW}!${NC} $*"; } fail() { echo -e " ${RED}FAIL${NC} $*" >&2; } section() { echo "" echo -e "${CYAN}────────────────────────────────────────${NC}" echo -e "${CYAN} $*${NC}" echo -e "${CYAN}────────────────────────────────────────${NC}" } # ── 进度状态机 ────────────────────────────────────────────────────────────── # 0=未开始, 1=进行中, 2=成功, 3=失败 S_DEPS=0 S_ENV=0 S_REDIS=0 S_PG=0 S_BACKEND=0 S_FRONTEND=0 S_TAURI=0 set_status() { local step=$1 val=$2 case $step in deps) S_DEPS=$val ;; env) S_ENV=$val ;; redis) S_REDIS=$val ;; pg) S_PG=$val ;; backend) S_BACKEND=$val ;; frontend) S_FRONTEND=$val ;; tauri) S_TAURI=$val ;; esac } print_status() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "${CYAN} 启动状态总览${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "$([[ $S_DEPS -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 依赖检查" echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 环境配置" echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") Redis (:6381)" echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") PostgreSQL (:5435)" echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 后端服务 (:18001)" if [[ $MODE == "gui" || $MODE == "tauri" ]]; then echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") 前端服务 (:18002)" fi if [[ $MODE == "tauri" ]]; then echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}OK${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}FAIL${NC}" || echo " ${YELLOW}..${NC}") Tauri 客户端" fi } # ── 前置检查 ──────────────────────────────────────────────────────────────── check_deps() { section "检查依赖" set_status deps 1 for cmd in python3 node npm; do if ! command -v "$cmd" &>/dev/null; then fail "缺少依赖: $cmd" set_status deps 3 return 1 fi done ok "Python $(python3 --version 2>&1 | awk '{print $2}')" ok "Node $(node --version 2>&1)" ok "npm $(npm --version 2>&1)" # Python 版本 >= 3.11 if ! python3 -c 'import sys; sys.exit(0 if (sys.version_info.major, sys.version_info.minor) >= (3, 11) else 1)'; then fail "Python 版本需 >= 3.11" set_status deps 3 return 1 fi # Tauri 模式需要 Rust if [[ $MODE == "tauri" ]]; then if ! command -v rustc &>/dev/null; then warn "未检测到 Rust(Tauri 需要):请运行 brew install rust 或 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" else ok "Rust $(rustc --version 2>&1 | awk '{print $2}')" fi fi set_status deps 2 } check_env() { section "检查环境配置" set_status env 1 if [[ ! -f .env ]]; then warn "未找到 .env,将使用环境变量默认值" if [[ -f .env.example ]]; then cp .env.example .env ok "已从 .env.example 生成 .env" fi else ok ".env 存在" fi set_status env 2 } check_redis() { section "检查 Redis" set_status redis 1 # agentkit 专属容器映射到 6381,避免与 pms-redis(6379) / geo_redis(6380) 冲突 if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then ok "Redis 运行中 (127.0.0.1:6381, agentkit 专属容器)" set_status redis 2 return 0 fi warn "Redis 未运行 (6381),启动 agentkit 专属 Docker 容器..." if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d redis &>/dev/null; then sleep 2 if redis-cli -h 127.0.0.1 -p 6381 ping 2>/dev/null | grep -q PONG; then ok "Redis Docker 容器启动成功 (127.0.0.1:6381)" set_status redis 2 return 0 fi fi fail "Redis 启动失败(请确保 Docker 运行中)" set_status redis 3 return 1 } check_postgres() { section "检查 PostgreSQL" set_status pg 1 # agentkit 专属容器映射到 5435,避免与 geo_db(5433) 冲突 if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then ok "PostgreSQL 运行中 (127.0.0.1:5435, agentkit 专属容器)" set_status pg 2 return 0 fi warn "PostgreSQL 未运行 (5435),启动 agentkit 专属 Docker 容器..." if docker compose -f docker-compose.yaml -f docker-compose.dev.yml up -d postgres &>/dev/null; then sleep 3 if PGPASSWORD=agentkit psql -h 127.0.0.1 -p 5435 -U agentkit -d agentkit -c "SELECT 1" &>/dev/null; then ok "PostgreSQL Docker 容器启动成功 (127.0.0.1:5435)" set_status pg 2 return 0 fi fi warn "PostgreSQL 启动失败(bitable 等功能可能受限,继续启动后端...)" set_status pg 2 # 不阻塞,继续 return 0 } # ── 安装依赖 ──────────────────────────────────────────────────────────────── install_deps() { section "安装依赖" info "后端 Python 依赖..." # 虚拟环境 if [[ ! -d .venv ]]; then python3 -m venv .venv ok "虚拟环境 .venv 创建完成" fi source .venv/bin/activate pip install -q -U pip pip install -e ".[dev]" -q ok "后端依赖安装完成" # 前端 npm 依赖 local FE_DIR="$PROJECT_ROOT/src/agentkit/server/frontend" if [[ ! -d "$FE_DIR/node_modules" ]]; then info "前端 npm 依赖..." cd "$FE_DIR" npm install cd "$PROJECT_ROOT" ok "前端依赖安装完成" else ok "前端 node_modules 已存在" fi } # ── 启动后端 ──────────────────────────────────────────────────────────────── start_backend() { section "启动后端服务" set_status backend 1 info "启动后端 API (:18001)..." source .venv/bin/activate agentkit serve --port 18001 & BACKEND_PID=$! # 等待健康检查就绪(最多 60 秒) info "等待后端就绪..." local attempt=0 while [[ $attempt -lt 60 ]]; do if curl -sf http://127.0.0.1:18001/api/v1/health &>/dev/null; then ok "后端 API 就绪 (http://127.0.0.1:18001, PID $BACKEND_PID)" set_status backend 2 return 0 fi # 检查进程是否还活着 if ! kill -0 $BACKEND_PID 2>/dev/null; then fail "后端进程意外退出" set_status backend 3 return 1 fi sleep 1 ((attempt++)) [[ $((attempt % 10)) -eq 0 ]] && info "等待中... (${attempt}s)" done fail "后端启动超时(60s 内未响应健康检查)" kill $BACKEND_PID 2>/dev/null || true set_status backend 3 return 1 } # ── 启动 Web GUI ──────────────────────────────────────────────────────────── start_gui() { section "启动 Web GUI" set_status frontend 1 info "启动 Web GUI (:18002)..." source .venv/bin/activate agentkit gui --port 18002 & GUI_PID=$! # 等待就绪 info "等待 Web GUI 就绪..." local attempt=0 while [[ $attempt -lt 60 ]]; do if curl -sf http://127.0.0.1:18002/api/v1/health &>/dev/null; then ok "Web GUI 就绪 (http://127.0.0.1:18002, PID $GUI_PID)" set_status frontend 2 return 0 fi if ! kill -0 $GUI_PID 2>/dev/null; then fail "Web GUI 进程意外退出" set_status frontend 3 return 1 fi sleep 1 ((attempt++)) [[ $((attempt % 10)) -eq 0 ]] && info "等待中... (${attempt}s)" done fail "Web GUI 启动超时" kill $GUI_PID 2>/dev/null || true set_status frontend 3 return 1 } # ── 启动 Tauri ───────────────────────────────────────────────────────────── start_tauri() { section "启动 Tauri 桌面客户端" set_status tauri 1 local FE_DIR="$PROJECT_ROOT/src/agentkit/server/frontend" if ! command -v tauri &>/dev/null; then warn "Tauri CLI 未安装,跳过桌面客户端" info "安装方式:npm install -g @tauri-apps/cli" set_status tauri 2 return 0 fi info "启动 Tauri 桌面客户端..." info " (Vite → :15173, 后端 API → :18001)" cd "$FE_DIR" npm run tauri dev & TAURI_PID=$! info "Tauri 启动中(首次运行需要编译 Rust,可能需要几分钟)..." ok "Tauri 进程已启动 (PID $TAURI_PID)" info "桌面窗口将自动打开,如未打开请手动查看终端输出" set_status tauri 2 cd "$PROJECT_ROOT" } # ── 数据库初始化 ──────────────────────────────────────────────────────────── init_db() { section "初始化数据库" info "创建测试用户..." local SETUP_SCRIPT="$PROJECT_ROOT/src/agentkit/server/frontend/e2e/setup-test-user.py" if [[ -f "$SETUP_SCRIPT" ]]; then source .venv/bin/activate if python3 "$SETUP_SCRIPT" 2>/dev/null; then ok "测试用户创建完成" else warn "测试用户创建失败(可能已存在或数据库未就绪)" fi fi } # ── 停止服务 ──────────────────────────────────────────────────────────────── stop_services() { echo "" info "正在停止所有服务..." for port in 18001 18002 15173 15174; do local pid=$(lsof -ti :$port 2>/dev/null || true) if [[ -n "$pid" ]]; then kill $pid 2>/dev/null && ok "端口 $port 已停止" || true fi done # Vite 进程 pkill -f "vite" 2>/dev/null && ok "Vite 进程已停止" || true echo "" ok "所有服务已停止。感谢使用!" } # ── 主流程 ───────────────────────────────────────────────────────────────── echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo -e "${CYAN} Fischer AgentKit — 本地开发环境启动${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" # 检查依赖 if ! check_deps; then echo "" fail "依赖检查失败,请先安装缺失的依赖" exit 1 fi check_env # 初始化数据库 [[ $INIT_DB -eq 1 ]] && init_db # 安装依赖 install_deps # 检查中间件 check_redis || true check_postgres || true echo "" # ── 启动服务 ──────────────────────────────────────────────────────────────── FAILED=0 case $MODE in serve) if ! start_backend; then FAILED=1; fi ;; gui) if ! start_backend; then FAILED=1; fi if [[ $FAILED -eq 0 ]] && ! start_gui; then FAILED=1; fi ;; tauri) if ! start_backend; then FAILED=1; fi if [[ $FAILED -eq 0 ]]; then start_tauri fi ;; esac # ── 状态总览 + 启动完成 ────────────────────────────────────────────────────── print_status echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" if [[ $FAILED -eq 0 ]]; then echo -e "${GREEN} 所有服务启动成功!${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" if [[ $MODE == "gui" ]]; then echo -e " Web GUI: ${GREEN}http://localhost:18002${NC}" echo " (在浏览器中打开,或直接在 http://localhost:18002 访问)" elif [[ $MODE == "tauri" ]]; then echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}" echo -e " Vite 热重载: ${GREEN}http://localhost:15173${NC}" echo " Tauri 桌面窗口应已自动打开" elif [[ $MODE == "serve" ]]; then echo -e " 后端 API: ${GREEN}http://localhost:18001${NC}" fi echo "" echo -e " ${YELLOW}按 Ctrl+C 停止所有服务${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" else echo -e "${RED} 服务启动失败,请查看上方错误信息${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" echo -e " 诊断命令:" echo -e " 查看日志: ${CYAN}curl http://127.0.0.1:18001/api/v1/health${NC}" echo -e " 停止服务: ${CYAN}bash scripts/dev-stop.sh${NC}" fi echo "" # 注册退出钩子 trap stop_services EXIT INT TERM # 保持脚本运行 if [[ $MODE == "tauri" ]]; then # Tauri 模式:等待 Tauri 进程 wait elif [[ $MODE == "gui" || $MODE == "serve" ]]; then # 等待后端进程 wait $BACKEND_PID 2>/dev/null || true fi