#!/usr/bin/env bash # 构建 Linux 解压即用包:包含 Python 运行时、依赖、CLI 可执行程序和脚本文档。 set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" PYTHON_BIN="${PYTHON_BIN:-python3}" APP_NAME="pam-deploy-agent" RELEASE_NAME="${APP_NAME}-linux-x86_64" PACKAGE_EXTRAS="${PACKAGE_EXTRAS:-mcp,chat}" BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build/linux_self_contained}" DIST_DIR="${DIST_DIR:-$ROOT_DIR/dist/linux_self_contained}" RELEASE_DIR="$DIST_DIR/$RELEASE_NAME" ARCHIVE_PATH="$DIST_DIR/${RELEASE_NAME}.tar.gz" if [[ "$(uname -s)" != "Linux" ]]; then echo "该脚本需要在 Linux x86_64 构建机上运行。" exit 1 fi if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then echo "未找到 Python: $PYTHON_BIN" exit 1 fi echo "==> 清理旧构建目录" rm -rf "$BUILD_DIR" "$RELEASE_DIR" "$ARCHIVE_PATH" mkdir -p "$BUILD_DIR" "$DIST_DIR" echo "==> 创建构建虚拟环境" "$PYTHON_BIN" -m venv "$BUILD_DIR/venv" source "$BUILD_DIR/venv/bin/activate" echo "==> 安装构建依赖" python -m pip install --upgrade pip setuptools wheel python -m pip install pyinstaller echo "==> 安装项目依赖" if [[ -n "$PACKAGE_EXTRAS" ]]; then python -m pip install -e ".[${PACKAGE_EXTRAS}]" else python -m pip install -e . fi PYINSTALLER_EXTRA_ARGS=() if python -c "import importlib.util; raise SystemExit(0 if importlib.util.find_spec('prompt_toolkit') else 1)"; then PYINSTALLER_EXTRA_ARGS+=(--collect-submodules prompt_toolkit --collect-data prompt_toolkit) fi if python -c "import importlib.util; raise SystemExit(0 if importlib.util.find_spec('rich') else 1)"; then PYINSTALLER_EXTRA_ARGS+=(--collect-submodules rich) fi echo "==> 使用 PyInstaller 生成自带 Python 运行时的可执行目录" python -m PyInstaller \ --clean \ --noconfirm \ --name "$APP_NAME" \ --onedir \ --console \ --distpath "$BUILD_DIR/pyinstaller_dist" \ --workpath "$BUILD_DIR/pyinstaller_build" \ --specpath "$BUILD_DIR" \ --collect-submodules pam_deploy_graph \ --collect-submodules langgraph \ --hidden-import pam_deploy_graph.cli \ "${PYINSTALLER_EXTRA_ARGS[@]}" \ packaging/pyinstaller_entry.py echo "==> 组装发布目录" mkdir -p "$RELEASE_DIR" cp -a "$BUILD_DIR/pyinstaller_dist/$APP_NAME/." "$RELEASE_DIR/" mkdir -p "$RELEASE_DIR/doc_scripts" cp -a doc_scripts/deploy.sh "$RELEASE_DIR/doc_scripts/deploy.sh" cp -a doc_scripts/config.txt.example "$RELEASE_DIR/doc_scripts/config.txt.example" cp -a doc_scripts/PAM_AUTO_DEPLY_SKILL.md "$RELEASE_DIR/doc_scripts/PAM_AUTO_DEPLY_SKILL.md" chmod +x "$RELEASE_DIR/doc_scripts/deploy.sh" mkdir -p "$RELEASE_DIR/prompts" cp -a prompts/action_review.txt "$RELEASE_DIR/prompts/action_review.txt" cp -a packaging/README_packaged_agent.md "$RELEASE_DIR/README.md" cp -a packaging/mcp_client.example.json "$RELEASE_DIR/mcp_client.example.json" cp -a LICENSE "$RELEASE_DIR/LICENSE" cat > "$RELEASE_DIR/run.sh" <<'RUN_SCRIPT' #!/usr/bin/env bash set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$DIR" print_help() { cat <<'HELP_TEXT' PAM 部署 Agent 解压即用包 用法: ./run.sh <命令> [参数] 常用命令: chat 启动交互式对话框。推荐从这里开始使用。 analyze 只做自然语言理解和部署计划生成,不执行 action。 preview 根据配置预演 action 路由和关键参数。 run-global 执行全局阶段:token、版本、上传、发布、Node URL、下载任务。 run-deploy 执行完整部署流程:全局阶段 + 逐 IP 阶段。 resume 从 checkpoint 继续执行。 rollback 显式回滚失败 IP;不传 --ip 时使用当前失败 IP。 confirm 兼容旧 checkpoint 的人工确认命令,新流程通常不需要使用。 通用参数: --config <路径> 参数配置文件。支持 config.txt 风格和 JSON。打包内置示例: doc_scripts/config.txt.example --strategy <策略> 执行策略: fake 全部使用 fake runner,不访问真实环境。 script_only 全部 action 走脚本。 hybrid_node_mcp PAM_HOME 走脚本,PAM_NODE 走 MCP。 --checkpoint <路径> checkpoint JSON 路径。用于断点续跑和显式回滚。 示例:runtime/checkpoints/demo.json --target-ip 指定目标工作站 IP。可重复传入多次。 --mcp-config <路径> MCP client JSON 配置文件。通常配置 server_url 和独立鉴权信息; Agent 会从 server list_tools 自动发现 tools。hybrid_node_mcp 策略、 resume 或 rollback 需要执行 MCP action 时使用。 示例:mcp_client.example.json --confirm 非交互命令执行真实 action 前必须显式传入。 chat 模式会在会话中要求输入 run,并分别确认参数、目标范围和最终执行。 --analyze-actions 每个 action 完成后的 LLM/规则审核默认都会执行;该参数只控制 是否把详细审核结果写入 events。审核建议停止时流程会暂停, resume 会重试当前 action。 LLM 参数: --llm-base-url OpenAI-compatible LLM 服务地址,例如 https://example.com/v1 --llm-api-key LLM API Key。也可通过环境变量 PAM_LLM_API_KEY 提供。 --llm-model <模型名> LLM 模型名称。也可通过环境变量 PAM_LLM_MODEL 提供。 --llm-action-analysis-prompt-file <路径> 自定义 action 审核提示词文件。打包内置基线: prompts/action_review.txt LLM 环境变量: PAM_LLM_BASE_URL PAM_LLM_API_KEY PAM_LLM_MODEL PAM_LLM_ACTION_ANALYSIS_PROMPT_FILE 日志环境变量: PAM_AGENT_LOG_FILE 运行日志路径,默认 logs/pam_deploy_agent.log。 PAM_AGENT_LOG_LEVEL 日志级别,默认 INFO。排查 LLM/MCP 时可临时设为 DEBUG。 示例: ./run.sh chat --config doc_scripts/config.txt.example --strategy fake --checkpoint runtime/checkpoints/demo.json ./run.sh chat --config doc_scripts/config.txt.example --strategy hybrid_node_mcp --mcp-config mcp_client.example.json --checkpoint runtime/checkpoints/demo.json ./run.sh analyze --config doc_scripts/config.txt.example --text "请用 MCP 预演部署 HET PAM Node 版本 2.0.5,不要动环境" ./run.sh run-deploy --config doc_scripts/config.txt.example --strategy fake --checkpoint runtime/checkpoints/demo.json --confirm # 失败或审核阻断暂停后,修复外部环境并从当前 action 重试: ./run.sh resume --checkpoint runtime/checkpoints/demo.json --confirm # 需要回滚失败 IP 时显式执行: ./run.sh rollback --checkpoint runtime/checkpoints/demo.json --confirm ./run.sh resume --checkpoint runtime/checkpoints/demo.json --confirm 查看子命令原始参数: ./run.sh chat --help ./run.sh run-deploy --help 说明: 1. 本包已包含 Python 运行时和 Python 依赖,目标机器不需要安装 Python 包。 2. doc_scripts 只包含运行必需文件:deploy.sh、config.txt.example、PAM_AUTO_DEPLY_SKILL.md。 3. prompts/action_review.txt 是当前默认 action 审核提示词基线,可复制后自行修改。 4. mcp_client.example.json 是 MCP server URL + 独立鉴权配置示例,需要按真实 MCP server 修改。 5. action 失败或审核阻断后会暂停;修复后用 resume 从当前 action 重试,需要回滚时用 rollback 显式执行。 6. chat 会在执行前归一化并展示实际写入脚本配置的参数;script_only / hybrid_node_mcp 会先检查 ZIP_FILE_PATH 是否存在。 7. PARENT_VERSION_NUMBER 是云下载可选参数;空值不发送,非空时传给 parentVersionNumber。 8. chat 执行过程中会播报每个 action 的开始、完成或失败;普通问候不会触发 LLM/结构化分析。 9. chat 内可使用 params、events、rollback、list checkpoints、load checkpoint、load params、llm config、llm test、mcp config 等命令。 10. 日志默认写入 logs/pam_deploy_agent.log,并会脱敏 token、secret、api_key、Authorization 等字段。 11. checkpoint 会保存完整运行参数,请放在受控目录。 HELP_TEXT } if [[ $# -eq 0 || "${1:-}" == "-h" || "${1:-}" == "--help" || "${1:-}" == "help" ]]; then print_help exit 0 fi exec "$DIR/pam-deploy-agent" "$@" RUN_SCRIPT chmod +x "$RELEASE_DIR/run.sh" echo "==> 生成 tar.gz" tar -C "$DIST_DIR" -czf "$ARCHIVE_PATH" "$RELEASE_NAME" format_bytes() { local bytes="$1" python - "$bytes" <<'PY' import sys value = float(sys.argv[1]) units = ["B", "KB", "MB", "GB"] for unit in units: if value < 1024 or unit == units[-1]: print(f"{value:.1f} {unit}") break value /= 1024 PY } EXTRACTED_BYTES="$(du -sb "$RELEASE_DIR" | awk '{print $1}')" ARCHIVE_BYTES="$(du -sb "$ARCHIVE_PATH" | awk '{print $1}')" echo "==> 构建完成" echo "发布目录: $RELEASE_DIR" echo "压缩包: $ARCHIVE_PATH" echo "解压后大小: $(format_bytes "$EXTRACTED_BYTES")" echo "压缩包大小: $(format_bytes "$ARCHIVE_BYTES")"