收紧分支环境安装流程
This commit is contained in:
@@ -113,7 +113,7 @@ bash scripts/install-ctms.sh release --base-url https://ctms.example.com
|
|||||||
```text
|
```text
|
||||||
--base-url <url> 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。
|
--base-url <url> 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。
|
||||||
--yes 跳过交互确认,适合自动化执行。
|
--yes 跳过交互确认,适合自动化执行。
|
||||||
--skip-build 跳过 docker compose up -d --build。
|
--skip-build 跳过镜像构建,仍会执行 docker compose up -d。
|
||||||
--skip-migrate 跳过 alembic upgrade head。
|
--skip-migrate 跳过 alembic upgrade head。
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -121,10 +121,10 @@ bash scripts/install-ctms.sh release --base-url https://ctms.example.com
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
1. 检查 docker、docker compose、openssl、curl。
|
1. 检查 docker、docker compose、openssl、curl。
|
||||||
2. 创建或更新 .env;已有 .env 会先备份为 .env.bak.<时间戳>。
|
2. 检查 .env;已存在则保留不改,不存在则新建。
|
||||||
3. main/release 缺少 JWT 或 RSA 私钥时自动生成,已有有效密钥保持不变。
|
3. 新建 main/release 的 .env 时自动生成 JWT 和 RSA 私钥。
|
||||||
4. main/release 先执行 backend-init。
|
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。
|
6. 执行 docker compose run --rm backend python -m alembic upgrade head。
|
||||||
7. 检查容器状态、后端环境、/health、/api/v1/auth/login-key。
|
7. 检查容器状态、后端环境、/health、/api/v1/auth/login-key。
|
||||||
```
|
```
|
||||||
|
|||||||
+52
-61
@@ -152,7 +152,7 @@ ${C_BOLD}选项:${C_RESET}
|
|||||||
dev/main 默认 http://127.0.0.1:8888
|
dev/main 默认 http://127.0.0.1:8888
|
||||||
release 必须通过此参数或交互输入提供
|
release 必须通过此参数或交互输入提供
|
||||||
${C_WHITE}--yes${C_RESET} 跳过所有交互确认,适合 CI/CD 自动化执行
|
${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}--skip-migrate${C_RESET} 跳过数据库迁移(alembic upgrade head)
|
||||||
${C_WHITE}--verbose${C_RESET} 展开所有命令的实时输出(默认折叠为 spinner)
|
${C_WHITE}--verbose${C_RESET} 展开所有命令的实时输出(默认折叠为 spinner)
|
||||||
${C_WHITE}-h, --help${C_RESET} 显示此帮助信息
|
${C_WHITE}-h, --help${C_RESET} 显示此帮助信息
|
||||||
@@ -279,13 +279,36 @@ read_env_value() {
|
|||||||
' "$ENV_FILE"
|
' "$ENV_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
normalize_private_key_to_stdout() {
|
resolve_effective_config() {
|
||||||
printf '%b' "$1"
|
local desired_project_name="$1"
|
||||||
}
|
local desired_runtime_env="$2"
|
||||||
|
local desired_login_key_id="$3"
|
||||||
|
|
||||||
private_key_is_valid() {
|
EFFECTIVE_PROJECT_NAME="$desired_project_name"
|
||||||
[[ -n "$1" ]] || return 1
|
EFFECTIVE_RUNTIME_ENV="$desired_runtime_env"
|
||||||
normalize_private_key_to_stdout "$1" | openssl rsa -check -noout >/dev/null 2>&1
|
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() {
|
generate_escaped_private_key() {
|
||||||
@@ -323,7 +346,7 @@ confirm_install() {
|
|||||||
step "确认安装参数"
|
step "确认安装参数"
|
||||||
|
|
||||||
local env_status build_status migrate_status
|
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 '执行')"
|
build_status="$([[ "$SKIP_BUILD" -eq 1 ]] && printf '跳过' || printf '执行')"
|
||||||
migrate_status="$([[ "$SKIP_MIGRATE" -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}" "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}" "健康检查地址" "${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}" ".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}" "$build_status"
|
||||||
printf ' %s%-18s%s %s\n' "${C_BOLD}" "数据库迁移" "${C_RESET}" "$migrate_status"
|
printf ' %s%-18s%s %s\n' "${C_BOLD}" "数据库迁移" "${C_RESET}" "$migrate_status"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
@@ -352,40 +375,12 @@ confirm_install() {
|
|||||||
|
|
||||||
# ── .env 写入 ─────────────────────────────────
|
# ── .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() {
|
write_env_file() {
|
||||||
local project_name="$1" runtime_env="$2" login_key_id="$3"
|
local project_name="$1" runtime_env="$2" login_key_id="$3"
|
||||||
local jwt_secret="$4" rsa_private_key="$5"
|
local jwt_secret="$4" rsa_private_key="$5"
|
||||||
local temp_file
|
local temp_file
|
||||||
temp_file="$(mktemp "$ROOT_DIR/.env.tmp.XXXXXX")"
|
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 'COMPOSE_PROJECT_NAME=%s\n' "$project_name"
|
||||||
printf 'ENV=%s\n' "$runtime_env"
|
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 project_name="$1" runtime_env="$2" login_key_id="$3"
|
||||||
local jwt_secret="" rsa_private_key=""
|
local jwt_secret="" rsa_private_key=""
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
ok "检测到现有 .env,保留原文件并跳过写入"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$TARGET_ENV" == "dev" ]]; then
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
jwt_secret="dev-secret"
|
jwt_secret="dev-secret"
|
||||||
rsa_private_key=""
|
rsa_private_key=""
|
||||||
else
|
else
|
||||||
jwt_secret="$(read_env_value JWT_SECRET_KEY || true)"
|
jwt_secret="$(generate_jwt_secret)"
|
||||||
if [[ -z "$jwt_secret" || "$jwt_secret" == "dev-secret" ]]; then
|
ok "已生成新的 JWT_SECRET_KEY"
|
||||||
jwt_secret="$(generate_jwt_secret)"
|
|
||||||
ok "已生成新的 JWT_SECRET_KEY"
|
|
||||||
else
|
|
||||||
ok "保留现有 JWT_SECRET_KEY"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rsa_private_key="$(read_env_value LOGIN_RSA_PRIVATE_KEY || true)"
|
log "正在生成 RSA 2048 密钥对,请稍候…"
|
||||||
if [[ -z "$rsa_private_key" ]]; then
|
rsa_private_key="$(generate_escaped_private_key)"
|
||||||
log "正在生成 RSA 2048 密钥对,请稍候…"
|
ok "已生成新的 LOGIN_RSA_PRIVATE_KEY"
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
backup_env_file
|
|
||||||
write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key"
|
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() {
|
run_backend_init() {
|
||||||
[[ "$TARGET_ENV" != "dev" ]] || return 0
|
[[ "$TARGET_ENV" != "dev" ]] || return 0
|
||||||
step "初始化生产数据库与管理员账号"
|
step "初始化数据库与管理员账号"
|
||||||
run_quiet "运行 backend-init 容器" -- docker compose run --rm backend-init
|
run_quiet "运行 backend-init 容器" -- docker compose run --rm backend-init
|
||||||
}
|
}
|
||||||
|
|
||||||
run_build_and_start() {
|
run_build_and_start() {
|
||||||
if [[ "$SKIP_BUILD" -eq 1 ]]; then
|
if [[ "$SKIP_BUILD" -eq 1 ]]; then
|
||||||
step "启动服务(跳过镜像重新构建)"
|
step "启动服务(跳过镜像构建)"
|
||||||
run_quiet "启动容器" -- docker compose up -d
|
run_quiet "启动容器" -- docker compose up -d
|
||||||
else
|
else
|
||||||
step "构建镜像并启动服务"
|
step "构建镜像并启动服务"
|
||||||
@@ -519,9 +506,11 @@ check_http_endpoint() {
|
|||||||
local path="$1"
|
local path="$1"
|
||||||
local url="${BASE_URL%/}$path"
|
local url="${BASE_URL%/}$path"
|
||||||
log "探测接口: $url"
|
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
|
while [[ "$attempt" -le "$max_attempts" ]]; do
|
||||||
if curl -fsS "$url" >/dev/null; then
|
if curl -fsS "$url" >/dev/null 2>&1; then
|
||||||
ok "接口就绪: $path"
|
ok "接口就绪: $path"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -575,14 +564,16 @@ main() {
|
|||||||
login_key_id="$(key_id)"
|
login_key_id="$(key_id)"
|
||||||
|
|
||||||
resolve_base_url
|
resolve_base_url
|
||||||
|
resolve_effective_config "$project_name" "$runtime_env" "$login_key_id"
|
||||||
check_dependencies
|
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"
|
prepare_env_file "$project_name" "$runtime_env" "$login_key_id"
|
||||||
run_compose_config
|
run_compose_config
|
||||||
run_backend_init
|
run_backend_init
|
||||||
run_build_and_start
|
run_build_and_start
|
||||||
run_migrations
|
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
|
show_success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user