#!/usr/bin/env bash # ============================================================ # LambdAgent PaaS 一键安装启动脚本 # 用法: # chmod +x setup.sh # ./setup.sh # 安装并启动(自动识别 发行包/源码 两种模式) # ./setup.sh --build # 构建普通 wheel 包到 dist/(含 .py 源码) # ./setup.sh --build-secure # 用 Nuitka 编译成 .so wheel 到 dist-secure/(防逆向,仍需 Python) # ./setup.sh --build-binary # 用 Nuitka 打成单文件二进制 dist-binary/(防逆向 + 免装 Python) # ./setup.sh --package # 打出「不含源码」的发行包 release/(优先用编译版) # ./setup.sh --docker # 使用 Docker 启动 # ./setup.sh --help # 查看帮助 # # 安装模式(install_deps 自动判定): # 发行模式 dist/*.whl 存在 → 从预编译 wheel 安装(终端用户拿到的包用这种) # 开发模式 无 wheel 但有源码目录 → editable 安装(开发者本机) # ============================================================ set -e # ── 颜色输出 ────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color info() { echo -e "${BLUE}[INFO]${NC} $1"; } ok() { echo -e "${GREEN}[OK]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } fail() { echo -e "${RED}[FAIL]${NC} $1"; exit 1; } # ── 项目根目录 ──────────────────────────────────────────── PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$PROJECT_DIR" # ── 帮助信息 ────────────────────────────────────────────── show_help() { echo "" echo "LambdAgent PaaS 一键安装启动脚本" echo "" echo "用法:" echo " ./setup.sh 安装依赖并启动服务(自动识别发行/源码模式)" echo " ./setup.sh --build 构建普通 wheel 包 → dist/*.whl(仍含 .py 源码)" echo " ./setup.sh --build-secure Nuitka 编译成 .so wheel → dist-secure/(防逆向,仍需 Python)" echo " ./setup.sh --build-binary Nuitka 打成单文件二进制 → dist-binary/(防逆向 + 免装 Python)" echo " ./setup.sh --package 打出不含源码的发行包 → release/(有编译版则优先用)" echo " ./setup.sh --docker 使用 Docker Compose 启动" echo " ./setup.sh --install 仅安装依赖,不启动服务" echo " ./setup.sh --launch 安装并一键启动 Agent 对话" echo " ./setup.sh --help 显示此帮助信息" echo "" echo "发布给终端用户的流程(开发者执行):" echo " ./setup.sh --build # 在源码树里先构建 wheel" echo " ./setup.sh --package # 组装 release/,里面只有 wheel + 启动脚本,无源码" echo " 然后把 release/ 压缩发给用户;用户解压后跑 ./setup.sh 即可" echo "" echo "环境要求:" echo " Python 方式: Python 3.10+" echo " Docker 方式: Docker + Docker Compose" echo "" } # ── 检查 Python ─────────────────────────────────────────── check_python() { if command -v python3 &>/dev/null; then PYTHON=python3 elif command -v python &>/dev/null; then PYTHON=python else fail "未找到 Python。请安装 Python 3.10+\n macOS: brew install python@3.12\n Ubuntu: sudo apt install python3 python3-venv python3-pip" fi # 检查版本 PY_VERSION=$($PYTHON -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') PY_MAJOR=$($PYTHON -c 'import sys; print(sys.version_info.major)') PY_MINOR=$($PYTHON -c 'import sys; print(sys.version_info.minor)') if [ "$PY_MAJOR" -lt 3 ] || { [ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 10 ]; }; then fail "Python 版本过低: $PY_VERSION(需要 3.10+)" fi ok "Python $PY_VERSION" } # ── 检查 Docker ─────────────────────────────────────────── check_docker() { if ! command -v docker &>/dev/null; then fail "未找到 Docker。请先安装 Docker Desktop\n https://www.docker.com/products/docker-desktop/" fi if ! docker info &>/dev/null 2>&1; then fail "Docker 未运行。请启动 Docker Desktop" fi if ! command -v docker-compose &>/dev/null && ! docker compose version &>/dev/null 2>&1; then fail "未找到 Docker Compose" fi ok "Docker $(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')" } # ── 配置 .env 文件 ──────────────────────────────────────── setup_env() { if [ ! -f .env ]; then if [ -f .env.example ]; then cp .env.example .env warn ".env 文件已从 .env.example 创建" echo "" echo " ┌─────────────────────────────────────────────┐" echo " │ 请编辑 .env 文件,填入你的 LLM API Key: │" echo " │ │" echo " │ 至少填写以下其中一个: │" echo " │ DASHSCOPE_API_KEY=sk-xxx │" echo " │ OPENAI_API_KEY=sk-xxx │" echo " │ ANTHROPIC_API_KEY=sk-xxx │" echo " │ DEEPSEEK_API_KEY=sk-xxx │" echo " │ │" echo " │ 编辑命令: nano .env 或 vim .env │" echo " └─────────────────────────────────────────────┘" echo "" read -p "是否现在编辑 .env 文件?(y/N) " -r if [[ $REPLY =~ ^[Yy]$ ]]; then if command -v nano &>/dev/null; then nano .env elif command -v vim &>/dev/null; then vim .env else info "请手动编辑 .env 文件后重新运行此脚本" exit 0 fi fi fi else ok ".env 文件已存在" fi } # ── 构建发行 wheel 包 ───────────────────────────────────── # 把 lambdagent / agentpaas 两个子包构建成 .whl 放进 dist/。 # wheel 是「打包后的安装件」——发给用户的不再是整个源码树(含 .git、 # 测试、商业计划书等),而是这两个 whl。注意:普通 wheel 内部仍是 .py # 源文件,它解决的是「别再把整个仓库 editable 送人」,不是代码混淆; # 真要防逆向需要 Nuitka 编译,见 docs/DESKTOP_MANUAL_zh.md「源码保护」一节。 build_wheels() { check_python if [ ! -d lambdagent ] || [ ! -d agentpaas ]; then fail "未找到源码目录(lambdagent/ agentpaas/)。--build 只能在源码树里执行。" fi # 构建 wheel 走 PEP 517 构建隔离,不依赖 .venv —— 直接用 $PYTHON 即可。 info "构建 wheel 包 → dist/ ..." mkdir -p dist rm -f dist/lambdagent-*.whl dist/agentpaas-*.whl $PYTHON -m pip wheel --no-deps ./lambdagent -w dist/ --quiet $PYTHON -m pip wheel --no-deps ./agentpaas -w dist/ --quiet echo "" ok "wheel 构建完成:" ls -1 dist/lambdagent-*.whl dist/agentpaas-*.whl 2>/dev/null | sed 's/^/ /' echo "" echo " 下一步: ./setup.sh --package 组装不含源码的发行包" echo "" } # ── 组装「不含源码」的发行包 ────────────────────────────── # 产出 release/:只含 wheel + 启动脚本 + 前端静态产物 + 配置样例 + 文档, # 不含 lambdagent/src、agentpaas/src、tests、.git、商业计划书等。 # 把 release/ 压缩后发给终端用户即可。 package_release() { # 优先用编译版(dist-secure/,.so wheel,防逆向);没有则退回普通 dist/ local WHEEL_SRC=dist if ls dist-secure/lambdagent-*.whl >/dev/null 2>&1 && ls dist-secure/agentpaas-*.whl >/dev/null 2>&1; then WHEEL_SRC=dist-secure info "检测到编译版 wheel,发行包将使用 dist-secure/(.so,防逆向)。" elif ! ls dist/lambdagent-*.whl >/dev/null 2>&1 || ! ls dist/agentpaas-*.whl >/dev/null 2>&1; then warn "未找到 wheel,先执行普通构建(如需防逆向请改用 ./setup.sh --build-secure)..." build_wheels fi # 从 agentpaas/pyproject.toml 取版本号(兼容无源码时回退到 wheel 文件名) local VER if [ -f agentpaas/pyproject.toml ]; then VER=$(grep -m1 '^version' agentpaas/pyproject.toml | sed -E 's/.*"([^"]+)".*/\1/') else VER=$(ls dist/agentpaas-*.whl 2>/dev/null | sed -E 's/.*agentpaas-([0-9][^-]*)-.*/\1/' | head -1) fi [ -z "$VER" ] && VER="dev" info "组装发行包 → release/ ..." rm -rf release mkdir -p release/dist cp "$WHEEL_SRC"/lambdagent-*.whl "$WHEEL_SRC"/agentpaas-*.whl release/dist/ cp setup.sh release/ chmod +x release/setup.sh # 前端静态产物(已编译,无源码) [ -d webui-dist ] && cp -R webui-dist release/ # 用户需要的配置样例 / 编排文件 / 文档(均非核心源码) [ -f .env.example ] && cp .env.example release/ [ -f docker-compose.yml ] && cp docker-compose.yml release/ [ -f Dockerfile ] && cp Dockerfile release/ 2>/dev/null || true [ -d agentexample ] && cp -R agentexample release/ mkdir -p release/docs [ -f docs/DESKTOP_MANUAL_zh.md ] && cp docs/DESKTOP_MANUAL_zh.md release/docs/ [ -f QUICK_START.md ] && cp QUICK_START.md release/ [ -f README.md ] && cp README.md release/ [ -f LICENSE ] && cp LICENSE release/ # 清掉 macOS 垃圾文件 find release -name '.DS_Store' -delete 2>/dev/null || true echo "" ok "发行包已生成: release/" echo " 内容(不含任何 src 源码 / .git / 内部文档):" (cd release && find . -maxdepth 2 -type f | sed 's/^/ /' | head -30) echo "" echo " 压缩发布: zip -r lambdagent-paas-${VER}.zip release/" echo " 用户使用: 解压 → cd release → ./setup.sh" echo "" } # ── 创建虚拟环境并安装依赖 ──────────────────────────────── # 自动判定两种模式: # 发行模式: 同目录 dist/*.whl 存在 → 从预编译 wheel 安装(终端用户) # 开发模式: 无 wheel 但有源码目录 → editable 安装(开发者本机) install_deps() { info "创建 Python 虚拟环境..." if [ ! -d .venv ]; then $PYTHON -m venv .venv ok "虚拟环境已创建: .venv/" else ok "虚拟环境已存在: .venv/" fi # 激活虚拟环境 source .venv/bin/activate pip install --quiet --upgrade pip >/dev/null 2>&1 if ls dist/lambdagent-*.whl >/dev/null 2>&1 && ls dist/agentpaas-*.whl >/dev/null 2>&1; then # ── 发行模式: 从 wheel 安装 ── info "检测到预编译 wheel,按【发行模式】安装..." # 先装 lambdagent(agentpaas 依赖它),再装 agentpaas; # 第三方依赖(fastapi 等)由 pip 从 PyPI 拉取。 pip install --quiet --upgrade dist/lambdagent-*.whl ok "lambdagent 已从 wheel 安装" pip install --quiet --upgrade dist/agentpaas-*.whl ok "agentpaas 已从 wheel 安装" elif [ -d lambdagent ] && [ -d agentpaas ]; then # ── 开发模式: editable 源码安装 ── info "未检测到 wheel,按【开发模式】editable 安装源码..." # 修复历史 bug: 原为 `pip install -e ".[all]"`(root 无 pyproject, # 实际安装失败),lambdagent 真实路径是 ./lambdagent。 pip install -e "./lambdagent[all]" --quiet 2>&1 | tail -1 ok "lambdagent 已安装(editable)" pip install -e "./agentpaas[dev]" --quiet 2>&1 | tail -1 ok "agentpaas 已安装(editable)" else fail "既无 dist/*.whl 也无源码目录(lambdagent/ agentpaas/),无法安装。" fi echo "" ok "所有依赖安装完成!" echo "" echo " 激活虚拟环境: source .venv/bin/activate" echo "" } # ── Python 方式启动 ─────────────────────────────────────── start_python() { source .venv/bin/activate # 加载 .env if [ -f .env ]; then set -a source .env set +a fi info "启动 AgentPaaS 服务(开发模式)..." echo "" echo " ┌─────────────────────────────────────────────┐" echo " │ 服务地址: http://localhost:${AGENTPAAS_PORT:-8000} │" echo " │ API 文档: http://localhost:${AGENTPAAS_PORT:-8000}/docs │" echo " │ 按 Ctrl+C 停止服务 │" echo " └─────────────────────────────────────────────┘" echo "" python -m agentpaas serve --dev } # ── Docker 方式启动 ─────────────────────────────────────── start_docker() { check_docker setup_env info "使用 Docker Compose 启动..." # 使用根目录的 docker-compose if docker compose version &>/dev/null 2>&1; then COMPOSE_CMD="docker compose" else COMPOSE_CMD="docker-compose" fi $COMPOSE_CMD -f docker-compose.yml up --build } # ── 一键启动 Agent 对话 ─────────────────────────────────── launch_agent() { source .venv/bin/activate # 加载 .env if [ -f .env ]; then set -a source .env set +a fi CONFIG_FILE="${1:-agent-config.yml}" if [ ! -f "$CONFIG_FILE" ]; then fail "Agent 配置文件不存在: $CONFIG_FILE\n 请创建配置文件或指定路径: ./setup.sh --launch your-config.yml" fi info "一键启动 Agent: $CONFIG_FILE" python -m agentpaas launch "$CONFIG_FILE" } # ── 主流程 ──────────────────────────────────────────────── main() { echo "" echo "================================================" echo " LambdAgent PaaS 一键安装启动" echo "================================================" echo "" case "${1:-}" in --help|-h) show_help exit 0 ;; --build) build_wheels ;; --build-secure) check_python bash scripts/build_secure.sh "$PYTHON" ;; --build-binary) check_python bash scripts/build_binary.sh "$PYTHON" ;; --package) package_release ;; --docker) start_docker ;; --install) check_python setup_env install_deps echo "安装完成! 启动服务请运行:" echo " source .venv/bin/activate" echo " python -m agentpaas serve --dev" ;; --launch) check_python setup_env install_deps launch_agent "${2:-}" ;; *) check_python setup_env install_deps start_python ;; esac } main "$@"