滚动更新新增 Git 访问方式选择(HTTPS/SSH)及工作区变更预检

- install.sh:
  - 滚动更新流程新增「选择 Git 访问方式」步骤(HTTPS 登录 / SSH 访问)
  - SSH 方式支持指定私钥文件路径或粘贴私钥内容(临时文件用后即删)
  - 仓库地址输入回填当前 remote(同协议时)作为默认值
  - 选环境后立即检测工作区变更,提供暂存/放弃/取消处理
  - 更新结束后按 HEAD 是否变化决定自动恢复暂存或提示手动恢复
- scripts/update.sh:
  - 新增 --ssh-key 参数,SSH 协议下通过 GIT_SSH_COMMAND -i 指定私钥
  - 新增 --skip-dirty-check 参数,供 install.sh 预检后透传
- scripts/common.sh:
  - ctms_ask_line 提示语改输出到 stderr,避免被命令替换吞掉

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cheng Zhou
2026-06-24 09:26:54 +08:00
parent 2df8d60172
commit 24d5b7445f
5 changed files with 388 additions and 1237 deletions
+4 -2
View File
@@ -485,10 +485,12 @@ ctms_ask_line() {
local default_value="${2:-}"
local value=""
# 提示语输出到 stderr:调用方多用 $(ctms_ask_line ...) 捕获返回值,
# 若提示语走 stdout 会被一并吞掉,导致终端只剩光标、提示语还混进返回值。
if [[ -n "$default_value" ]]; then
printf '\n%s%s%s %s %s[%s]%s: ' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" "$prompt" "${CC_MUTED}" "$default_value" "${C_RESET}"
printf '\n%s%s%s %s %s[%s]%s: ' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" "$prompt" "${CC_MUTED}" "$default_value" "${C_RESET}" >&2
else
printf '\n%s%s%s %s: ' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" "$prompt"
printf '\n%s%s%s %s: ' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" "$prompt" >&2
fi
IFS= read -r value || value=""
printf '%s' "${value:-$default_value}"
+63 -6
View File
@@ -14,13 +14,18 @@ ${C_BOLD}环境:${C_RESET}
dev | main | release
${C_BOLD}说明:${C_RESET}
使用 docker compose stats --no-stream 获取资源快照,并输出整理后的状态面板。
交互终端下动态刷新容器资源面板(按 q 或 Enter 返回);
非交互终端或带 --once 时只输出一次快照。
${C_BOLD}选项:${C_RESET}
--once 仅输出一次快照,不进入动态刷新
EOF
}
STATS_OUTPUT=""
STATS_ERROR=""
STATUS_BOX_WIDTH=127
STATUS_REFRESH_INTERVAL=2
collect_compose_stats() {
local env="$1"
@@ -76,7 +81,10 @@ render_stats_panel() {
fi
ctms_box_top "${CC_ACCENT}${C_BOLD}" "容器资源" "$STATUS_BOX_WIDTH"
ctms_box_line "${CC_ACCENT}${C_BOLD}" "$STATUS_BOX_WIDTH" "$(printf '%-22s %-9s %-27s %-8s %-21s %-21s %-6s' "容器" "CPU" "内存使用 / 限制" "内存%" "网络 I/O" "磁盘 I/O" "PIDs")" "${CC_MUTED}$(printf '%-22s %-9s %-27s %-8s %-21s %-21s %-6s' "容器" "CPU" "内存使用 / 限制" "内存%" "网络 I/O" "磁盘 I/O" "PIDs")${C_RESET}"
# 表头含中文(双宽),用 ctms_pad_right 按显示宽度对齐,列宽与下方数据行的 %-Ns 一致
local header
header="$(ctms_pad_right "容器" 22) $(ctms_pad_right "CPU" 9) $(ctms_pad_right "内存使用 / 限制" 27) $(ctms_pad_right "内存%" 8) $(ctms_pad_right "网络 I/O" 21) $(ctms_pad_right "磁盘 I/O" 21) $(ctms_pad_right "PIDs" 6)"
ctms_box_line "${CC_ACCENT}${C_BOLD}" "$STATUS_BOX_WIDTH" "$header" "${CC_MUTED}${header}${C_RESET}"
printf '%s├%s┤%s\n' "${CC_ACCENT}${C_BOLD}" "$(ctms_repeat_char '─' "$(( STATUS_BOX_WIDTH - 2 ))")" "${C_RESET}"
printf '%s\n' "$STATS_OUTPUT" | while IFS='|' read -r name cpu mem mem_percent net_io block_io pids; do
@@ -89,21 +97,70 @@ render_stats_panel() {
printf ' %s数据来源%s docker compose stats --all --no-stream\n\n' "${CC_MUTED}" "${C_RESET}"
}
run_dynamic() {
local env="$1" project="$2"
local key=""
# 隐藏光标,退出时(含 Ctrl-C)务必恢复并清屏,避免终端残留
printf '\033[?25l'
trap 'ctms_restore_cursor; printf "\033[2J\033[H"; exit 0' INT TERM
while true; do
collect_compose_stats "$env" || true
# 定位左上角重绘,再清除光标之后的残留行(容器数变化时不留尾巴)
printf '\033[H'
render_stats_panel "$env" "$project" || true
printf ' %s每 %ss 自动刷新%s %s按 q 或 Enter 返回菜单%s\n' \
"${CC_MUTED}" "$STATUS_REFRESH_INTERVAL" "${C_RESET}" \
"${CC_INFO}${C_BOLD}" "${C_RESET}"
printf '\033[J'
# 刷新间隔内监听退出键;超时(无输入)则继续刷新
key=""
if IFS= read -rsn1 -t "$STATUS_REFRESH_INTERVAL" key; then
case "$key" in
q|Q|'') break ;; # q/Q 或 Enter 退出
esac
fi
done
trap - INT TERM
ctms_restore_cursor
printf '\033[2J\033[H'
}
main() {
if [[ $# -ne 1 ]]; then
local once=0
local -a positional=()
while [[ $# -gt 0 ]]; do
case "$1" in
--once) once=1; shift ;;
*) positional+=("$1"); shift ;;
esac
done
if [[ "${#positional[@]}" -ne 1 ]]; then
usage
exit 1
fi
local env="$1"
local env="${positional[0]}"
local project=""
ctms_require_env "$env"
project="$(ctms_project_name "$env")"
ctms_require_docker_compose
cd "$CTMS_ROOT_DIR"
collect_compose_stats "$env" || true
render_stats_panel "$env" "$project"
# 非交互终端或显式 --once:只输出一次快照
if [[ "$once" -eq 1 || ! -t 1 || ! -t 0 ]]; then
collect_compose_stats "$env" || true
render_stats_panel "$env" "$project"
return
fi
printf '\033[2J'
run_dynamic "$env" "$project"
}
main "$@"
+24 -1
View File
@@ -9,8 +9,10 @@ source "$SCRIPT_DIR/common.sh"
TARGET_ENV=""
REPO_URL=""
GIT_BRANCH=""
SSH_KEY=""
ASSUME_YES=0
SKIP_BACKUP=0
SKIP_DIRTY_CHECK=0
FORCE_PULL=0
INSTALL_ARGS=()
@@ -55,8 +57,11 @@ ${C_BOLD}选项:${C_RESET}
${C_WHITE}--repo-url <url>${C_RESET} Git 仓库地址(HTTPS 或 SSH
不指定时会提示输入或使用当前远程地址
${C_WHITE}--branch <name>${C_RESET} 指定拉取的分支(默认为当前分支)
${C_WHITE}--ssh-key <path>${C_RESET} SSH 私钥文件路径(仅 SSH 仓库地址时生效)
不指定时使用本机默认 SSH 配置(~/.ssh/
${C_WHITE}--yes${C_RESET} 跳过所有交互确认,适合 CI/CD 自动化执行
${C_WHITE}--skip-backup${C_RESET} 跳过更新前自动备份 tag(不推荐)
${C_WHITE}--skip-dirty-check${C_RESET} 跳过工作目录变更检测(由 install.sh 预检后透传)
${C_WHITE}--force${C_RESET} 强制覆盖本地更改(git reset --hard
${C_WHITE}-h, --help${C_RESET} 显示此帮助信息
@@ -121,8 +126,14 @@ parse_args() {
GIT_BRANCH="$2"
shift 2
;;
--ssh-key)
[[ $# -ge 2 ]] || ctms_fail "--ssh-key 需要提供私钥文件路径"
SSH_KEY="$2"
shift 2
;;
--yes) ASSUME_YES=1; shift ;;
--skip-backup) SKIP_BACKUP=1; shift ;;
--skip-dirty-check) SKIP_DIRTY_CHECK=1; shift ;;
--force) FORCE_PULL=1; shift ;;
-h|--help) usage; exit 0 ;;
--skip-build|--skip-migrate|--verbose|--base-url)
@@ -200,6 +211,10 @@ verify_project_identity() {
# ── 工作目录状态检查 ─────────────────────────
check_working_directory() {
if [[ "$SKIP_DIRTY_CHECK" -eq 1 ]]; then
return 0
fi
ctms_step "检查工作目录状态"
if [[ "$FORCE_PULL" -eq 1 ]]; then
@@ -363,7 +378,14 @@ pick_remote_ref() {
setup_git_credentials() {
# SSH 协议不需要额外配置
if [[ "$REPO_URL" =~ ^(git@|ssh://) ]]; then
ctms_info "使用 SSH 协议,跳过凭证配置"
if [[ -n "$SSH_KEY" ]]; then
[[ -f "$SSH_KEY" ]] || ctms_fail "指定的 SSH 私钥文件不存在: $SSH_KEY"
# 通过 GIT_SSH_COMMAND 指定私钥;IdentitiesOnly 确保只用这把钥匙
export GIT_SSH_COMMAND="ssh -i '$SSH_KEY' -o IdentitiesOnly=yes"
ctms_info "使用 SSH 协议,已指定私钥: $SSH_KEY"
else
ctms_info "使用 SSH 协议,使用本机默认 SSH 配置"
fi
return 0
fi
@@ -415,6 +437,7 @@ cleanup_git_credentials() {
rm -f "$GIT_ASKPASS" 2>/dev/null || true
fi
unset GIT_ASKPASS GIT_ASKPASS_USERNAME GIT_ASKPASS_PASSWORD
unset GIT_SSH_COMMAND
}
# ── 备份当前版本 ─────────────────────────────