2df8d60172
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
302 lines
11 KiB
Bash
Executable File
302 lines
11 KiB
Bash
Executable File
#!/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 <<EOF
|
||
|
||
${CC_PRIMARY}${C_BOLD}═══════════════════════════════════════════════════════════════${C_RESET}
|
||
${CC_PRIMARY}${C_BOLD} CTMS 部署控制台 - 使用帮助${C_RESET}
|
||
${CC_PRIMARY}${C_BOLD}═══════════════════════════════════════════════════════════════${C_RESET}
|
||
|
||
${C_BOLD}【主入口】${C_RESET} ${CC_INFO}./install.sh${C_RESET} ${CC_MUTED}↑/↓ 选择 Enter 确认 Q 退出${C_RESET}
|
||
|
||
${C_BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}
|
||
|
||
${C_BOLD}【底层脚本(CI/CD)】${C_RESET}
|
||
|
||
${CC_INFO}bash scripts/install.sh <env> [--base-url <url>] [--yes] [--skip-build] [--skip-migrate]${C_RESET}
|
||
${CC_INFO}bash scripts/update.sh <env> [--repo-url <url>] [--branch <name>] [--yes] [--force]${C_RESET}
|
||
${CC_INFO}bash scripts/uninstall.sh <env>${C_RESET}
|
||
${CC_INFO}bash scripts/status.sh <env>${C_RESET}
|
||
|
||
${CC_MUTED}env: dev(开发)| main(内网测试)| release(生产)${C_RESET}
|
||
${CC_MUTED}update.sh 认证:export GIT_USERNAME=xxx GIT_PASSWORD=yyy${C_RESET}
|
||
|
||
${CC_MUTED}查看脚本帮助:${C_RESET} bash scripts/<script>.sh --help
|
||
|
||
${CC_PRIMARY}${C_BOLD}═══════════════════════════════════════════════════════════════${C_RESET}
|
||
|
||
EOF
|
||
}
|
||
|
||
require_interactive_terminal() {
|
||
[[ -t 0 && -t 1 ]] || ctms_fail "install.sh 需要在交互式终端中运行"
|
||
}
|
||
|
||
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" "↑/↓ 选择 Enter 确认 Q 退出" "${CC_MUTED}↑/↓ 选择 Enter 确认 Q 退出${C_RESET}"
|
||
ctms_box_bottom "${C_BLUE}${C_BOLD}" "$UI_BOX_WIDTH"
|
||
printf '\n'
|
||
render_status_panel
|
||
}
|
||
|
||
render_status_panel() {
|
||
ctms_box_top "${C_MAGENTA}${C_BOLD}" "环境状态" "$UI_BOX_WIDTH"
|
||
printf '%b\n' "$ENV_STATUS_LINES" | while IFS= read -r line; do
|
||
ctms_box_line "${C_MAGENTA}${C_BOLD}" "$UI_BOX_WIDTH" "$line" "$line"
|
||
done
|
||
ctms_box_bottom "${C_MAGENTA}${C_BOLD}" "$UI_BOX_WIDTH"
|
||
printf '\n'
|
||
}
|
||
|
||
build_env_status() {
|
||
local output=""
|
||
local detected=0
|
||
local docker_error=""
|
||
|
||
if ! command -v docker >/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"
|
||
local default_index="$2"
|
||
shift 2
|
||
local options=("$@")
|
||
local selected="${default_index:-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; printf '\033[2J\033[H'; exit 0 ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# 读取 .ctms-env 中的 KEY = VALUE 配置
|
||
ctms_config_get() {
|
||
local key="$1" file="$ROOT_DIR/.ctms-env"
|
||
[[ -f "$file" ]] || return 1
|
||
awk -F' *= *' -v k="$key" 'NF==2 && $1==k { print $2; exit }' "$file"
|
||
}
|
||
|
||
pick_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 -a opts=(
|
||
"dev 本地开发环境 Vite 热加载 / 默认密钥"
|
||
"main 内网测试环境 预发布验证 / 生产模式"
|
||
"release 生产环境 HTTPS / RSA 密钥"
|
||
)
|
||
if [[ -n "$env" ]]; then
|
||
case "$default_index" in
|
||
0) opts[0]="dev(默认) 本地开发环境 Vite 热加载 / 默认密钥" ;;
|
||
1) opts[1]="main(默认) 内网测试环境 预发布验证 / 生产模式" ;;
|
||
2) opts[2]="release(默认) 生产环境 HTTPS / RSA 密钥" ;;
|
||
esac
|
||
fi
|
||
local menu_title="选择环境"
|
||
[[ -n "$env" ]] && menu_title="选择环境 (已识别: ${env})"
|
||
select_menu "$menu_title" "$default_index" "${opts[@]}"
|
||
case "$SELECTED_INDEX" in
|
||
0) SELECTED_ENV="dev" ;;
|
||
1) SELECTED_ENV="main" ;;
|
||
2) SELECTED_ENV="release" ;;
|
||
esac
|
||
# 手动选择与配置不一致时更新 .ctms-env
|
||
if [[ -n "$env" && "$SELECTED_ENV" != "$env" ]]; then
|
||
local file="$ROOT_DIR/.ctms-env"
|
||
sed -i.bak "s/^env *=.*/env = $SELECTED_ENV/" "$file" && rm -f "${file}.bak"
|
||
ctms_info "已将 .ctms-env 中的环境更新为: $SELECTED_ENV"
|
||
fi
|
||
}
|
||
|
||
collect_install_options() {
|
||
local env="$1"
|
||
SELECTED_BASE_URL="$(ctms_config_get "base-url" || true)"
|
||
|
||
if [[ "$env" == "release" && -z "$SELECTED_BASE_URL" ]]; then
|
||
SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址,例如 https://ctms.example.com")"
|
||
[[ -n "$SELECTED_BASE_URL" ]] || ctms_fail "release 环境必须提供健康检查地址"
|
||
elif [[ "$env" == "main" && -z "$SELECTED_BASE_URL" ]]; 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
|
||
printf '\033[2J\033[H'
|
||
exit 0
|
||
}
|
||
|
||
main "$@"
|