From c9b677e96a3ab1b87134cf2cba1309140d53f4ef Mon Sep 17 00:00:00 2001 From: Cheng Zhou Date: Thu, 28 May 2026 10:57:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E5=88=86=E6=94=AF=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=AE=89=E8=A3=85=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../guides/branch-environment-installation.md | 8 +- scripts/install-ctms.sh | 113 ++++++++---------- 2 files changed, 56 insertions(+), 65 deletions(-) diff --git a/docs/guides/branch-environment-installation.md b/docs/guides/branch-environment-installation.md index 6dfbcfce..2b60b5e8 100644 --- a/docs/guides/branch-environment-installation.md +++ b/docs/guides/branch-environment-installation.md @@ -113,7 +113,7 @@ bash scripts/install-ctms.sh release --base-url https://ctms.example.com ```text --base-url 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。 --yes 跳过交互确认,适合自动化执行。 ---skip-build 跳过 docker compose up -d --build。 +--skip-build 跳过镜像构建,仍会执行 docker compose up -d。 --skip-migrate 跳过 alembic upgrade head。 ``` @@ -121,10 +121,10 @@ bash scripts/install-ctms.sh release --base-url https://ctms.example.com ```text 1. 检查 docker、docker compose、openssl、curl。 -2. 创建或更新 .env;已有 .env 会先备份为 .env.bak.<时间戳>。 -3. main/release 缺少 JWT 或 RSA 私钥时自动生成,已有有效密钥保持不变。 +2. 检查 .env;已存在则保留不改,不存在则新建。 +3. 新建 main/release 的 .env 时自动生成 JWT 和 RSA 私钥。 4. main/release 先执行 backend-init。 -5. 执行 docker compose up -d --build。 +5. 启动容器;默认执行 docker compose up -d --build,--skip-build 时执行 docker compose up -d。 6. 执行 docker compose run --rm backend python -m alembic upgrade head。 7. 检查容器状态、后端环境、/health、/api/v1/auth/login-key。 ``` diff --git a/scripts/install-ctms.sh b/scripts/install-ctms.sh index 43e95562..3e672b83 100755 --- a/scripts/install-ctms.sh +++ b/scripts/install-ctms.sh @@ -152,7 +152,7 @@ ${C_BOLD}选项:${C_RESET} dev/main 默认 http://127.0.0.1:8888 release 必须通过此参数或交互输入提供 ${C_WHITE}--yes${C_RESET} 跳过所有交互确认,适合 CI/CD 自动化执行 - ${C_WHITE}--skip-build${C_RESET} 跳过镜像重新构建(docker compose up -d) + ${C_WHITE}--skip-build${C_RESET} 跳过镜像构建,仍会启动容器(docker compose up -d) ${C_WHITE}--skip-migrate${C_RESET} 跳过数据库迁移(alembic upgrade head) ${C_WHITE}--verbose${C_RESET} 展开所有命令的实时输出(默认折叠为 spinner) ${C_WHITE}-h, --help${C_RESET} 显示此帮助信息 @@ -279,13 +279,36 @@ read_env_value() { ' "$ENV_FILE" } -normalize_private_key_to_stdout() { - printf '%b' "$1" -} +resolve_effective_config() { + local desired_project_name="$1" + local desired_runtime_env="$2" + local desired_login_key_id="$3" -private_key_is_valid() { - [[ -n "$1" ]] || return 1 - normalize_private_key_to_stdout "$1" | openssl rsa -check -noout >/dev/null 2>&1 + EFFECTIVE_PROJECT_NAME="$desired_project_name" + EFFECTIVE_RUNTIME_ENV="$desired_runtime_env" + EFFECTIVE_LOGIN_KEY_ID="$desired_login_key_id" + + [[ -f "$ENV_FILE" ]] || return 0 + + local env_project_name env_runtime_env env_login_key_id + env_project_name="$(read_env_value COMPOSE_PROJECT_NAME || true)" + env_runtime_env="$(read_env_value ENV || true)" + env_login_key_id="$(read_env_value LOGIN_RSA_KEY_ID || true)" + + [[ -n "$env_project_name" ]] || fail ".env 缺少 COMPOSE_PROJECT_NAME,请补齐后重试" + [[ -n "$env_runtime_env" ]] || fail ".env 缺少 ENV,请补齐后重试" + [[ -n "$env_login_key_id" ]] || fail ".env 缺少 LOGIN_RSA_KEY_ID,请补齐后重试" + + if [[ "$env_project_name" != "$desired_project_name" ]]; then + fail ".env 的 COMPOSE_PROJECT_NAME=$env_project_name 与目标环境 $TARGET_ENV 期望值 $desired_project_name 不一致" + fi + if [[ "$env_runtime_env" != "$desired_runtime_env" ]]; then + fail ".env 的 ENV=$env_runtime_env 与目标环境 $TARGET_ENV 期望值 $desired_runtime_env 不一致" + fi + + EFFECTIVE_PROJECT_NAME="$env_project_name" + EFFECTIVE_RUNTIME_ENV="$env_runtime_env" + EFFECTIVE_LOGIN_KEY_ID="$env_login_key_id" } generate_escaped_private_key() { @@ -323,7 +346,7 @@ confirm_install() { step "确认安装参数" local env_status build_status migrate_status - env_status="$([[ -f "$ENV_FILE" ]] && printf '已存在(将备份后重写)' || printf '不存在(将新建)')" + env_status="$([[ -f "$ENV_FILE" ]] && printf '已存在' || printf '新建')" build_status="$([[ "$SKIP_BUILD" -eq 1 ]] && printf '跳过' || printf '执行')" migrate_status="$([[ "$SKIP_MIGRATE" -eq 1 ]] && printf '跳过' || printf '执行')" @@ -333,7 +356,7 @@ confirm_install() { printf ' %s%-18s%s %s\n' "${C_BOLD}" "Compose 项目" "${C_RESET}" "$project_name" printf ' %s%-18s%s %s\n' "${C_BOLD}" "健康检查地址" "${C_RESET}" "$BASE_URL" printf ' %s%-18s%s %s\n' "${C_BOLD}" ".env 文件" "${C_RESET}" "$env_status" - printf ' %s%-18s%s %s\n' "${C_BOLD}" "pg_data 目录" "${C_RESET}" "$([[ -d "$ROOT_DIR/pg_data" ]] && printf '已存在(保留)' || printf '不存在')" + printf ' %s%-18s%s %s\n' "${C_BOLD}" "pg_data 目录" "${C_RESET}" "$([[ -d "$ROOT_DIR/pg_data" ]] && printf '已存在' || printf '新建')" printf ' %s%-18s%s %s\n' "${C_BOLD}" "镜像构建" "${C_RESET}" "$build_status" printf ' %s%-18s%s %s\n' "${C_BOLD}" "数据库迁移" "${C_RESET}" "$migrate_status" printf '\n' @@ -352,40 +375,12 @@ confirm_install() { # ── .env 写入 ───────────────────────────────── -backup_env_file() { - if [[ -f "$ENV_FILE" ]]; then - local backup_file - backup_file="$ENV_FILE.bak.$(date +%Y%m%d%H%M%S)" - cp "$ENV_FILE" "$backup_file" - ok "已备份现有 .env → $(basename "$backup_file")" - fi -} - write_env_file() { local project_name="$1" runtime_env="$2" login_key_id="$3" local jwt_secret="$4" rsa_private_key="$5" local temp_file temp_file="$(mktemp "$ROOT_DIR/.env.tmp.XXXXXX")" - if [[ -f "$ENV_FILE" ]]; then - awk ' - BEGIN { - prefixes[1] = "COMPOSE_PROJECT_NAME=" - prefixes[2] = "ENV=" - prefixes[3] = "JWT_SECRET_KEY=" - prefixes[4] = "LOGIN_RSA_KEY_ID=" - prefixes[5] = "LOGIN_RSA_PRIVATE_KEY=" - } - { - drop = 0 - for (i = 1; i <= 5; i++) { - if (index($0, prefixes[i]) == 1) { drop = 1; break } - } - if (!drop) print - } - ' "$ENV_FILE" > "$temp_file" - fi - { printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" printf 'ENV=%s\n' "$runtime_env" @@ -403,31 +398,23 @@ prepare_env_file() { local project_name="$1" runtime_env="$2" login_key_id="$3" local jwt_secret="" rsa_private_key="" + if [[ -f "$ENV_FILE" ]]; then + ok "检测到现有 .env,保留原文件并跳过写入" + return + fi + if [[ "$TARGET_ENV" == "dev" ]]; then jwt_secret="dev-secret" rsa_private_key="" else - jwt_secret="$(read_env_value JWT_SECRET_KEY || true)" - if [[ -z "$jwt_secret" || "$jwt_secret" == "dev-secret" ]]; then - jwt_secret="$(generate_jwt_secret)" - ok "已生成新的 JWT_SECRET_KEY" - else - ok "保留现有 JWT_SECRET_KEY" - fi + jwt_secret="$(generate_jwt_secret)" + ok "已生成新的 JWT_SECRET_KEY" - rsa_private_key="$(read_env_value LOGIN_RSA_PRIVATE_KEY || true)" - if [[ -z "$rsa_private_key" ]]; then - log "正在生成 RSA 2048 密钥对,请稍候…" - rsa_private_key="$(generate_escaped_private_key)" - ok "已生成新的 LOGIN_RSA_PRIVATE_KEY" - elif private_key_is_valid "$rsa_private_key"; then - ok "保留现有有效 LOGIN_RSA_PRIVATE_KEY" - else - fail "现有 LOGIN_RSA_PRIVATE_KEY 不是合法的 PEM 私钥,请修复 .env 后重试(脚本不会自动覆盖已有密钥)" - fi + log "正在生成 RSA 2048 密钥对,请稍候…" + rsa_private_key="$(generate_escaped_private_key)" + ok "已生成新的 LOGIN_RSA_PRIVATE_KEY" fi - backup_env_file write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key" } @@ -440,13 +427,13 @@ run_compose_config() { run_backend_init() { [[ "$TARGET_ENV" != "dev" ]] || return 0 - step "初始化生产数据库与管理员账号" + step "初始化数据库与管理员账号" run_quiet "运行 backend-init 容器" -- docker compose run --rm backend-init } run_build_and_start() { if [[ "$SKIP_BUILD" -eq 1 ]]; then - step "启动服务(跳过镜像重新构建)" + step "启动服务(跳过镜像构建)" run_quiet "启动容器" -- docker compose up -d else step "构建镜像并启动服务" @@ -519,9 +506,11 @@ check_http_endpoint() { local path="$1" local url="${BASE_URL%/}$path" log "探测接口: $url" - local attempt=1 max_attempts=10 delay=2 + local attempt=1 max_attempts=10 delay=2 warmup_delay=3 + log "等待服务预热 ${warmup_delay}s …" + sleep "$warmup_delay" while [[ "$attempt" -le "$max_attempts" ]]; do - if curl -fsS "$url" >/dev/null; then + if curl -fsS "$url" >/dev/null 2>&1; then ok "接口就绪: $path" return 0 fi @@ -575,14 +564,16 @@ main() { login_key_id="$(key_id)" resolve_base_url + resolve_effective_config "$project_name" "$runtime_env" "$login_key_id" check_dependencies - confirm_install "$project_name" "$runtime_env" + confirm_install "$EFFECTIVE_PROJECT_NAME" "$EFFECTIVE_RUNTIME_ENV" prepare_env_file "$project_name" "$runtime_env" "$login_key_id" run_compose_config run_backend_init run_build_and_start run_migrations - run_health_checks "$runtime_env" "$login_key_id" + resolve_effective_config "$project_name" "$runtime_env" "$login_key_id" + run_health_checks "$EFFECTIVE_RUNTIME_ENV" "$EFFECTIVE_LOGIN_KEY_ID" show_success }