sync_kiro_config.sh 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env bash
  2. # =============================================================================
  3. # FranklinWH Toolbox - Kiro CLI 配置单向同步脚本(A → B)
  4. # =============================================================================
  5. # 作用:把「版本库配置源」单向推送到「线上部署目标」。
  6. #
  7. # A 端(版本库源):deploy/kiro/
  8. # B 端(部署目标):${KIRO_HOME}/.kiro 默认 /var/www/fwh-toolbox/server/.kiro
  9. #
  10. # 同步内容(一律 A → B,不回写):
  11. # * steering/ 覆盖同步(rsync --delete,B 端该目录与 A 端一致)
  12. # * agents/ 覆盖同步(rsync --delete)
  13. # * 自定义 skill 扫描 A 端 skills/ 下的目录,逐个覆盖同步到 B 端
  14. # (每个 skill 目录内部 --delete,不影响 B 端其它 skill)
  15. # * skills.sh 生态 skill 读取 skills-sh.list,在 B 端用 `npx skills add` 安装
  16. # (要装新的:先在 skills-sh.list 加一行,再跑本脚本)
  17. #
  18. # 不同步:settings/(kiro-cli 自动生成)。
  19. # 安全:顶层 skills/ 不做 --delete,命令安装的 skill 目录不会被误删。
  20. #
  21. # 用法:
  22. # ./sync_kiro_config.sh # 执行同步
  23. # KIRO_HOME=/path ./sync_kiro_config.sh # 指定 B 端 HOME
  24. # KIRO_DRY_RUN=y ./sync_kiro_config.sh # 预演,只打印动作不落盘
  25. # KIRO_SKILLS_INSTALL=n ./sync_kiro_config.sh # 跳过 skills.sh 命令安装,只同步文件
  26. #
  27. # 环境变量:
  28. # KIRO_HOME B 端 HOME(决定 .kiro 位置) (默认 /var/www/fwh-toolbox/server)
  29. # KIRO_RUN_AS_USER B 端属主 / 运行 kiro-cli 的用户 (默认当前用户)
  30. # A_DIR A 端目录 (默认 <repo>/deploy/kiro)
  31. # KIRO_SKILLS_LIST skills.sh 安装清单文件 (默认 ${A_DIR}/skills-sh.list)
  32. # KIRO_AGENT skills CLI 目标 agent (默认 kiro-cli)
  33. # KIRO_SKILLS_INSTALL 是否命令安装 skills.sh skill (默认 y)
  34. # KIRO_DRY_RUN 只打印不落盘 (y/n) (默认 n)
  35. # =============================================================================
  36. set -euo pipefail
  37. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  38. DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" # deploy/
  39. KIRO_HOME="${KIRO_HOME:-/var/www/fwh-toolbox/server}"
  40. KIRO_RUN_AS_USER="${KIRO_RUN_AS_USER:-$(id -un)}"
  41. A_DIR="${A_DIR:-${DEPLOY_DIR}/kiro}"
  42. B_DIR="${KIRO_HOME%/}/.kiro"
  43. KIRO_SKILLS_LIST="${KIRO_SKILLS_LIST:-${A_DIR}/skills-sh.list}"
  44. KIRO_AGENT="${KIRO_AGENT:-kiro-cli}"
  45. KIRO_SKILLS_INSTALL="${KIRO_SKILLS_INSTALL:-y}"
  46. KIRO_DRY_RUN="${KIRO_DRY_RUN:-n}"
  47. # A→B 覆盖同步的配置子目录
  48. SYNC_SUBDIRS=(steering agents)
  49. c_reset='\033[0m'; c_blue='\033[1;34m'; c_green='\033[1;32m'; c_yellow='\033[1;33m'; c_red='\033[1;31m'
  50. info() { echo -e "${c_blue}[INFO]${c_reset} $*"; }
  51. ok() { echo -e "${c_green}[ OK ]${c_reset} $*"; }
  52. warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; }
  53. err() { echo -e "${c_red}[FAIL]${c_reset} $*" >&2; }
  54. has() { command -v "$1" >/dev/null 2>&1; }
  55. is_dry() { [[ "${KIRO_DRY_RUN}" == "y" ]]; }
  56. # 执行文件系统命令(dry-run 只打印)
  57. run_fs() {
  58. if is_dry; then echo -e "${c_yellow}[DRY]${c_reset} $*"; return 0; fi
  59. bash -lc "$*"
  60. }
  61. # 以 B 端运行用户身份执行(HOME=KIRO_HOME),用于 npx skills add
  62. as_target_user() {
  63. local cmd="$*"
  64. if is_dry; then
  65. echo -e "${c_yellow}[DRY]${c_reset} (as ${KIRO_RUN_AS_USER}, HOME=${KIRO_HOME}) ${cmd}"
  66. return 0
  67. fi
  68. if [[ "${KIRO_RUN_AS_USER}" == "$(id -un)" ]]; then
  69. HOME="${KIRO_HOME}" bash -lc "${cmd}"
  70. else
  71. sudo -u "${KIRO_RUN_AS_USER}" -H env HOME="${KIRO_HOME}" bash -lc "${cmd}"
  72. fi
  73. }
  74. # 扫描 A 端 skills/ 下实际存在的自定义 skill 目录名
  75. custom_skill_names() {
  76. local d
  77. [[ -d "${A_DIR}/skills" ]] || return 0
  78. for d in "${A_DIR}/skills"/*/; do
  79. [[ -d "${d}" ]] && basename "${d}"
  80. done
  81. }
  82. # 读取 skills.sh 清单:去注释/空行,输出每行 "<owner>/<repo>@<skill>"
  83. read_skills_list() {
  84. [[ -f "${KIRO_SKILLS_LIST}" ]] || return 0
  85. sed -E 's/#.*$//; s/[[:space:]]+$//; s/^[[:space:]]+//' "${KIRO_SKILLS_LIST}" \
  86. | grep -vE '^[[:space:]]*$'
  87. }
  88. # rsync A→B,一个子路径(带 --delete,作用范围仅该子目录)
  89. rsync_dir() {
  90. local rel="$1" # 如 steering、skills/fwh-toolbox-rules
  91. local src="${A_DIR}/${rel}"
  92. local dst="${B_DIR}/${rel}"
  93. [[ -d "${src}" ]] || { warn "跳过(源不存在):${src}"; return 0; }
  94. run_fs "sudo mkdir -p '${dst}'"
  95. if has rsync; then
  96. run_fs "sudo rsync -a --delete '${src}/' '${dst}/'"
  97. else
  98. run_fs "sudo rm -rf '${dst}'/* && sudo cp -rf '${src}/.' '${dst}/'"
  99. fi
  100. }
  101. # ---------------------------------------------------------------------------
  102. # 1. 同步 steering / agents(覆盖)
  103. # ---------------------------------------------------------------------------
  104. sync_config_dirs() {
  105. local sub
  106. for sub in "${SYNC_SUBDIRS[@]}"; do
  107. info "同步 ${sub}/ (A→B 覆盖)"
  108. rsync_dir "${sub}"
  109. done
  110. }
  111. # ---------------------------------------------------------------------------
  112. # 2. 同步自定义 skill(逐个目录覆盖,扫描 A 端)
  113. # ---------------------------------------------------------------------------
  114. sync_custom_skills() {
  115. local name count=0
  116. while IFS= read -r name; do
  117. [[ -z "${name}" ]] && continue
  118. info "同步自定义 skill:skills/${name}/ (A→B 覆盖)"
  119. rsync_dir "skills/${name}"
  120. count=$((count+1))
  121. done < <(custom_skill_names)
  122. [[ ${count} -eq 0 ]] && info "A 端无自定义 skill" || ok "自定义 skill 同步完成(共 ${count} 个)"
  123. }
  124. # ---------------------------------------------------------------------------
  125. # 3. 命令安装 skills.sh 生态 skill(读清单,在 B 端安装)
  126. # ---------------------------------------------------------------------------
  127. install_skills_sh() {
  128. if [[ "${KIRO_SKILLS_INSTALL}" != "y" ]]; then
  129. info "跳过 skills.sh 命令安装(KIRO_SKILLS_INSTALL=${KIRO_SKILLS_INSTALL})"
  130. return 0
  131. fi
  132. if [[ ! -f "${KIRO_SKILLS_LIST}" ]]; then
  133. warn "未找到清单文件:${KIRO_SKILLS_LIST},跳过命令安装"
  134. return 0
  135. fi
  136. if ! has npx; then
  137. warn "无 npx,跳过 skills.sh 命令安装"
  138. return 0
  139. fi
  140. info "从清单安装 skills.sh skill:${KIRO_SKILLS_LIST}"
  141. local entry repo skill count=0
  142. while IFS= read -r entry; do
  143. [[ -z "${entry}" ]] && continue
  144. if [[ "${entry}" != *"@"* ]]; then
  145. warn "清单行格式不符(应为 owner/repo@skill),跳过:${entry}"
  146. continue
  147. fi
  148. repo="${entry%@*}"
  149. skill="${entry##*@}"
  150. info "安装 skill:${repo} --skill ${skill} → agent ${KIRO_AGENT}"
  151. as_target_user "cd '${KIRO_HOME}' && npx --yes skills add '${repo}' --skill '${skill}' --agent '${KIRO_AGENT}' --yes" \
  152. || warn "skill 安装失败(可稍后重试):${entry}"
  153. count=$((count+1))
  154. done < <(read_skills_list)
  155. ok "skills.sh skill 命令安装完成(共 ${count} 个)"
  156. }
  157. # ---------------------------------------------------------------------------
  158. # main
  159. # ---------------------------------------------------------------------------
  160. main() {
  161. info "== Kiro 配置单向同步(A → B)=="
  162. info "A 端(版本库) :${A_DIR}"
  163. info "B 端(部署目标):${B_DIR}"
  164. info "自定义 skill :$(custom_skill_names | tr '\n' ' ')"
  165. info "skills.sh 清单 :${KIRO_SKILLS_LIST}(命令安装=${KIRO_SKILLS_INSTALL})"
  166. info "dry-run=${KIRO_DRY_RUN}"
  167. if [[ ! -d "${A_DIR}" ]]; then err "A 端不存在:${A_DIR}"; exit 1; fi
  168. sync_config_dirs
  169. sync_custom_skills
  170. install_skills_sh
  171. # 属主归还给 B 端运行用户
  172. run_fs "sudo chown -R '${KIRO_RUN_AS_USER}' '${B_DIR}'"
  173. ok "同步结束"
  174. }
  175. main "$@"