#!/usr/bin/env bash # ============================================================ # LambdaAgentPaaS — Mac/Linux 一键启动脚本 # # 用法: # ./deploy/start.sh # 正常启动(首次会自动初始化) # ./deploy/start.sh --build # 强制重新构建镜像 # ./deploy/start.sh --reset # 清除所有数据重新初始化(谨慎!) # # 要求: Docker Desktop 已安装并运行 # ============================================================ set -euo pipefail # ── 路径常量 ───────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" DATA_DIR="${HOME}/.agentpaas/data" ENV_FILE="${HOME}/.agentpaas/.env" APP_PORT="${AGENTPAAS_PORT:-8000}" APP_URL="http://localhost:${APP_PORT}" # ── 颜色输出 ───────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${BLUE}ℹ ${NC}$*"; } success() { echo -e "${GREEN}✅ $*${NC}"; } warn() { echo -e "${YELLOW}⚠️ $*${NC}"; } error() { echo -e "${RED}❌ $*${NC}" >&2; } header() { echo -e "\n${BOLD}${CYAN}$*${NC}\n"; } # ── 参数解析 ───────────────────────────────────────────────── BUILD_FLAG="" RESET_FLAG="" for arg in "$@"; do case $arg in --build) BUILD_FLAG="--build" ;; --reset) RESET_FLAG="yes" ;; esac done # ── 检查 Docker ────────────────────────────────────────────── check_docker() { if ! command -v docker &>/dev/null; then error "未找到 Docker。请先安装 Docker Desktop:" echo " https://docs.docker.com/desktop/install/mac-install/" echo "" if [[ "$OSTYPE" == "darwin"* ]]; then read -p "是否现在打开下载页面? (y/N) " yn [[ "$yn" =~ ^[Yy]$ ]] && open "https://docs.docker.com/desktop/install/mac-install/" fi exit 1 fi if ! docker info &>/dev/null 2>&1; then info "Docker Desktop 未运行,正在启动..." if [[ "$OSTYPE" == "darwin"* ]]; then open -a "Docker Desktop" 2>/dev/null || open -a "Docker" 2>/dev/null || true fi echo -n "等待 Docker 就绪" local wait=0 until docker info &>/dev/null 2>&1; do echo -n "." sleep 2 ((wait+=2)) if (( wait > 60 )); then echo "" error "Docker 启动超时(60s)。请手动启动 Docker Desktop 后重试。" exit 1 fi done echo "" success "Docker 已就绪" fi } # ── 检查 docker compose ────────────────────────────────────── check_compose() { if docker compose version &>/dev/null 2>&1; then COMPOSE_CMD="docker compose" elif command -v docker-compose &>/dev/null; then COMPOSE_CMD="docker-compose" else error "未找到 docker compose。请升级 Docker Desktop 到最新版本。" exit 1 fi } # ── 初始化数据目录 ──────────────────────────────────────────── init_data_dir() { mkdir -p "${DATA_DIR}/instances" mkdir -p "${DATA_DIR}/knowledge_bases" mkdir -p "${DATA_DIR}/logs" # 限制 data 目录权限(避免被其他用户读取) chmod 700 "${HOME}/.agentpaas" 2>/dev/null || true } # ── 初始化 .env 配置文件 ───────────────────────────────────── init_env() { if [[ -f "$ENV_FILE" && -z "$RESET_FLAG" ]]; then return 0 # 已存在且不是 reset 模式 fi header "🔧 首次配置初始化" mkdir -p "$(dirname "$ENV_FILE")" # 复制模板 cp "${PROJECT_DIR}/.env.template" "$ENV_FILE" # 自动生成 MASTER_KEY(安全随机 64 字节 hex) if command -v python3 &>/dev/null; then MASTER_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") else MASTER_KEY=$(openssl rand -hex 32) fi # 写入 DATA_DIR 和 MASTER_KEY sed -i.bak "s|^AGENTPAAS_DATA_DIR=.*|AGENTPAAS_DATA_DIR=${DATA_DIR}|" "$ENV_FILE" sed -i.bak "s|^AGENTPAAS_MASTER_KEY=.*|AGENTPAAS_MASTER_KEY=${MASTER_KEY}|" "$ENV_FILE" rm -f "${ENV_FILE}.bak" success "配置文件已创建: $ENV_FILE" echo "" warn "请编辑 ${ENV_FILE} 填写至少一个 LLM API Key:" echo " DASHSCOPE_API_KEY= (阿里云百炼,国内推荐)" echo " ANTHROPIC_API_KEY= (Claude)" echo " OPENAI_API_KEY= (GPT)" echo "" echo "编辑完成后重新运行此脚本。" echo "" read -p "是否现在打开配置文件编辑? (Y/n) " yn yn="${yn:-Y}" if [[ "$yn" =~ ^[Yy]$ ]]; then # 尝试用系统默认编辑器打开 ${EDITOR:-$(command -v nano || command -v vi || echo "open")} "$ENV_FILE" fi } # ── 检查是否配置了至少一个 API Key ─────────────────────────── check_api_keys() { local has_key=false local key_vars=(DASHSCOPE_API_KEY OPENAI_API_KEY ANTHROPIC_API_KEY DEEPSEEK_API_KEY ZHIPU_API_KEY MOONSHOT_API_KEY) for var in "${key_vars[@]}"; do val=$(grep "^${var}=" "$ENV_FILE" 2>/dev/null | cut -d= -f2 | tr -d ' \r\n') if [[ -n "$val" ]]; then has_key=true break fi done if [[ "$has_key" == "false" ]]; then warn "未检测到任何 LLM API Key。" warn "Agent 功能将不可用,直到在 ${ENV_FILE} 中配置至少一个 API Key。" echo "" read -p "继续启动? (y/N) " yn [[ "$yn" =~ ^[Yy]$ ]] || exit 0 fi } # ── 启动服务 ───────────────────────────────────────────────── start_services() { header "🚀 启动 LambdaAgentPaaS" cd "$PROJECT_DIR" # 将 DATA_DIR 导出供 docker-compose.yml 使用 export AGENTPAAS_DATA_DIR="$DATA_DIR" # 加载 .env 中的其他变量 set -a source "$ENV_FILE" set +a if [[ -n "$RESET_FLAG" ]]; then info "清除旧容器和镜像..." $COMPOSE_CMD down --remove-orphans 2>/dev/null || true docker rmi lambdagentpaas:latest 2>/dev/null || true fi info "构建并启动容器(首次构建约 3-5 分钟)..." $COMPOSE_CMD up -d ${BUILD_FLAG} } # ── 等待健康检查 ───────────────────────────────────────────── wait_for_health() { echo -n "等待服务就绪" local wait=0 until curl -sf "${APP_URL}/health" &>/dev/null; do echo -n "." sleep 2 ((wait+=2)) if (( wait > 120 )); then echo "" error "服务启动超时(120s)" echo "" echo "查看日志:" echo " docker logs lambdagentpaas" exit 1 fi done echo "" } # ── 打印成功信息 ───────────────────────────────────────────── print_success() { echo "" echo -e "${GREEN}╔══════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ 🎉 LambdaAgentPaaS 已成功启动! ║${NC}" echo -e "${GREEN}╠══════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║ Web 界面: ${BOLD}${APP_URL}${NC}${GREEN} ║${NC}" echo -e "${GREEN}║ API 文档: ${APP_URL}/docs ║${NC}" echo -e "${GREEN}╠══════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║ 数据目录: ${DATA_DIR}${NC}" echo -e "${GREEN}║ 配置文件: ${ENV_FILE}${NC}" echo -e "${GREEN}╠══════════════════════════════════════════════╣${NC}" echo -e "${GREEN}║ 停止服务: ./deploy/stop.sh ║${NC}" echo -e "${GREEN}║ 查看日志: docker logs -f lambdagentpaas ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════╝${NC}" echo "" } # ── 主流程 ─────────────────────────────────────────────────── main() { header "LambdaAgentPaaS 启动器" check_docker check_compose init_data_dir init_env check_api_keys start_services wait_for_health print_success # 自动打开浏览器 if [[ "$OSTYPE" == "darwin"* ]]; then open "$APP_URL" elif command -v xdg-open &>/dev/null; then xdg-open "$APP_URL" fi } main "$@"