环境配置统一以 .env 为单一事实来源,移除 .ctms-env
- 删除 .ctms-env:环境名由 .env 的 COMPOSE_PROJECT_NAME 反推 (ctms_detect_env),健康检查地址改存 .env 的 CTMS_BASE_URL - prepare_env_file 由“已存在则整体跳过”改为逐项探测补全: 身份类缺失补目标值、密钥类缺失/空才生成、已有值一律不动 - resolve_effective_config 放宽为“冲突才致命”:身份类缺失交给 补全,仅 COMPOSE_PROJECT_NAME/ENV 与目标环境冲突时报错停止 (防止覆盖后孤立旧环境容器) - 新增 ctms_env_has_key 区分“键缺失”与“键存在但空值” (dev 的 LOGIN_RSA_PRIVATE_KEY= 是合法空值) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+8
-55
@@ -171,16 +171,9 @@ select_menu() {
|
|||||||
done
|
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() {
|
pick_env() {
|
||||||
local env default_index=0
|
local env default_index=0
|
||||||
env="$(ctms_config_get "env" || true)"
|
env="$(ctms_detect_env)"
|
||||||
case "$env" in
|
case "$env" in
|
||||||
main) default_index=1 ;;
|
main) default_index=1 ;;
|
||||||
release) default_index=2 ;;
|
release) default_index=2 ;;
|
||||||
@@ -210,44 +203,6 @@ pick_env() {
|
|||||||
esac
|
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 表示无可卸载环境(调用方应跳过卸载)。
|
# 返回 0 表示已选定 SELECTED_ENV,返回 1 表示无可卸载环境(调用方应跳过卸载)。
|
||||||
@@ -276,7 +231,7 @@ pick_deployed_env() {
|
|||||||
|
|
||||||
collect_install_options() {
|
collect_install_options() {
|
||||||
local env="$1"
|
local env="$1"
|
||||||
SELECTED_BASE_URL="$(ctms_config_get "base-url" || true)"
|
SELECTED_BASE_URL="$(ctms_read_env_value CTMS_BASE_URL || true)"
|
||||||
|
|
||||||
if [[ "$env" == "release" && -z "$SELECTED_BASE_URL" ]]; then
|
if [[ "$env" == "release" && -z "$SELECTED_BASE_URL" ]]; then
|
||||||
SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址,例如 https://ctms.example.com")"
|
SELECTED_BASE_URL="$(ctms_ask_line "请输入健康检查地址,例如 https://ctms.example.com")"
|
||||||
@@ -505,9 +460,8 @@ interactive_main() {
|
|||||||
pick_env
|
pick_env
|
||||||
collect_install_options "$SELECTED_ENV"
|
collect_install_options "$SELECTED_ENV"
|
||||||
BREADCRUMB="${OP_LABEL} › ${SELECTED_ENV} › 执行中"
|
BREADCRUMB="${OP_LABEL} › ${SELECTED_ENV} › 执行中"
|
||||||
if run_install_like_script "install.sh" "$SELECTED_ENV"; then
|
# 环境与健康检查地址由底层 install.sh 写入 .env(单一事实来源);失败不退出控制台。
|
||||||
persist_env "$SELECTED_ENV"
|
run_install_like_script "install.sh" "$SELECTED_ENV" || true
|
||||||
fi
|
|
||||||
pause_return
|
pause_return
|
||||||
;;
|
;;
|
||||||
1)
|
1)
|
||||||
@@ -520,9 +474,8 @@ interactive_main() {
|
|||||||
# 记录更新前 HEAD,用于结束后判断代码是否被改动(决定 stash 能否安全自动恢复)
|
# 记录更新前 HEAD,用于结束后判断代码是否被改动(决定 stash 能否安全自动恢复)
|
||||||
local head_before_update
|
local head_before_update
|
||||||
head_before_update="$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true)"
|
head_before_update="$(git -C "$ROOT_DIR" rev-parse HEAD 2>/dev/null || true)"
|
||||||
if run_update_script "$SELECTED_ENV"; then
|
# 环境与健康检查地址由底层 update→install.sh 写入 .env;失败也要继续做 stash 善后。
|
||||||
persist_env "$SELECTED_ENV"
|
run_update_script "$SELECTED_ENV" || true
|
||||||
fi
|
|
||||||
restore_stash_after_update "$head_before_update"
|
restore_stash_after_update "$head_before_update"
|
||||||
fi
|
fi
|
||||||
pause_return
|
pause_return
|
||||||
@@ -537,9 +490,9 @@ interactive_main() {
|
|||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
OP_LABEL="资源状态"
|
OP_LABEL="资源状态"
|
||||||
# 只读快照,无需选环境:直接用 .ctms-env 识别的环境(无配置回退 dev)
|
# 只读快照,无需选环境:从 .env 反推环境(无配置回退 dev)
|
||||||
local env_for_status
|
local env_for_status
|
||||||
env_for_status="$(ctms_config_get "env" || true)"
|
env_for_status="$(ctms_detect_env)"
|
||||||
env_for_status="${env_for_status:-dev}"
|
env_for_status="${env_for_status:-dev}"
|
||||||
BREADCRUMB="${OP_LABEL} › ${env_for_status}"
|
BREADCRUMB="${OP_LABEL} › ${env_for_status}"
|
||||||
# status.sh 自身是交互循环(q/Enter 退出并清屏),正常退出直接回菜单;
|
# status.sh 自身是交互循环(q/Enter 退出并清屏),正常退出直接回菜单;
|
||||||
|
|||||||
@@ -261,6 +261,17 @@ ctms_key_id() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 从 .env 的 COMPOSE_PROJECT_NAME 反推环境名(dev/main/release)。
|
||||||
|
# .env 是部署环境的唯一事实来源(main/release 均为 production,仅项目名可区分)。
|
||||||
|
# 无 .env 或无法识别时输出空字符串,由调用方决定回退策略。
|
||||||
|
ctms_detect_env() {
|
||||||
|
case "$(ctms_read_env_value COMPOSE_PROJECT_NAME)" in
|
||||||
|
ctms_dev) printf 'dev' ;;
|
||||||
|
ctms_main) printf 'main' ;;
|
||||||
|
ctms_release) printf 'release' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# 默认健康检查地址;release 无默认值(必须显式提供 HTTPS)
|
# 默认健康检查地址;release 无默认值(必须显式提供 HTTPS)
|
||||||
ctms_default_base_url() {
|
ctms_default_base_url() {
|
||||||
ctms_require_env "$1"
|
ctms_require_env "$1"
|
||||||
@@ -399,6 +410,14 @@ ctms_read_env_value() {
|
|||||||
' "$CTMS_ENV_FILE"
|
' "$CTMS_ENV_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 判断 .env 中是否存在某个键(区分“键缺失”与“键存在但值为空”:
|
||||||
|
# dev 的 LOGIN_RSA_PRIVATE_KEY= 是合法空值,不能当作缺失去重新生成)。
|
||||||
|
ctms_env_has_key() {
|
||||||
|
local key="$1"
|
||||||
|
[[ -f "$CTMS_ENV_FILE" ]] || return 1
|
||||||
|
grep -q "^${key}=" "$CTMS_ENV_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
ctms_upsert_env_value() {
|
ctms_upsert_env_value() {
|
||||||
local key="$1" value="$2"
|
local key="$1" value="$2"
|
||||||
local temp_file
|
local temp_file
|
||||||
|
|||||||
+67
-24
@@ -149,20 +149,18 @@ resolve_effective_config() {
|
|||||||
env_runtime_env="$(read_env_value ENV || true)"
|
env_runtime_env="$(read_env_value ENV || true)"
|
||||||
env_login_key_id="$(read_env_value LOGIN_RSA_KEY_ID || true)"
|
env_login_key_id="$(read_env_value LOGIN_RSA_KEY_ID || true)"
|
||||||
|
|
||||||
[[ -n "$env_project_name" ]] || fail ".env 缺少 COMPOSE_PROJECT_NAME,请补齐后重试"
|
# 身份类“冲突”才致命(防止覆盖后孤立原环境的容器);“缺失”留给 prepare_env_file 补全。
|
||||||
[[ -n "$env_runtime_env" ]] || fail ".env 缺少 ENV,请补齐后重试"
|
if [[ -n "$env_project_name" && "$env_project_name" != "$desired_project_name" ]]; then
|
||||||
[[ -n "$env_login_key_id" ]] || fail ".env 缺少 LOGIN_RSA_KEY_ID,请补齐后重试"
|
fail ".env 的 COMPOSE_PROJECT_NAME=$env_project_name 与目标环境 $TARGET_ENV 期望值 $desired_project_name 不一致;如需切换环境请先卸载旧环境或手动清理 .env"
|
||||||
|
|
||||||
if [[ "$env_project_name" != "$desired_project_name" ]]; then
|
|
||||||
fail ".env 的 COMPOSE_PROJECT_NAME=$env_project_name 与目标环境 $TARGET_ENV 期望值 $desired_project_name 不一致"
|
|
||||||
fi
|
fi
|
||||||
if [[ "$env_runtime_env" != "$desired_runtime_env" ]]; then
|
if [[ -n "$env_runtime_env" && "$env_runtime_env" != "$desired_runtime_env" ]]; then
|
||||||
fail ".env 的 ENV=$env_runtime_env 与目标环境 $TARGET_ENV 期望值 $desired_runtime_env 不一致"
|
fail ".env 的 ENV=$env_runtime_env 与目标环境 $TARGET_ENV 期望值 $desired_runtime_env 不一致;如需切换环境请先卸载旧环境或手动清理 .env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EFFECTIVE_PROJECT_NAME="$env_project_name"
|
# 仅采用 .env 中实际存在的值,缺失项保留目标默认(由 prepare_env_file 写回)。
|
||||||
EFFECTIVE_RUNTIME_ENV="$env_runtime_env"
|
[[ -n "$env_project_name" ]] && EFFECTIVE_PROJECT_NAME="$env_project_name"
|
||||||
EFFECTIVE_LOGIN_KEY_ID="$env_login_key_id"
|
[[ -n "$env_runtime_env" ]] && EFFECTIVE_RUNTIME_ENV="$env_runtime_env"
|
||||||
|
[[ -n "$env_login_key_id" ]] && EFFECTIVE_LOGIN_KEY_ID="$env_login_key_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 生成单行转义 PEM:行间用 \n 连接,末尾不追加多余 \n
|
# 生成单行转义 PEM:行间用 \n 连接,末尾不追加多余 \n
|
||||||
@@ -255,25 +253,68 @@ 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
|
# 首次安装:无 .env,写全量。
|
||||||
ok "检测到现有 .env,保留原文件并跳过写入"
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
|
jwt_secret="dev-secret"
|
||||||
|
rsa_private_key=""
|
||||||
|
else
|
||||||
|
jwt_secret="$(generate_jwt_secret)"
|
||||||
|
ok "已生成新的 JWT_SECRET_KEY"
|
||||||
|
log "正在生成 RSA 2048 密钥对,请稍候…"
|
||||||
|
rsa_private_key="$(generate_escaped_private_key)"
|
||||||
|
ok "已生成新的 LOGIN_RSA_PRIVATE_KEY"
|
||||||
|
fi
|
||||||
|
ctms_write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key"
|
||||||
|
ok ".env 写入完成"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$TARGET_ENV" == "dev" ]]; then
|
# 已有 .env:逐项探测,缺则补全,已有值一律不动(身份类冲突已在 resolve_effective_config 拦截)。
|
||||||
jwt_secret="dev-secret"
|
local repaired=0
|
||||||
rsa_private_key=""
|
|
||||||
else
|
|
||||||
jwt_secret="$(generate_jwt_secret)"
|
|
||||||
ok "已生成新的 JWT_SECRET_KEY"
|
|
||||||
|
|
||||||
log "正在生成 RSA 2048 密钥对,请稍候…"
|
# 身份类:缺失补目标值
|
||||||
rsa_private_key="$(generate_escaped_private_key)"
|
if ! ctms_env_has_key COMPOSE_PROJECT_NAME; then
|
||||||
ok "已生成新的 LOGIN_RSA_PRIVATE_KEY"
|
ctms_upsert_env_value COMPOSE_PROJECT_NAME "$project_name"; repaired=1
|
||||||
|
warn ".env 缺少 COMPOSE_PROJECT_NAME,已补为 $project_name"
|
||||||
|
fi
|
||||||
|
if ! ctms_env_has_key ENV; then
|
||||||
|
ctms_upsert_env_value ENV "$runtime_env"; repaired=1
|
||||||
|
warn ".env 缺少 ENV,已补为 $runtime_env"
|
||||||
|
fi
|
||||||
|
if ! ctms_env_has_key LOGIN_RSA_KEY_ID; then
|
||||||
|
ctms_upsert_env_value LOGIN_RSA_KEY_ID "$login_key_id"; repaired=1
|
||||||
|
warn ".env 缺少 LOGIN_RSA_KEY_ID,已补为 $login_key_id"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ctms_write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key"
|
# JWT 密钥:缺失或空才补(保留已有值,避免使现有会话失效)
|
||||||
ok ".env 写入完成"
|
if [[ -z "$(read_env_value JWT_SECRET_KEY || true)" ]]; then
|
||||||
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
|
jwt_secret="dev-secret"
|
||||||
|
else
|
||||||
|
jwt_secret="$(generate_jwt_secret)"
|
||||||
|
fi
|
||||||
|
ctms_upsert_env_value JWT_SECRET_KEY "$jwt_secret"; repaired=1
|
||||||
|
warn ".env 缺少 JWT_SECRET_KEY,已补全"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# RSA 私钥:非 dev 缺失或空才生成;dev 仅确保键存在(允许空值)
|
||||||
|
if [[ -z "$(read_env_value LOGIN_RSA_PRIVATE_KEY || true)" ]]; then
|
||||||
|
if [[ "$TARGET_ENV" != "dev" ]]; then
|
||||||
|
log "正在生成 RSA 2048 密钥对,请稍候…"
|
||||||
|
rsa_private_key="$(generate_escaped_private_key)"
|
||||||
|
ctms_upsert_env_value LOGIN_RSA_PRIVATE_KEY "$rsa_private_key"; repaired=1
|
||||||
|
warn ".env 缺少 LOGIN_RSA_PRIVATE_KEY,已生成新密钥"
|
||||||
|
elif ! ctms_env_has_key LOGIN_RSA_PRIVATE_KEY; then
|
||||||
|
ctms_upsert_env_value LOGIN_RSA_PRIVATE_KEY ""; repaired=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$repaired" -eq 1 ]]; then
|
||||||
|
ok ".env 已补全缺失项"
|
||||||
|
else
|
||||||
|
ok "检测到完整 .env,沿用现有配置"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Docker 流程 ───────────────────────────────
|
# ── Docker 流程 ───────────────────────────────
|
||||||
@@ -456,6 +497,8 @@ main() {
|
|||||||
confirm_install "$EFFECTIVE_PROJECT_NAME" "$EFFECTIVE_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"
|
||||||
sync_frontend_build_env "$runtime_env"
|
sync_frontend_build_env "$runtime_env"
|
||||||
|
# 健康检查地址持久化到 .env(单一事实来源);独立 upsert,绕开 prepare_env_file 对已存在 .env 的跳过。
|
||||||
|
ctms_upsert_env_value CTMS_BASE_URL "$BASE_URL"
|
||||||
run_compose_config
|
run_compose_config
|
||||||
run_backend_init
|
run_backend_init
|
||||||
run_build_and_start
|
run_build_and_start
|
||||||
|
|||||||
Reference in New Issue
Block a user