501 lines
18 KiB
Bash
Executable File
501 lines
18 KiB
Bash
Executable File
#!/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}✓${NC} $*"; }
|
||
info() { echo -e " ${CYAN}→${NC} $*"; }
|
||
warn() { echo -e " ${YELLOW}!${NC} $*"; }
|
||
fail() { echo -e " ${RED}✗${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}✓${NC}" || [[ $S_DEPS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 依赖检查"
|
||
echo -e "$([[ $S_ENV -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_ENV -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 环境配置"
|
||
echo -e "$([[ $S_REDIS -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_REDIS -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") Redis (:6381)"
|
||
echo -e "$([[ $S_PG -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_PG -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") PostgreSQL (:5435)"
|
||
echo -e "$([[ $S_BACKEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_BACKEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 后端服务 (:18001)"
|
||
if [[ $MODE == "gui" || $MODE == "tauri" ]]; then
|
||
echo -e "$([[ $S_FRONTEND -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_FRONTEND -eq 3 ]] && echo " ${RED}✗${NC}" || echo " ${YELLOW}○${NC}") 前端服务 (:18002)"
|
||
fi
|
||
if [[ $MODE == "tauri" ]]; then
|
||
echo -e "$([[ $S_TAURI -eq 2 ]] && echo " ${GREEN}✓${NC}" || [[ $S_TAURI -eq 3 ]] && echo " ${RED}✗${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
|