deploy_kiro_cli.sh 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. #!/usr/bin/env bash
  2. # =============================================================================
  3. # FranklinWH Toolbox - Kiro CLI 配置部署脚本
  4. # =============================================================================
  5. # 作用:把版本库内的 kiro-cli 配置(agents / steering / skills)部署到
  6. # 后端运行用户的 HOME 下的 .kiro 目录,供后端以 ACP 子进程方式拉起的 kiro-cli
  7. # 加载使用。
  8. #
  9. # 注意:settings/ 由 kiro-cli 自动生成,不纳入部署(不覆盖目标机自身的设置)。
  10. #
  11. # 配置来源(版本库内,权威配置):
  12. # deploy/kiro/
  13. # ├── agents/ Agent 定义(*.json)
  14. # ├── steering/ Steering 规则(*.md)
  15. # └── skills/ 仅自定义 skill(skills.sh 生态的通过命令安装,不入此目录)
  16. #
  17. # 部署目标(与 install_kiro_cli.sh 的 KIRO_HOME 保持一致):
  18. # ${KIRO_HOME}/.kiro 默认 /var/www/fwh-toolbox/server/.kiro
  19. # ├── agents/ ← 拷贝同步
  20. # ├── steering/ ← 拷贝同步
  21. # ├── settings/ ← 不部署(kiro-cli 自动生成)
  22. # └── skills/
  23. # ├── <skills.sh 生态> ← 用 `npx skills add` 命令安装(可控、可更新)
  24. # └── <自定义 skill> ← 直接拷贝(skills.sh 上不存在,无法命令安装)
  25. #
  26. # Skill 分类原则:
  27. # * 来自 skills.sh 生态的 skill → 命令安装。清单外置在 ${KIRO_CONFIG_SRC}/skills-sh.list
  28. # (每行 "<owner>/<repo>@<skill>",# 注释/空行忽略;文件内含安装命令备注)。
  29. # 新增/调整只需改该清单文件,无需改本脚本。
  30. # * 非 skills.sh 的自定义 skill → 直接拷贝。**不再硬编码清单**,脚本扫描
  31. # ${KIRO_CONFIG_SRC}/skills/ 下实际存在的目录逐个拷贝。新增只需把目录放进去。
  32. #
  33. # 前置条件:
  34. # * 目标用户存在,且 kiro-cli 已安装(见 install_kiro_cli.sh)。
  35. # * 命令安装 skill 需要 Node.js / npx(skills CLI 通过 npx 调用)。
  36. # * rsync(可选,用于同步;缺失时回退到 cp)。
  37. #
  38. # 环境变量:
  39. # KIRO_RUN_AS_USER 部署并运行 kiro-cli 的系统用户 (默认当前用户)
  40. # KIRO_HOME 目标用户 HOME(决定 .kiro 位置) (默认 /var/www/fwh-toolbox/server)
  41. # KIRO_CONFIG_SRC 配置来源目录 (默认 <repo>/deploy/kiro)
  42. # KIRO_SKILLS_LIST skills.sh 安装清单文件 (默认 ${KIRO_CONFIG_SRC}/skills-sh.list)
  43. # KIRO_AGENT skills CLI 的目标 agent (默认 kiro-cli)
  44. # KIRO_SKILLS_INSTALL 是否命令安装 skills.sh skill (y/n) (默认 y)
  45. # KIRO_WIKI_DOCS 产品文档知识库目录(替换 __WIKI_DOCS__) (默认当前 Wiki docs 路径)
  46. # KIRO_OFFICECLI_BIN officecli 可执行文件(替换 __OFFICECLI_BIN__)(默认当前 officecli 路径)
  47. # KIRO_DRY_RUN 只打印将执行的动作,不落盘 (y/n) (默认 n)
  48. # =============================================================================
  49. set -euo pipefail
  50. # ---------------------------------------------------------------------------
  51. # 路径与默认值
  52. # ---------------------------------------------------------------------------
  53. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  54. DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" # deploy/
  55. PROJECT_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" # 仓库根目录
  56. KIRO_RUN_AS_USER="${KIRO_RUN_AS_USER:-$(id -un)}"
  57. KIRO_HOME="${KIRO_HOME:-/var/www/fwh-toolbox/server}"
  58. KIRO_CONFIG_SRC="${KIRO_CONFIG_SRC:-${DEPLOY_DIR}/kiro}"
  59. KIRO_AGENT="${KIRO_AGENT:-kiro-cli}"
  60. KIRO_SKILLS_INSTALL="${KIRO_SKILLS_INSTALL:-y}"
  61. KIRO_DRY_RUN="${KIRO_DRY_RUN:-n}"
  62. # agent.json 占位符替换值(部署时可覆盖;默认取当前环境路径)
  63. # __KIRO_HOME__ → ${KIRO_HOME}(fwh-pyrun、templates 的父路径)
  64. # __WIKI_DOCS__ → ${KIRO_WIKI_DOCS}(产品文档知识库目录)
  65. # __OFFICECLI_BIN__ → ${KIRO_OFFICECLI_BIN}(officecli 可执行文件)
  66. KIRO_WIKI_DOCS="${KIRO_WIKI_DOCS:-/home/jerry-remote/web/FranklinWH-Wiki/client/docs}"
  67. KIRO_OFFICECLI_BIN="${KIRO_OFFICECLI_BIN:-/home/jerry-remote/.local/bin/officecli}"
  68. # 文件系统操作是否使用 sudo(默认 y,保持原行为)。当目标目录当前用户可写时可设 n。
  69. KIRO_USE_SUDO="${KIRO_USE_SUDO:-y}"
  70. if [[ "${KIRO_USE_SUDO}" == "y" ]]; then SUDO="sudo "; else SUDO=""; fi
  71. KIRO_DEST="${KIRO_HOME%/}/.kiro"
  72. # ---------------------------------------------------------------------------
  73. # Skill 来源
  74. # ---------------------------------------------------------------------------
  75. # skills.sh 生态:从清单文件读取(每行 "<owner>/<repo>@<skill>",# 注释/空行忽略)
  76. KIRO_SKILLS_LIST="${KIRO_SKILLS_LIST:-${KIRO_CONFIG_SRC}/skills-sh.list}"
  77. # 自定义 skill:不再硬编码,直接扫描 ${KIRO_CONFIG_SRC}/skills/ 下实际存在的目录进行拷贝
  78. # 拷贝同步的配置子目录(settings 由 kiro-cli 自动生成,不部署)
  79. CONFIG_SUBDIRS=(agents steering)
  80. # ---------------------------------------------------------------------------
  81. # 日志
  82. # ---------------------------------------------------------------------------
  83. c_reset='\033[0m'; c_blue='\033[1;34m'; c_green='\033[1;32m'; c_yellow='\033[1;33m'; c_red='\033[1;31m'
  84. info() { echo -e "${c_blue}[INFO]${c_reset} $*"; }
  85. ok() { echo -e "${c_green}[ OK ]${c_reset} $*"; }
  86. warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
  87. err() { echo -e "${c_red}[FAIL]${c_reset} $*" >&2; }
  88. has() { command -v "$1" >/dev/null 2>&1; }
  89. is_dry() { [[ "${KIRO_DRY_RUN}" == "y" ]]; }
  90. # 解析目标用户 HOME(仅用于日志/一致性校验;实际目标由 KIRO_HOME 决定)
  91. resolve_user_home() {
  92. getent passwd "${KIRO_RUN_AS_USER}" | cut -d: -f6
  93. }
  94. # 以目标用户身份执行命令(设置 HOME=KIRO_HOME,保证 .kiro 落到目标位置)
  95. # dry-run 时只打印。
  96. as_target_user() {
  97. local cmd="$*"
  98. if is_dry; then
  99. echo -e "${c_yellow}[DRY]${c_reset} (as ${KIRO_RUN_AS_USER}, HOME=${KIRO_HOME}) ${cmd}"
  100. return 0
  101. fi
  102. if [[ "${KIRO_RUN_AS_USER}" == "$(id -un)" ]]; then
  103. HOME="${KIRO_HOME}" bash -lc "${cmd}"
  104. else
  105. sudo -u "${KIRO_RUN_AS_USER}" -H env HOME="${KIRO_HOME}" bash -lc "${cmd}"
  106. fi
  107. }
  108. # 以目标用户身份 / 或 sudo 执行文件系统操作(run_fs "<shell>")
  109. run_fs() {
  110. local cmd="$*"
  111. if is_dry; then
  112. echo -e "${c_yellow}[DRY]${c_reset} ${cmd}"
  113. return 0
  114. fi
  115. bash -lc "${cmd}"
  116. }
  117. # 检查并(尽力)安装沙箱运行 Python 所需依赖:bwrap、python3。
  118. # 自动安装需要 sudo 与受支持的包管理器(apt/dnf/yum/zypper/pacman)。
  119. # 失败时给出告警但不中断部署(与 npx 缺失的处理一致)。
  120. ensure_sandbox_deps() {
  121. local missing=()
  122. has bwrap || missing+=("bwrap")
  123. has python3 || missing+=("python3")
  124. if [[ ${#missing[@]} -eq 0 ]]; then
  125. ok "沙箱依赖已就绪:bwrap、python3"
  126. return 0
  127. fi
  128. warn "缺少沙箱依赖:${missing[*]},尝试自动安装 ..."
  129. # 将逻辑包名映射到各发行版的实际包名
  130. local pkgs_apt=() pkgs_rpm=() pkgs_pacman=()
  131. local m
  132. for m in "${missing[@]}"; do
  133. case "$m" in
  134. bwrap) pkgs_apt+=("bubblewrap"); pkgs_rpm+=("bubblewrap"); pkgs_pacman+=("bubblewrap") ;;
  135. python3) pkgs_apt+=("python3"); pkgs_rpm+=("python3"); pkgs_pacman+=("python") ;;
  136. esac
  137. done
  138. if is_dry; then
  139. echo -e "${c_yellow}[DRY]${c_reset} 将安装:${missing[*]}"
  140. return 0
  141. fi
  142. if has apt-get; then
  143. run_fs "sudo apt-get update -y && sudo apt-get install -y ${pkgs_apt[*]}" || true
  144. elif has dnf; then
  145. run_fs "sudo dnf install -y ${pkgs_rpm[*]}" || true
  146. elif has yum; then
  147. run_fs "sudo yum install -y ${pkgs_rpm[*]}" || true
  148. elif has zypper; then
  149. run_fs "sudo zypper install -y ${pkgs_rpm[*]}" || true
  150. elif has pacman; then
  151. run_fs "sudo pacman -Sy --noconfirm ${pkgs_pacman[*]}" || true
  152. else
  153. err "未识别的包管理器,无法自动安装:${missing[*]}"
  154. err "请手动安装后重试(bwrap 对应包名通常为 bubblewrap)。"
  155. return 1
  156. fi
  157. # 复检
  158. local still=()
  159. has bwrap || still+=("bwrap")
  160. has python3 || still+=("python3")
  161. if [[ ${#still[@]} -gt 0 ]]; then
  162. err "以下依赖安装后仍不可用:${still[*]}。Python3 沙箱能力将不可用。"
  163. return 1
  164. fi
  165. ok "沙箱依赖安装完成:${missing[*]}"
  166. }
  167. # ---------------------------------------------------------------------------
  168. # 0. 前置检查
  169. # ---------------------------------------------------------------------------
  170. ensure_prereqs() {
  171. if [[ ! -d "${KIRO_CONFIG_SRC}" ]]; then
  172. err "配置来源不存在:${KIRO_CONFIG_SRC}"
  173. return 1
  174. fi
  175. if ! getent passwd "${KIRO_RUN_AS_USER}" >/dev/null; then
  176. err "系统用户不存在:${KIRO_RUN_AS_USER}"
  177. return 1
  178. fi
  179. if [[ "${KIRO_SKILLS_INSTALL}" == "y" ]] && ! has npx; then
  180. warn "未找到 npx(Node.js)。skills.sh skill 的命令安装将被跳过。"
  181. warn "如需命令安装,请先安装 Node.js(提供 npx),或设 KIRO_SKILLS_INSTALL=n。"
  182. fi
  183. # Python3 沙箱依赖(bwrap + python3),供 fwh-toolbox-agent 的 fwh-pyrun 使用
  184. ensure_sandbox_deps || warn "沙箱依赖未完全就绪,fwh-pyrun 可能无法运行。"
  185. }
  186. # ---------------------------------------------------------------------------
  187. # 1. 准备目标目录(属主为目标用户)
  188. # ---------------------------------------------------------------------------
  189. prepare_dest() {
  190. info "准备目标目录:${KIRO_DEST}(属主 ${KIRO_RUN_AS_USER})"
  191. run_fs "${SUDO}mkdir -p '${KIRO_DEST}/skills'"
  192. run_fs "${SUDO}chown -R '${KIRO_RUN_AS_USER}' '${KIRO_HOME%/}/.kiro'"
  193. }
  194. # ---------------------------------------------------------------------------
  195. # 2. 同步配置子目录(agents / settings / steering)
  196. # ---------------------------------------------------------------------------
  197. sync_config_dirs() {
  198. local sub src
  199. for sub in "${CONFIG_SUBDIRS[@]}"; do
  200. src="${KIRO_CONFIG_SRC}/${sub}"
  201. if [[ ! -d "${src}" ]]; then
  202. warn "跳过(源不存在):${src}"
  203. continue
  204. fi
  205. info "同步配置:${sub}/ → ${KIRO_DEST}/${sub}/"
  206. run_fs "${SUDO}mkdir -p '${KIRO_DEST}/${sub}'"
  207. if has rsync; then
  208. run_fs "${SUDO}rsync -a --delete '${src}/' '${KIRO_DEST}/${sub}/'"
  209. else
  210. run_fs "${SUDO}rm -rf '${KIRO_DEST}/${sub}'/* && ${SUDO}cp -rf '${src}/.' '${KIRO_DEST}/${sub}/'"
  211. fi
  212. done
  213. run_fs "${SUDO}chown -R '${KIRO_RUN_AS_USER}' '${KIRO_HOME%/}/.kiro'"
  214. ok "配置目录同步完成"
  215. }
  216. # ---------------------------------------------------------------------------
  217. # 3. 命令安装 skills.sh 生态的 skill(从清单文件读取)
  218. # ---------------------------------------------------------------------------
  219. # 解析清单文件:去掉 # 注释与空行,输出每行 "<owner>/<repo>@<skill>"
  220. read_skills_list() {
  221. [[ -f "${KIRO_SKILLS_LIST}" ]] || return 0
  222. sed -E 's/#.*$//; s/[[:space:]]+$//; s/^[[:space:]]+//' "${KIRO_SKILLS_LIST}" \
  223. | grep -vE '^[[:space:]]*$'
  224. }
  225. install_skills_sh() {
  226. if [[ "${KIRO_SKILLS_INSTALL}" != "y" ]]; then
  227. info "跳过 skills.sh skill 命令安装(KIRO_SKILLS_INSTALL=${KIRO_SKILLS_INSTALL})"
  228. return 0
  229. fi
  230. if [[ ! -f "${KIRO_SKILLS_LIST}" ]]; then
  231. warn "未找到 skills.sh 清单文件:${KIRO_SKILLS_LIST},跳过命令安装"
  232. return 0
  233. fi
  234. if ! has npx; then
  235. warn "无 npx,跳过 skills.sh skill 命令安装"
  236. return 0
  237. fi
  238. info "从清单安装 skills.sh skill:${KIRO_SKILLS_LIST}"
  239. local entry repo skill ok_count=0 fail_count=0 failed_list=""
  240. while IFS= read -r entry; do
  241. [[ -z "${entry}" ]] && continue
  242. if [[ "${entry}" != *"@"* ]]; then
  243. warn "清单行格式不符(应为 owner/repo@skill),跳过:${entry}"
  244. continue
  245. fi
  246. repo="${entry%@*}"
  247. skill="${entry##*@}"
  248. if install_one_skill "${repo}" "${skill}"; then
  249. ok_count=$((ok_count+1))
  250. else
  251. fail_count=$((fail_count+1))
  252. failed_list+=" ${entry}"
  253. fi
  254. done < <(read_skills_list)
  255. if [[ ${fail_count} -eq 0 ]]; then
  256. ok "skills.sh skill 安装完成(成功 ${ok_count} 个)"
  257. else
  258. warn "skills.sh skill 安装:成功 ${ok_count} 个,失败 ${fail_count} 个 →${failed_list}"
  259. warn "可重跑本脚本重试,或手动执行:npx skills add <owner>/<repo> --skill <skill> --agent ${KIRO_AGENT} --yes"
  260. fi
  261. }
  262. # 安装单个 skill 并校验是否真正落地;失败自动重试一次。
  263. # 参数:$1=<owner>/<repo> $2=<skill>
  264. # 校验方式:安装后检查目标 skills 目录下是否出现该 skill 目录(含 SKILL.md)。
  265. install_one_skill() {
  266. local repo="$1" skill="$2"
  267. local dest_skill="${KIRO_DEST}/skills/${skill}"
  268. local attempt
  269. for attempt in 1 2; do
  270. info "命令安装 skill:${repo} --skill ${skill} → agent ${KIRO_AGENT}(第 ${attempt} 次)"
  271. if is_dry; then
  272. as_target_user "cd '${KIRO_HOME}' && npx --yes skills add '${repo}' --skill '${skill}' --agent '${KIRO_AGENT}' --yes"
  273. return 0
  274. fi
  275. # 逐条独立执行;stdin 接 /dev/null,避免 TUI 在管道/循环中争用输入导致后续条目静默失败
  276. as_target_user "cd '${KIRO_HOME}' && npx --yes skills add '${repo}' --skill '${skill}' --agent '${KIRO_AGENT}' --yes < /dev/null" || true
  277. # 安装后校验:目标目录出现且含 SKILL.md 视为成功
  278. if [[ -f "${dest_skill}/SKILL.md" ]]; then
  279. ok "skill 就绪:${skill}"
  280. return 0
  281. fi
  282. warn "skill 未检测到落地:${skill}(第 ${attempt} 次),准备重试 ..."
  283. sleep 1
  284. done
  285. err "skill 安装失败:${repo}@${skill}"
  286. return 1
  287. }
  288. # ---------------------------------------------------------------------------
  289. # 4. 拷贝自定义 skill(扫描 ${KIRO_CONFIG_SRC}/skills/ 实际存在的目录)
  290. # ---------------------------------------------------------------------------
  291. copy_custom_skills() {
  292. local skills_src="${KIRO_CONFIG_SRC}/skills"
  293. if [[ ! -d "${skills_src}" ]]; then
  294. info "无自定义 skill 目录(${skills_src}),跳过拷贝"
  295. return 0
  296. fi
  297. local dir name dest count=0
  298. for dir in "${skills_src}"/*/; do
  299. [[ -d "${dir}" ]] || continue
  300. name="$(basename "${dir}")"
  301. dest="${KIRO_DEST}/skills/${name}"
  302. info "拷贝自定义 skill:${name} → ${dest}"
  303. if has rsync; then
  304. run_fs "${SUDO}rsync -a --delete '${skills_src}/${name}/' '${dest}/'"
  305. else
  306. run_fs "${SUDO}rm -rf '${dest}' && ${SUDO}mkdir -p '${dest}' && ${SUDO}cp -rf '${skills_src}/${name}/.' '${dest}/'"
  307. fi
  308. count=$((count+1))
  309. done
  310. run_fs "${SUDO}chown -R '${KIRO_RUN_AS_USER}' '${KIRO_DEST}/skills'"
  311. ok "自定义 skill 拷贝完成(共 ${count} 个)"
  312. }
  313. # ---------------------------------------------------------------------------
  314. # 渲染占位符(部署后按目标环境替换 agents 与 skills 中的路径)
  315. # ---------------------------------------------------------------------------
  316. # 版本库中的 agents/*.json 与 skills/**/*.md 用占位符表示环境相关路径,
  317. # 部署到目标后在此统一替换为实值:
  318. # __KIRO_HOME__ → ${KIRO_HOME} (fwh-pyrun、templates 的父路径)
  319. # __WIKI_DOCS__ → ${KIRO_WIKI_DOCS} (产品文档知识库目录)
  320. # __OFFICECLI_BIN__ → ${KIRO_OFFICECLI_BIN}(officecli 可执行文件)
  321. # 注意:需在 agents / skills 全部拷贝到目标之后调用。
  322. render_placeholders() {
  323. info "替换占位符:__KIRO_HOME__=${KIRO_HOME%/} __WIKI_DOCS__=${KIRO_WIKI_DOCS} __OFFICECLI_BIN__=${KIRO_OFFICECLI_BIN}"
  324. # 用 # 作为 sed 分隔符(路径含 /);当前替换值不含 #
  325. local sed_expr="s#__KIRO_HOME__#${KIRO_HOME%/}#g; s#__WIKI_DOCS__#${KIRO_WIKI_DOCS%/}#g; s#__OFFICECLI_BIN__#${KIRO_OFFICECLI_BIN}#g"
  326. # agents/*.json
  327. if [[ -d "${KIRO_DEST}/agents" ]]; then
  328. run_fs "${SUDO}find '${KIRO_DEST}/agents' -maxdepth 1 -type f -name '*.json' -exec sed -i -E '${sed_expr}' {} +"
  329. fi
  330. # skills/**/*.md
  331. if [[ -d "${KIRO_DEST}/skills" ]]; then
  332. run_fs "${SUDO}find '${KIRO_DEST}/skills' -type f -name '*.md' -exec sed -i -E '${sed_expr}' {} +"
  333. fi
  334. run_fs "${SUDO}chown -R '${KIRO_RUN_AS_USER}' '${KIRO_HOME%/}/.kiro'"
  335. ok "占位符替换完成(agents + skills)"
  336. }
  337. # ---------------------------------------------------------------------------
  338. # 4.2 部署 PPT 公司模板(随部署路径走)
  339. # ---------------------------------------------------------------------------
  340. # 源:${KIRO_CONFIG_SRC}/templates/*
  341. # 目标:${KIRO_HOME}/templates/(agent.json 中以 __KIRO_HOME__/templates 引用)
  342. install_templates() {
  343. local src="${KIRO_CONFIG_SRC}/templates"
  344. local dest="${KIRO_HOME%/}/templates"
  345. if [[ ! -d "${src}" ]]; then
  346. info "无模板目录(${src}),跳过模板部署"
  347. return 0
  348. fi
  349. info "部署模板:${src}/ → ${dest}/"
  350. run_fs "${SUDO}mkdir -p '${dest}'"
  351. if has rsync; then
  352. run_fs "${SUDO}rsync -a '${src}/' '${dest}/'"
  353. else
  354. run_fs "${SUDO}cp -rf '${src}/.' '${dest}/'"
  355. fi
  356. run_fs "${SUDO}chown -R '${KIRO_RUN_AS_USER}' '${dest}'"
  357. ok "模板部署完成:${dest}"
  358. }
  359. # ---------------------------------------------------------------------------
  360. # 4.5 部署 fwh-pyrun 包装脚本(Python3 沙箱执行器,供 fwh-toolbox-agent 调用)
  361. # ---------------------------------------------------------------------------
  362. # 源:${KIRO_CONFIG_SRC}/bin/fwh-pyrun
  363. # 目标:${KIRO_HOME}/bin/fwh-pyrun(须与 agent.json 中 allowedCommands 的路径一致)
  364. # 同时确保沙箱目录 /tmp/fwh-toolbox-agent 存在(属主为运行用户)。
  365. install_pyrun() {
  366. local src="${KIRO_CONFIG_SRC}/bin/fwh-pyrun"
  367. local dest_dir="${KIRO_HOME%/}/bin"
  368. local dest="${dest_dir}/fwh-pyrun"
  369. if [[ ! -f "${src}" ]]; then
  370. warn "未找到 fwh-pyrun 源(${src}),跳过部署(Python3 沙箱能力不可用)"
  371. return 0
  372. fi
  373. info "部署 fwh-pyrun:${src} → ${dest}"
  374. run_fs "${SUDO}mkdir -p '${dest_dir}'"
  375. run_fs "${SUDO}cp -f '${src}' '${dest}'"
  376. run_fs "${SUDO}chmod 0755 '${dest}'"
  377. run_fs "${SUDO}chown '${KIRO_RUN_AS_USER}' '${dest}'"
  378. # 沙箱可写目录(唯一持久写入点),属主为运行用户
  379. run_fs "${SUDO}mkdir -p /tmp/fwh-toolbox-agent && ${SUDO}chown '${KIRO_RUN_AS_USER}' /tmp/fwh-toolbox-agent"
  380. ok "fwh-pyrun 部署完成:${dest}"
  381. }
  382. # ---------------------------------------------------------------------------
  383. # 5. 校验
  384. # ---------------------------------------------------------------------------
  385. verify() {
  386. info "校验部署结果 ..."
  387. if is_dry; then
  388. info "(dry-run) 跳过实际校验"
  389. return 0
  390. fi
  391. local sub
  392. for sub in "${CONFIG_SUBDIRS[@]}" skills; do
  393. if [[ -d "${KIRO_DEST}/${sub}" ]]; then
  394. ok "存在:${KIRO_DEST}/${sub}"
  395. else
  396. warn "缺失:${KIRO_DEST}/${sub}"
  397. fi
  398. done
  399. local name dir
  400. for dir in "${KIRO_CONFIG_SRC}/skills"/*/; do
  401. [[ -d "${dir}" ]] || continue
  402. name="$(basename "${dir}")"
  403. [[ -f "${KIRO_DEST}/skills/${name}/SKILL.md" ]] \
  404. && ok "自定义 skill 就绪:${name}" \
  405. || warn "自定义 skill 缺 SKILL.md:${name}"
  406. done
  407. if has npx && [[ "${KIRO_SKILLS_INSTALL}" == "y" ]]; then
  408. info "已安装 skill(项目级,HOME=${KIRO_HOME}):"
  409. as_target_user "cd '${KIRO_HOME}' && npx --yes skills list 2>/dev/null" || true
  410. fi
  411. # fwh-pyrun 沙箱执行器校验
  412. local pyrun="${KIRO_HOME%/}/bin/fwh-pyrun"
  413. if [[ -x "${pyrun}" ]]; then
  414. ok "fwh-pyrun 就绪:${pyrun}"
  415. else
  416. warn "fwh-pyrun 缺失或不可执行:${pyrun}(Python3 沙箱能力不可用)"
  417. fi
  418. has bwrap && ok "bwrap 可用:$(command -v bwrap)" || warn "bwrap 不可用(Python3 沙箱能力不可用)"
  419. has python3 && ok "python3 可用:$(command -v python3)" || warn "python3 不可用(Python3 沙箱能力不可用)"
  420. # 模板校验
  421. if [[ -d "${KIRO_HOME%/}/templates" ]]; then
  422. ok "模板目录就绪:${KIRO_HOME%/}/templates"
  423. else
  424. warn "模板目录缺失:${KIRO_HOME%/}/templates"
  425. fi
  426. # 占位符残留校验(替换后不应再出现 __XXX__)
  427. if grep -rlE '__(KIRO_HOME|WIKI_DOCS|OFFICECLI_BIN)__' "${KIRO_DEST}/agents" "${KIRO_DEST}/skills" >/dev/null 2>&1; then
  428. warn "agents/skills 中仍存在未替换的占位符,请检查 render_placeholders"
  429. else
  430. ok "占位符已全部替换(agents + skills)"
  431. fi
  432. }
  433. # ---------------------------------------------------------------------------
  434. # main
  435. # ---------------------------------------------------------------------------
  436. main() {
  437. info "== 部署 Kiro CLI 配置 =="
  438. info "运行用户 = ${KIRO_RUN_AS_USER}(passwd HOME=$(resolve_user_home))"
  439. info "配置来源 = ${KIRO_CONFIG_SRC}"
  440. info "部署目标 = ${KIRO_DEST}"
  441. info "目标 agent = ${KIRO_AGENT}"
  442. info "命令安装 = ${KIRO_SKILLS_INSTALL} dry-run = ${KIRO_DRY_RUN}"
  443. if [[ "$(resolve_user_home)" != "${KIRO_HOME%/}" ]]; then
  444. warn "KIRO_HOME(${KIRO_HOME}) 与该用户 passwd 家目录($(resolve_user_home)) 不一致;"
  445. warn "请确认 config.env 的 KIRO_HOME 与后端拉起 kiro-cli 时的 HOME 一致。"
  446. fi
  447. ensure_prereqs
  448. prepare_dest
  449. sync_config_dirs
  450. install_skills_sh
  451. copy_custom_skills
  452. install_templates
  453. install_pyrun
  454. render_placeholders
  455. verify
  456. echo
  457. echo -e "${c_green}Kiro CLI 配置部署结束${c_reset}"
  458. cat <<EOF
  459. 目标目录 : ${KIRO_DEST}
  460. 已同步 : ${CONFIG_SUBDIRS[*]}
  461. 命令安装源 : ${KIRO_SKILLS_LIST}
  462. 拷贝源 : ${KIRO_CONFIG_SRC}/skills/(扫描实际目录)
  463. 模板目录 : ${KIRO_HOME%/}/templates(公司 PPT 模板等)
  464. 沙箱执行器 : ${KIRO_HOME%/}/bin/fwh-pyrun(bwrap 隔离 Python3)
  465. 占位符替换 : __KIRO_HOME__=${KIRO_HOME%/} __WIKI_DOCS__=${KIRO_WIKI_DOCS} __OFFICECLI_BIN__=${KIRO_OFFICECLI_BIN}
  466. EOF
  467. }
  468. main "$@"