#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=scripts/common.sh source "$ROOT_DIR/scripts/common.sh" SELECTED_INDEX=0 SELECTED_ENV="" SELECTED_BASE_URL="" ENV_STATUS_LINES="" UI_BOX_WIDTH=76 usage() { cat </dev/null 2>&1; then ENV_STATUS_LINES=" ${C_YELLOW}Docker 未安装或 docker 命令不可用${C_RESET}" return fi if ! docker ps >/dev/null 2>&1; then docker_error="$(docker ps 2>&1 >/dev/null || true)" ENV_STATUS_LINES=" ${C_YELLOW}Docker 未运行或当前用户无权限访问 Docker${C_RESET}" [[ -n "$docker_error" ]] && ENV_STATUS_LINES="${ENV_STATUS_LINES}\n ${C_DIM}${docker_error}${C_RESET}" return fi local env project rows total running stopped status_label for env in dev main release; do project="$(ctms_project_name "$env")" rows="$(docker ps -a \ --filter "label=com.docker.compose.project=$project" \ --format '{{.Names}}|{{.State}}|{{.Status}}' 2>/dev/null || true)" [[ -n "$rows" ]] || continue detected=1 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 )) if [[ "$running" -eq "$total" && "$total" -gt 0 ]]; then status_label="${CC_SUCCESS}${ICON_RUNNING} 运行中${C_RESET}" elif [[ "$running" -gt 0 ]]; then status_label="${CC_WARN}${ICON_RUNNING} 部分运行${C_RESET}" else status_label="${CC_DANGER}${ICON_STOPPED} 已停止${C_RESET}" fi output="${output} ${CC_INFO}${C_BOLD}${env}${C_RESET} ${CC_MUTED}(${project})${C_RESET} ${status_label} ${CC_MUTED}${running}/${total} 运行" [[ "$stopped" -gt 0 ]] && output="${output},${stopped} 已停止" output="${output}${C_RESET}\n" output="${output}$(printf '%s\n' "$rows" | awk -F'|' '{ printf " %-18s %-8s %s\n", $1, $2, $3 }')\n" done if [[ "$detected" -eq 0 ]]; then rows="$(docker ps -a \ --filter "name=ctms_" \ --format '{{.Names}}|{{.State}}|{{.Status}}' 2>/dev/null || true)" if [[ -n "$rows" ]]; then 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 }')" ENV_STATUS_LINES=" ${C_YELLOW}检测到 CTMS 命名容器,但无法从 Compose 标签确认环境${C_RESET}\n" ENV_STATUS_LINES="${ENV_STATUS_LINES} ${running}/${total} 运行\n" ENV_STATUS_LINES="${ENV_STATUS_LINES}$(printf '%s\n' "$rows" | awk -F'|' '{ printf " - %-18s %-8s %s\n", $1, $2, $3 }')" else ENV_STATUS_LINES=" ${C_DIM}未检测到 CTMS Compose 容器(ctms_dev / ctms_main / ctms_release)${C_RESET}" fi else ENV_STATUS_LINES="${output%\\n}" fi } select_menu() { local title="$1" shift local options=("$@") local selected=0 local key="" [[ "${#options[@]}" -gt 0 ]] || ctms_fail "菜单没有可选项" trap ctms_restore_cursor EXIT INT TERM printf '\033[?25l\033[2J' while true; do printf '\033[H' banner ctms_box_top "${C_BLUE}${C_BOLD}" "$title" "$UI_BOX_WIDTH" local i for i in "${!options[@]}"; do if [[ "$i" -eq "$selected" ]]; then ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET} ${options[$i]}" "${CC_INFO}${C_BOLD}${ICON_BULLET} ${options[$i]}${C_RESET}" else ctms_box_line "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" "${ICON_BULLET_OFF} ${options[$i]}" "${CC_MUTED}${ICON_BULLET_OFF} ${options[$i]}${C_RESET}" fi done ctms_box_bottom "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH" printf '\033[J' key="$(ctms_read_key)" case "$key" in "[A") selected=$(( (selected - 1 + ${#options[@]}) % ${#options[@]} )) ;; "[B") selected=$(( (selected + 1) % ${#options[@]} )) ;; "") ctms_restore_cursor; SELECTED_INDEX="$selected"; return ;; q|Q) ctms_restore_cursor; exit 0 ;; esac done } pick_env() { select_menu "选择环境" \ "dev 本地开发环境 Vite 热加载 / 默认密钥" \ "main 内网测试环境 预发布验证 / 生产模式" \ "release 生产环境 HTTPS / RSA 密钥" case "$SELECTED_INDEX" in 0) SELECTED_ENV="dev" ;; 1) SELECTED_ENV="main" ;; 2) SELECTED_ENV="release" ;; esac } collect_install_options() { local env="$1" SELECTED_BASE_URL="" if [[ "$env" == "release" ]]; then SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址,例如 https://ctms.example.com")" [[ -n "$SELECTED_BASE_URL" ]] || ctms_fail "release 环境必须提供健康检查地址" elif [[ "$env" == "main" ]]; then SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址" "http://127.0.0.1:8888")" fi } run_script() { local script="$1" shift [[ -x "$ROOT_DIR/scripts/$script" ]] || ctms_fail "脚本不可执行: $ROOT_DIR/scripts/$script" "$ROOT_DIR/scripts/$script" "$@" } run_install_like_script() { local script="$1" local env="$2" if [[ -n "$SELECTED_BASE_URL" ]]; then run_script "$script" "$env" --base-url "$SELECTED_BASE_URL" else run_script "$script" "$env" fi } # 操作完成后暂停,等用户确认再返回菜单 pause_return() { printf '\n %s%s%s 按 Enter 返回主菜单…' "${CC_INFO}${C_BOLD}" "$ICON_PROMPT" "${C_RESET}" read -r _ || true } interactive_main() { select_menu "选择操作" \ "安装部署 配置环境、构建容器、迁移数据库、健康检查" \ "滚动更新 重建服务、执行迁移、复用安装健康检查" \ "安全卸载 停止并移除容器,保留 .env 与数据文件" \ "资源状态 查看 CPU、内存、网络与磁盘 I/O 快照" \ "使用帮助 查看入口和底层脚本说明" \ "退出控制台" case "$SELECTED_INDEX" in 0) pick_env collect_install_options "$SELECTED_ENV" run_install_like_script "install.sh" "$SELECTED_ENV" pause_return ;; 1) pick_env collect_install_options "$SELECTED_ENV" run_install_like_script "update.sh" "$SELECTED_ENV" pause_return ;; 2) pick_env run_script "uninstall.sh" "$SELECTED_ENV" pause_return ;; 3) pick_env run_script "status.sh" "$SELECTED_ENV" pause_return ;; 4) usage pause_return ;; 5) return 1 ;; esac return 0 } main() { if [[ $# -gt 0 ]]; then usage ctms_fail "install.sh 不接受参数,请直接执行 ./install.sh" fi require_interactive_terminal while true; do build_env_status interactive_main || break done exit 0 } main "$@"