Compare commits
9 Commits
e531ce75f9
...
b1c55abb10
| Author | SHA1 | Date | |
|---|---|---|---|
| b1c55abb10 | |||
| 24d5b7445f | |||
| 2df8d60172 | |||
| b83c900109 | |||
| 20b414e741 | |||
| a81647a4f8 | |||
| b72b5314a0 | |||
| b126a759f0 | |||
| 05cd74fd43 |
File diff suppressed because it is too large
Load Diff
+312
-20
@@ -8,7 +8,14 @@ source "$ROOT_DIR/scripts/common.sh"
|
||||
SELECTED_INDEX=0
|
||||
SELECTED_ENV=""
|
||||
SELECTED_BASE_URL=""
|
||||
SELECTED_REPO_URL=""
|
||||
SELECTED_SSH_KEY=""
|
||||
SELECTED_SSH_KEY_TEMP=0
|
||||
STASHED_BEFORE_UPDATE=0
|
||||
ENV_STATUS_LINES=""
|
||||
DEPLOYED_ENVS=""
|
||||
BREADCRUMB="请选择操作"
|
||||
OP_LABEL=""
|
||||
UI_BOX_WIDTH=76
|
||||
|
||||
usage() {
|
||||
@@ -46,7 +53,7 @@ require_interactive_terminal() {
|
||||
banner() {
|
||||
printf '\n'
|
||||
ctms_box_top "${C_BLUE}${C_BOLD}" "CTMS DEPLOYMENT CONSOLE" "$UI_BOX_WIDTH"
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET} 安装 · 更新 · 卸载 · 资源状态" "${CC_INFO}${C_BOLD}${ICON_BULLET}${C_RESET} 安装 · 更新 · 卸载 · 资源状态"
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET} ${BREADCRUMB}" "${CC_INFO}${C_BOLD}${ICON_BULLET}${C_RESET} ${CC_PRIMARY}${C_BOLD}${BREADCRUMB}${C_RESET}"
|
||||
ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "↑/↓ 选择 Enter 确认 Q 退出" "${CC_MUTED}↑/↓ 选择 Enter 确认 Q 退出${C_RESET}"
|
||||
ctms_box_bottom "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH"
|
||||
printf '\n'
|
||||
@@ -66,6 +73,7 @@ build_env_status() {
|
||||
local output=""
|
||||
local detected=0
|
||||
local docker_error=""
|
||||
DEPLOYED_ENVS=""
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
ENV_STATUS_LINES=" ${C_YELLOW}Docker 未安装或 docker 命令不可用${C_RESET}"
|
||||
@@ -88,6 +96,7 @@ build_env_status() {
|
||||
[[ -n "$rows" ]] || continue
|
||||
|
||||
detected=1
|
||||
DEPLOYED_ENVS="${DEPLOYED_ENVS}${env} "
|
||||
total="$(printf '%s\n' "$rows" | awk 'NF { count++ } END { print count + 0 }')"
|
||||
running="$(printf '%s\n' "$rows" | awk -F'|' '$2 == "running" { count++ } END { print count + 0 }')"
|
||||
stopped=$(( total - running ))
|
||||
@@ -126,9 +135,10 @@ build_env_status() {
|
||||
|
||||
select_menu() {
|
||||
local title="$1"
|
||||
shift
|
||||
local default_index="$2"
|
||||
shift 2
|
||||
local options=("$@")
|
||||
local selected=0
|
||||
local selected="${default_index:-0}"
|
||||
local key=""
|
||||
|
||||
[[ "${#options[@]}" -gt 0 ]] || ctms_fail "菜单没有可选项"
|
||||
@@ -169,17 +179,30 @@ ctms_config_get() {
|
||||
}
|
||||
|
||||
pick_env() {
|
||||
local env
|
||||
local env default_index=0
|
||||
env="$(ctms_config_get "env" || true)"
|
||||
case "$env" in
|
||||
main) default_index=1 ;;
|
||||
release) default_index=2 ;;
|
||||
esac
|
||||
# 按显示宽度对齐三列(中文双宽,半角空格凑不齐)
|
||||
local n0="dev" n1="main" n2="release"
|
||||
if [[ -n "$env" ]]; then
|
||||
ctms_require_env "$env"
|
||||
SELECTED_ENV="$env"
|
||||
return
|
||||
case "$default_index" in
|
||||
0) n0="dev(默认)" ;;
|
||||
1) n1="main(默认)" ;;
|
||||
2) n2="release(默认)" ;;
|
||||
esac
|
||||
fi
|
||||
select_menu "选择环境" \
|
||||
"dev 本地开发环境 Vite 热加载 / 默认密钥" \
|
||||
"main 内网测试环境 预发布验证 / 生产模式" \
|
||||
"release 生产环境 HTTPS / RSA 密钥"
|
||||
local -a opts=(
|
||||
"$(ctms_pad_right "$n0" 14)$(ctms_pad_right "本地开发环境" 16)Vite 热加载 / 默认密钥"
|
||||
"$(ctms_pad_right "$n1" 14)$(ctms_pad_right "内网测试环境" 16)预发布验证 / 生产模式"
|
||||
"$(ctms_pad_right "$n2" 14)$(ctms_pad_right "生产环境" 16)HTTPS / RSA 密钥"
|
||||
)
|
||||
local menu_title="选择环境"
|
||||
[[ -n "$env" ]] && menu_title="选择环境 (已识别: ${env})"
|
||||
BREADCRUMB="${OP_LABEL:-操作} › 选择环境"
|
||||
select_menu "$menu_title" "$default_index" "${opts[@]}"
|
||||
case "$SELECTED_INDEX" in
|
||||
0) SELECTED_ENV="dev" ;;
|
||||
1) SELECTED_ENV="main" ;;
|
||||
@@ -187,6 +210,70 @@ pick_env() {
|
||||
esac
|
||||
}
|
||||
|
||||
# 把环境持久化到 .ctms-env,仅在操作成功完成后调用。
|
||||
# 文件不存在则创建(首次安装场景);存在则就地更新 env 行。
|
||||
# 若本次收集到健康检查地址(SELECTED_BASE_URL),一并写入/更新 base-url。
|
||||
persist_env() {
|
||||
local env_to_save="$1"
|
||||
local file="$ROOT_DIR/.ctms-env"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
{
|
||||
printf 'env = %s\n' "$env_to_save"
|
||||
[[ -n "${SELECTED_BASE_URL:-}" ]] && printf 'base-url = %s\n' "$SELECTED_BASE_URL"
|
||||
} >"$file"
|
||||
ctms_info "已创建 .ctms-env,环境记录为: $env_to_save"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local current
|
||||
current="$(ctms_config_get "env" || true)"
|
||||
if [[ "$current" != "$env_to_save" ]]; then
|
||||
sed -i.bak "s/^env *=.*/env = $env_to_save/" "$file" && rm -f "${file}.bak"
|
||||
ctms_info "已将 .ctms-env 中的环境更新为: $env_to_save"
|
||||
fi
|
||||
|
||||
# 同步 base-url:存在则替换该行,不存在则追加
|
||||
if [[ -n "${SELECTED_BASE_URL:-}" ]]; then
|
||||
local current_url
|
||||
current_url="$(ctms_config_get "base-url" || true)"
|
||||
if [[ "$current_url" != "$SELECTED_BASE_URL" ]]; then
|
||||
if grep -q '^base-url *=' "$file"; then
|
||||
sed -i.bak "s|^base-url *=.*|base-url = $SELECTED_BASE_URL|" "$file" && rm -f "${file}.bak"
|
||||
else
|
||||
printf 'base-url = %s\n' "$SELECTED_BASE_URL" >>"$file"
|
||||
fi
|
||||
ctms_info "已将 .ctms-env 中的健康检查地址更新为: $SELECTED_BASE_URL"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 卸载专用:仅在“实际检测到容器”的环境中选择,避免选到空环境走完确认却什么都没卸。
|
||||
# 始终弹出选择菜单(哪怕只有一个环境),作为危险确认前的显式安全闸门。
|
||||
# 返回 0 表示已选定 SELECTED_ENV,返回 1 表示无可卸载环境(调用方应跳过卸载)。
|
||||
pick_deployed_env() {
|
||||
# DEPLOYED_ENVS 由 build_env_status 填充(空格分隔)
|
||||
local -a envs=()
|
||||
read -r -a envs <<<"${DEPLOYED_ENVS:-}"
|
||||
|
||||
if [[ "${#envs[@]}" -eq 0 ]]; then
|
||||
printf '\n %s%s%s %s未检测到任何已部署的 CTMS 环境,无需卸载。%s\n' \
|
||||
"${CC_WARN}${C_BOLD}" "$ICON_WARN" "${C_RESET}" "${CC_MUTED}" "${C_RESET}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 卸载是破坏性操作:即使只有一个环境也显式选择一次,作为危险确认前的安全闸门。
|
||||
local -a opts=()
|
||||
local e
|
||||
for e in "${envs[@]}"; do
|
||||
opts+=("$(ctms_pad_right "$e" 14)$(ctms_pad_right "Compose 项目" 16)$(ctms_project_name "$e")")
|
||||
done
|
||||
BREADCRUMB="${OP_LABEL:-安全卸载} › 选择环境"
|
||||
select_menu "选择要卸载的环境" "" "${opts[@]}"
|
||||
SELECTED_ENV="${envs[$SELECTED_INDEX]}"
|
||||
return 0
|
||||
}
|
||||
|
||||
collect_install_options() {
|
||||
local env="$1"
|
||||
SELECTED_BASE_URL="$(ctms_config_get "base-url" || true)"
|
||||
@@ -199,6 +286,166 @@ collect_install_options() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 读取当前仓库 origin 的远程地址(供输入仓库地址时作默认值回填)
|
||||
ctms_current_remote_url() {
|
||||
git -C "$ROOT_DIR" remote get-url origin 2>/dev/null || true
|
||||
}
|
||||
|
||||
# 滚动更新专用:选环境后立即检测工作区变更,脏则就地处理。
|
||||
# 返回 0 表示可继续,返回 1 表示用户取消本次更新(调用方应跳过后续步骤)。
|
||||
# 处理干净后,update.sh 会以 --skip-dirty-check 跳过重复检测。
|
||||
check_workspace_clean() {
|
||||
STASHED_BEFORE_UPDATE=0
|
||||
cd "$ROOT_DIR" || return 1
|
||||
command -v git >/dev/null 2>&1 || return 0 # 无 git 交给 update.sh 报错
|
||||
git rev-parse --git-dir >/dev/null 2>&1 || return 0
|
||||
|
||||
local dirty=0
|
||||
git diff-index --quiet HEAD -- 2>/dev/null || dirty=1
|
||||
[[ -n "$(git ls-files --others --exclude-standard 2>/dev/null)" ]] && dirty=1
|
||||
|
||||
if [[ "$dirty" -eq 0 ]]; then
|
||||
ctms_ok "工作目录干净"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '\n %s%s%s 检测到本地有未提交/未跟踪的更改,请选择处理方式:\n' \
|
||||
"${CC_WARN}${C_BOLD}" "$ICON_WARN" "${C_RESET}"
|
||||
printf ' %s1)%s 暂存更改(git stash)\n' "${C_BOLD}" "${C_RESET}"
|
||||
printf ' %s2)%s 放弃更改(git reset --hard)\n' "${C_BOLD}" "${C_RESET}"
|
||||
printf ' %s3)%s 取消更新\n' "${C_BOLD}" "${C_RESET}"
|
||||
printf ' 请输入选项 [1-3]: '
|
||||
|
||||
local choice
|
||||
read -r choice || choice=""
|
||||
case "$choice" in
|
||||
1)
|
||||
if git stash push -m "auto-stash before update $(date +%Y%m%d-%H%M%S)"; then
|
||||
ctms_ok "已暂存本地更改(可用 'git stash pop' 恢复)"
|
||||
STASHED_BEFORE_UPDATE=1
|
||||
else
|
||||
ctms_warn "暂存失败,已取消本次更新"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
printf ' 确认输入 %syes%s 放弃所有本地更改: ' "${C_BOLD}" "${C_RESET}"
|
||||
local confirm
|
||||
read -r confirm || confirm=""
|
||||
[[ "$confirm" == "yes" ]] || { ctms_info "已取消本次更新"; return 1; }
|
||||
git reset --hard HEAD || { ctms_warn "重置失败,已取消"; return 1; }
|
||||
git clean -fd || true
|
||||
ctms_ok "已放弃本地更改"
|
||||
;;
|
||||
*)
|
||||
ctms_info "已取消本次更新"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
# 滚动更新善后:根据更新前后 HEAD 是否变化,决定如何处理之前暂存的本地改动。
|
||||
# 仅当 check_workspace_clean 走了「暂存」分支(STASHED_BEFORE_UPDATE=1)时才动作。
|
||||
# · HEAD 未变(更新在拉取代码前就失败/中断)→ stash 基线仍匹配,自动 pop,无冲突风险;
|
||||
# · HEAD 已变(代码已更新,无论成功失败)→ 不自动 pop,避免与新代码冲突,仅提示手动恢复。
|
||||
restore_stash_after_update() {
|
||||
[[ "$STASHED_BEFORE_UPDATE" -eq 1 ]] || return 0
|
||||
|
||||
local head_before="$1" head_after
|
||||
head_after="$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true)"
|
||||
|
||||
if [[ -n "$head_before" && "$head_before" == "$head_after" ]]; then
|
||||
ctms_info "代码未发生变化,正在自动恢复你暂存的本地改动..."
|
||||
if git -C "$ROOT_DIR" stash pop >/dev/null 2>&1; then
|
||||
ctms_ok "已自动恢复暂存的本地改动"
|
||||
STASHED_BEFORE_UPDATE=0
|
||||
return 0
|
||||
fi
|
||||
ctms_warn "自动恢复失败(可能存在冲突),改动仍保留在 stash 栈中"
|
||||
fi
|
||||
|
||||
printf '\n %s%s%s 你更新前暂存的本地改动仍保留在 stash 栈中。\n' \
|
||||
"${CC_WARN}${C_BOLD}" "$ICON_WARN" "${C_RESET}"
|
||||
printf ' 恢复请执行: %sgit stash pop%s 查看请执行: %sgit stash list%s\n' \
|
||||
"${C_BOLD}" "${C_RESET}" "${C_BOLD}" "${C_RESET}"
|
||||
}
|
||||
|
||||
# SSH 访问专用:收集私钥来源,写入 SELECTED_SSH_KEY(私钥文件路径)。
|
||||
# 两种来源:① 输入已有私钥文件路径;② 留空则改为粘贴私钥内容,写入 chmod 600 临时文件
|
||||
# (SELECTED_SSH_KEY_TEMP=1 标记,run_update_script 调用后删除)。两者都留空则回退系统默认 SSH 配置。
|
||||
collect_ssh_key() {
|
||||
local key_path
|
||||
key_path="$(ctms_ask_line "请输入 SSH 私钥文件路径(留空则改为粘贴私钥内容)")"
|
||||
|
||||
# 展开起始的 ~ 为 $HOME
|
||||
[[ "$key_path" == "~"* ]] && key_path="${key_path/#\~/$HOME}"
|
||||
|
||||
if [[ -n "$key_path" ]]; then
|
||||
[[ -f "$key_path" ]] || ctms_fail "SSH 私钥文件不存在: $key_path"
|
||||
[[ -r "$key_path" ]] || ctms_fail "SSH 私钥文件不可读: $key_path"
|
||||
SELECTED_SSH_KEY="$key_path"
|
||||
ctms_info "将使用 SSH 私钥文件: $key_path"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 粘贴私钥内容:多行读取,以 Ctrl-D(EOF)结束
|
||||
printf '\n %s%s%s 请粘贴 SSH 私钥内容,粘贴完成后按 %sCtrl-D%s 结束(留空则使用系统默认 SSH 配置):\n' \
|
||||
"${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" "${C_BOLD}" "${C_RESET}"
|
||||
local content
|
||||
content="$(cat)"
|
||||
|
||||
if [[ -z "$content" ]]; then
|
||||
ctms_info "未提供私钥,将使用系统默认 SSH 配置(~/.ssh/)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmp_key
|
||||
tmp_key="$(mktemp "/tmp/ctms-ssh-key-$$.XXXXXX")" || ctms_fail "无法创建临时私钥文件"
|
||||
chmod 600 "$tmp_key"
|
||||
printf '%s\n' "$content" >"$tmp_key"
|
||||
SELECTED_SSH_KEY="$tmp_key"
|
||||
SELECTED_SSH_KEY_TEMP=1
|
||||
ctms_info "私钥内容已写入临时文件(用后即删)"
|
||||
}
|
||||
|
||||
# 滚动更新专用:选择 Git 访问方式(HTTPS 登录 / SSH 密钥),并手动输入对应格式的仓库地址。
|
||||
# 选好的地址写入 SELECTED_REPO_URL,由 run_update_script 透传给 update.sh --repo-url。
|
||||
# update.sh 会按 URL 协议自动区分认证:https:// 走账号/Token,git@/ssh:// 走 SSH 密钥。
|
||||
pick_access_method() {
|
||||
SELECTED_REPO_URL=""
|
||||
SELECTED_SSH_KEY=""
|
||||
SELECTED_SSH_KEY_TEMP=0
|
||||
local -a opts=(
|
||||
"$(ctms_pad_right "HTTPS 登录" 16)$(ctms_pad_right "账号 / Token" 18)https:// 仓库地址"
|
||||
"$(ctms_pad_right "SSH 访问" 16)$(ctms_pad_right "本机 SSH 密钥" 18)git@ 或 ssh:// 仓库地址"
|
||||
)
|
||||
BREADCRUMB="${OP_LABEL:-滚动更新} › ${SELECTED_ENV} › 选择访问方式"
|
||||
select_menu "选择 Git 访问方式" "" "${opts[@]}"
|
||||
|
||||
# 当前 remote 地址:与所选协议匹配时作为默认值回填,留空即沿用
|
||||
local current_url
|
||||
current_url="$(ctms_current_remote_url)"
|
||||
|
||||
local url default=""
|
||||
case "$SELECTED_INDEX" in
|
||||
0)
|
||||
[[ "$current_url" =~ ^https?:// ]] && default="$current_url"
|
||||
url="$(ctms_ask_line "请输入仓库地址" "$default")"
|
||||
[[ -n "$url" ]] || ctms_fail "仓库地址不能为空"
|
||||
[[ "$url" =~ ^https?:// ]] || ctms_fail "HTTPS 方式仓库地址需以 http:// 或 https:// 开头"
|
||||
;;
|
||||
1)
|
||||
[[ "$current_url" =~ ^(git@|ssh://) ]] && default="$current_url"
|
||||
url="$(ctms_ask_line "请输入仓库地址" "$default")"
|
||||
[[ -n "$url" ]] || ctms_fail "仓库地址不能为空"
|
||||
[[ "$url" =~ ^(git@|ssh://) ]] || ctms_fail "SSH 方式仓库地址需以 git@ 或 ssh:// 开头"
|
||||
collect_ssh_key
|
||||
;;
|
||||
esac
|
||||
SELECTED_REPO_URL="$url"
|
||||
}
|
||||
|
||||
run_script() {
|
||||
local script="$1"
|
||||
shift
|
||||
@@ -217,6 +464,25 @@ run_install_like_script() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 滚动更新专用:在 install 透传基础上追加 --repo-url / --ssh-key,决定 update.sh 使用的访问方式。
|
||||
run_update_script() {
|
||||
local env="$1"
|
||||
# 工作区脏检查已在 check_workspace_clean 处理过,让 update.sh 跳过重复检测
|
||||
local -a args=("$env" --skip-dirty-check)
|
||||
[[ -n "$SELECTED_REPO_URL" ]] && args+=(--repo-url "$SELECTED_REPO_URL")
|
||||
[[ -n "$SELECTED_SSH_KEY" ]] && args+=(--ssh-key "$SELECTED_SSH_KEY")
|
||||
[[ -n "$SELECTED_BASE_URL" ]] && args+=(--base-url "$SELECTED_BASE_URL")
|
||||
|
||||
local rc=0
|
||||
run_script "update.sh" "${args[@]}" || rc=$?
|
||||
|
||||
# 清理本次粘贴生成的临时私钥
|
||||
if [[ "$SELECTED_SSH_KEY_TEMP" -eq 1 && -n "$SELECTED_SSH_KEY" ]]; then
|
||||
rm -f "$SELECTED_SSH_KEY" 2>/dev/null || true
|
||||
fi
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
# 操作完成后暂停,等用户确认再返回菜单
|
||||
pause_return() {
|
||||
printf '\n %s%s%s 按 Enter 返回主菜单…' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}"
|
||||
@@ -224,7 +490,8 @@ pause_return() {
|
||||
}
|
||||
|
||||
interactive_main() {
|
||||
select_menu "选择操作" \
|
||||
BREADCRUMB="请选择操作"
|
||||
select_menu "选择操作" "" \
|
||||
"安装部署 配置环境、构建容器、迁移数据库、健康检查" \
|
||||
"滚动更新 重建服务、执行迁移、复用安装健康检查" \
|
||||
"安全卸载 停止并移除容器,保留 .env 与数据文件" \
|
||||
@@ -234,28 +501,53 @@ interactive_main() {
|
||||
|
||||
case "$SELECTED_INDEX" in
|
||||
0)
|
||||
OP_LABEL="安装部署"
|
||||
pick_env
|
||||
collect_install_options "$SELECTED_ENV"
|
||||
run_install_like_script "install.sh" "$SELECTED_ENV"
|
||||
BREADCRUMB="${OP_LABEL} › ${SELECTED_ENV} › 执行中"
|
||||
if run_install_like_script "install.sh" "$SELECTED_ENV"; then
|
||||
persist_env "$SELECTED_ENV"
|
||||
fi
|
||||
pause_return
|
||||
;;
|
||||
1)
|
||||
OP_LABEL="滚动更新"
|
||||
pick_env
|
||||
collect_install_options "$SELECTED_ENV"
|
||||
run_install_like_script "update.sh" "$SELECTED_ENV"
|
||||
if check_workspace_clean; then
|
||||
pick_access_method
|
||||
collect_install_options "$SELECTED_ENV"
|
||||
BREADCRUMB="${OP_LABEL} › ${SELECTED_ENV} › 执行中"
|
||||
# 记录更新前 HEAD,用于结束后判断代码是否被改动(决定 stash 能否安全自动恢复)
|
||||
local head_before_update
|
||||
head_before_update="$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true)"
|
||||
if run_update_script "$SELECTED_ENV"; then
|
||||
persist_env "$SELECTED_ENV"
|
||||
fi
|
||||
restore_stash_after_update "$head_before_update"
|
||||
fi
|
||||
pause_return
|
||||
;;
|
||||
2)
|
||||
pick_env
|
||||
run_script "uninstall.sh" "$SELECTED_ENV"
|
||||
OP_LABEL="安全卸载"
|
||||
if pick_deployed_env; then
|
||||
BREADCRUMB="${OP_LABEL} › ${SELECTED_ENV} › 执行中"
|
||||
run_script "uninstall.sh" "$SELECTED_ENV"
|
||||
fi
|
||||
pause_return
|
||||
;;
|
||||
3)
|
||||
pick_env
|
||||
run_script "status.sh" "$SELECTED_ENV"
|
||||
pause_return
|
||||
OP_LABEL="资源状态"
|
||||
# 只读快照,无需选环境:直接用 .ctms-env 识别的环境(无配置回退 dev)
|
||||
local env_for_status
|
||||
env_for_status="$(ctms_config_get "env" || true)"
|
||||
env_for_status="${env_for_status:-dev}"
|
||||
BREADCRUMB="${OP_LABEL} › ${env_for_status}"
|
||||
# status.sh 自身是交互循环(q/Enter 退出并清屏),正常退出直接回菜单;
|
||||
# 仅在出错时暂停,避免错误信息被菜单重绘一闪而过。
|
||||
run_script "status.sh" "$env_for_status" || pause_return
|
||||
;;
|
||||
4)
|
||||
BREADCRUMB="使用帮助"
|
||||
usage
|
||||
pause_return
|
||||
;;
|
||||
|
||||
+4
-2
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
# ── 备份当前版本 ─────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user