feat: 增加 CTMS 一键安装脚本
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
# CTMS 安装步骤向导
|
# CTMS 安装步骤向导
|
||||||
|
|
||||||
按目标分支选择对应章节执行:
|
推荐使用安装脚本完成 Docker 部署、环境变量、健康检查和数据迁移。手工步骤保留为排障附录。
|
||||||
|
|
||||||
- [dev 分支安装](#dev-分支安装)
|
- [安装前准备](#安装前准备)
|
||||||
- [main 分支安装](#main-分支安装)
|
- [推荐脚本安装](#推荐脚本安装)
|
||||||
- [release 分支安装](#release-分支安装)
|
- [手工安装/排障:dev 分支](#手工安装排障dev-分支)
|
||||||
|
- [手工安装/排障:main 分支](#手工安装排障main-分支)
|
||||||
|
- [手工安装/排障:release 分支](#手工安装排障release-分支)
|
||||||
|
- [常见安装问题](#常见安装问题)
|
||||||
|
|
||||||
## 安装前准备
|
## 安装前准备
|
||||||
|
|
||||||
@@ -89,7 +92,61 @@ grep -n "^\\.env\\.\\*$" .gitignore
|
|||||||
|
|
||||||
预期能看到 `.env` 和 `.env.*` 已被忽略。
|
预期能看到 `.env` 和 `.env.*` 已被忽略。
|
||||||
|
|
||||||
## dev 分支安装
|
## 推荐脚本安装
|
||||||
|
|
||||||
|
安装脚本入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-ctms.sh dev|main|release [选项]
|
||||||
|
```
|
||||||
|
|
||||||
|
常用示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-ctms.sh dev
|
||||||
|
bash scripts/install-ctms.sh main --base-url http://127.0.0.1:8888
|
||||||
|
bash scripts/install-ctms.sh release --base-url https://ctms.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
可选参数:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--base-url <url> 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。
|
||||||
|
--yes 跳过交互确认,适合自动化执行。
|
||||||
|
--skip-build 跳过 docker compose up -d --build。
|
||||||
|
--skip-migrate 跳过 alembic upgrade head。
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 检查 docker、docker compose、openssl、curl。
|
||||||
|
2. 创建或更新 .env;已有 .env 会先备份为 .env.bak.<时间戳>。
|
||||||
|
3. main/release 缺少 JWT 或 RSA 私钥时自动生成,已有有效密钥保持不变。
|
||||||
|
4. main/release 先执行 backend-init。
|
||||||
|
5. 执行 docker compose up -d --build。
|
||||||
|
6. 执行 docker compose run --rm backend python -m alembic upgrade head。
|
||||||
|
7. 检查容器状态、后端环境、/health、/api/v1/auth/login-key。
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本不会执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
git checkout
|
||||||
|
git pull
|
||||||
|
docker compose down -v
|
||||||
|
删除 pg_data
|
||||||
|
删除数据库 volume
|
||||||
|
自动安装系统依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
如果只想查看参数说明:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-ctms.sh --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## 手工安装/排障:dev 分支
|
||||||
|
|
||||||
### 1. 切换分支
|
### 1. 切换分支
|
||||||
|
|
||||||
@@ -184,7 +241,7 @@ npm run build
|
|||||||
cd ..
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
## main 分支安装
|
## 手工安装/排障:main 分支
|
||||||
|
|
||||||
### 1. 切换分支
|
### 1. 切换分支
|
||||||
|
|
||||||
@@ -213,6 +270,12 @@ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login_rsa_main
|
|||||||
awk '{printf "%s\\n", $0}' login_rsa_main.pem
|
awk '{printf "%s\\n", $0}' login_rsa_main.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
|
也可以直接生成完整的 `.env` 配置行,复制输出结果替换 `.env` 中的 `LOGIN_RSA_PRIVATE_KEY`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf 'LOGIN_RSA_PRIVATE_KEY=%s\n' "$(awk '{printf "%s\\n", $0}' login_rsa_main.pem)"
|
||||||
|
```
|
||||||
|
|
||||||
### 3. 写入环境配置
|
### 3. 写入环境配置
|
||||||
|
|
||||||
在仓库根目录创建或覆盖 `.env`,将占位符替换为上一步生成的值:
|
在仓库根目录创建或覆盖 `.env`,将占位符替换为上一步生成的值:
|
||||||
@@ -339,7 +402,7 @@ npm run build
|
|||||||
cd ..
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
## release 分支安装
|
## 手工安装/排障:release 分支
|
||||||
|
|
||||||
### 1. 切换分支
|
### 1. 切换分支
|
||||||
|
|
||||||
@@ -384,6 +447,12 @@ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login_rsa_rele
|
|||||||
awk '{printf "%s\\n", $0}' login_rsa_release.pem
|
awk '{printf "%s\\n", $0}' login_rsa_release.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
|
也可以直接生成完整的 `.env` 配置行,复制输出结果替换 `.env` 中的 `LOGIN_RSA_PRIVATE_KEY`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf 'LOGIN_RSA_PRIVATE_KEY=%s\n' "$(awk '{printf "%s\\n", $0}' login_rsa_release.pem)"
|
||||||
|
```
|
||||||
|
|
||||||
将 `login_rsa_release.pem` 保存到服务器安全目录或密钥管理系统。
|
将 `login_rsa_release.pem` 保存到服务器安全目录或密钥管理系统。
|
||||||
|
|
||||||
### 4. 写入环境配置
|
### 4. 写入环境配置
|
||||||
|
|||||||
Executable
+470
@@ -0,0 +1,470 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TARGET_ENV=""
|
||||||
|
BASE_URL=""
|
||||||
|
ASSUME_YES=0
|
||||||
|
SKIP_BUILD=0
|
||||||
|
SKIP_MIGRATE=0
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ENV_FILE="$ROOT_DIR/.env"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法:
|
||||||
|
bash scripts/install-ctms.sh dev|main|release [选项]
|
||||||
|
|
||||||
|
选项:
|
||||||
|
--base-url <url> 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。
|
||||||
|
--yes 跳过交互确认,适合自动化执行。
|
||||||
|
--skip-build 跳过 docker compose up -d --build。
|
||||||
|
--skip-migrate 跳过 alembic upgrade head。
|
||||||
|
-h, --help 显示帮助。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
bash scripts/install-ctms.sh dev
|
||||||
|
bash scripts/install-ctms.sh main --base-url http://127.0.0.1:8888
|
||||||
|
bash scripts/install-ctms.sh release --base-url https://ctms.example.com
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[CTMS] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
step() {
|
||||||
|
printf '\n[CTMS] >>> %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '\n[CTMS] 安装中止: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
log "执行: $*"
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_ENV="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$TARGET_ENV" in
|
||||||
|
dev|main|release) ;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
fail "目标环境必须是 dev、main 或 release"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--base-url)
|
||||||
|
[[ $# -ge 2 ]] || fail "--base-url 需要提供 URL"
|
||||||
|
BASE_URL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--yes)
|
||||||
|
ASSUME_YES=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-build)
|
||||||
|
SKIP_BUILD=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-migrate)
|
||||||
|
SKIP_MIGRATE=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
fail "未知参数: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command_name="$1"
|
||||||
|
local install_hint="$2"
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
printf '[CTMS] 缺少依赖: %s\n' "$command_name" >&2
|
||||||
|
printf '[CTMS] 安装建议: %s\n' "$install_hint" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_dependencies() {
|
||||||
|
step "检查系统依赖"
|
||||||
|
local missing=0
|
||||||
|
require_command docker "安装 Docker Engine 或 Docker Desktop,并确认 docker 命令可用。" || missing=1
|
||||||
|
require_command openssl "Linux: sudo apt-get install -y openssl;macOS: brew install openssl。" || missing=1
|
||||||
|
require_command curl "Linux: sudo apt-get install -y curl;macOS: brew install curl。" || missing=1
|
||||||
|
if [[ "$missing" -ne 0 ]]; then
|
||||||
|
fail "系统依赖不完整"
|
||||||
|
fi
|
||||||
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
|
fail "缺少 Docker Compose v2,请确认 docker compose version 可正常执行"
|
||||||
|
fi
|
||||||
|
log "依赖检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
compose_project_name() {
|
||||||
|
case "$TARGET_ENV" in
|
||||||
|
dev) printf 'ctms_dev' ;;
|
||||||
|
main) printf 'ctms_main' ;;
|
||||||
|
release) printf 'ctms_release' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
app_env() {
|
||||||
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
|
printf 'development'
|
||||||
|
else
|
||||||
|
printf 'production'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
key_id() {
|
||||||
|
local today
|
||||||
|
today="$(date +%Y%m%d)"
|
||||||
|
case "$TARGET_ENV" in
|
||||||
|
dev) printf 'default' ;;
|
||||||
|
main) printf 'main-%s' "$today" ;;
|
||||||
|
release) printf 'release-%s' "$today" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
default_base_url() {
|
||||||
|
case "$TARGET_ENV" in
|
||||||
|
dev|main) printf 'http://127.0.0.1:8888' ;;
|
||||||
|
release) printf '' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
read_env_value() {
|
||||||
|
local key="$1"
|
||||||
|
[[ -f "$ENV_FILE" ]] || return 0
|
||||||
|
awk -v key="$key" '
|
||||||
|
BEGIN { prefix = key "=" }
|
||||||
|
index($0, prefix) == 1 {
|
||||||
|
value = substr($0, length(prefix) + 1)
|
||||||
|
if (value ~ /^".*"$/ || value ~ /^\047.*\047$/) {
|
||||||
|
value = substr(value, 2, length(value) - 2)
|
||||||
|
}
|
||||||
|
print value
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$ENV_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_private_key_to_stdout() {
|
||||||
|
local escaped_key="$1"
|
||||||
|
printf '%b' "$escaped_key"
|
||||||
|
}
|
||||||
|
|
||||||
|
private_key_is_valid() {
|
||||||
|
local escaped_key="$1"
|
||||||
|
[[ -n "$escaped_key" ]] || return 1
|
||||||
|
if ! normalize_private_key_to_stdout "$escaped_key" | openssl rsa -check -noout >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_escaped_private_key() {
|
||||||
|
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null \
|
||||||
|
| awk '{printf "%s\\n", $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_jwt_secret() {
|
||||||
|
openssl rand -hex 32
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_base_url() {
|
||||||
|
if [[ -z "$BASE_URL" ]]; then
|
||||||
|
BASE_URL="$(default_base_url)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TARGET_ENV" == "release" && -z "$BASE_URL" && "$ASSUME_YES" -eq 0 ]]; then
|
||||||
|
read -r -p "请输入 release 健康检查域名,例如 https://ctms.example.com: " BASE_URL
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$BASE_URL" ]]; then
|
||||||
|
fail "release 环境必须通过 --base-url 或交互输入提供 HTTPS 地址"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TARGET_ENV" == "release" && "$BASE_URL" != https://* ]]; then
|
||||||
|
fail "release 的 --base-url 必须使用 HTTPS"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_install() {
|
||||||
|
local project_name="$1"
|
||||||
|
local runtime_env="$2"
|
||||||
|
step "确认安装参数"
|
||||||
|
log "目标环境: $TARGET_ENV"
|
||||||
|
log "运行 ENV: $runtime_env"
|
||||||
|
log "Compose 项目名: $project_name"
|
||||||
|
log "健康检查地址: $BASE_URL"
|
||||||
|
log "已有 .env: $([[ -f "$ENV_FILE" ]] && printf '会先备份再重写' || printf '不存在,将创建')"
|
||||||
|
log "已有 pg_data: $([[ -d "$ROOT_DIR/pg_data" ]] && printf '保留' || printf '不存在')"
|
||||||
|
log "构建镜像: $([[ "$SKIP_BUILD" -eq 1 ]] && printf '跳过' || printf '执行')"
|
||||||
|
log "数据库迁移: $([[ "$SKIP_MIGRATE" -eq 1 ]] && printf '跳过' || printf '执行')"
|
||||||
|
|
||||||
|
if [[ "$ASSUME_YES" -eq 1 ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local answer
|
||||||
|
read -r -p "确认继续安装?输入 yes 继续: " answer
|
||||||
|
[[ "$answer" == "yes" ]] || fail "用户取消"
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
log "已备份现有 .env: $backup_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_env_file() {
|
||||||
|
local project_name="$1"
|
||||||
|
local runtime_env="$2"
|
||||||
|
local login_key_id="$3"
|
||||||
|
local jwt_secret="$4"
|
||||||
|
local 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"
|
||||||
|
printf 'JWT_SECRET_KEY=%s\n' "$jwt_secret"
|
||||||
|
printf 'LOGIN_RSA_KEY_ID=%s\n' "$login_key_id"
|
||||||
|
printf 'LOGIN_RSA_PRIVATE_KEY=%s\n' "$rsa_private_key"
|
||||||
|
} >> "$temp_file"
|
||||||
|
mv "$temp_file" "$ENV_FILE"
|
||||||
|
log "已写入 .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_env_file() {
|
||||||
|
step "准备 .env"
|
||||||
|
local project_name="$1"
|
||||||
|
local runtime_env="$2"
|
||||||
|
local login_key_id="$3"
|
||||||
|
local jwt_secret=""
|
||||||
|
local rsa_private_key=""
|
||||||
|
|
||||||
|
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)"
|
||||||
|
log "已生成新的 JWT_SECRET_KEY"
|
||||||
|
else
|
||||||
|
log "保留已有 JWT_SECRET_KEY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsa_private_key="$(read_env_value LOGIN_RSA_PRIVATE_KEY || true)"
|
||||||
|
if [[ -z "$rsa_private_key" ]]; then
|
||||||
|
rsa_private_key="$(generate_escaped_private_key)"
|
||||||
|
log "已生成新的 LOGIN_RSA_PRIVATE_KEY"
|
||||||
|
elif private_key_is_valid "$rsa_private_key"; then
|
||||||
|
log "保留已有有效 LOGIN_RSA_PRIVATE_KEY"
|
||||||
|
else
|
||||||
|
fail "现有 LOGIN_RSA_PRIVATE_KEY 不是有效 PEM 私钥。请修复 .env 后重试,脚本不会自动覆盖已有无效密钥。"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_env_file
|
||||||
|
write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_compose_config() {
|
||||||
|
step "检查 Docker Compose 配置"
|
||||||
|
run docker compose config >/dev/null
|
||||||
|
log "Docker Compose 配置检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_backend_init() {
|
||||||
|
if [[ "$TARGET_ENV" == "dev" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
step "初始化生产数据库和管理员"
|
||||||
|
run docker compose run --rm backend-init
|
||||||
|
}
|
||||||
|
|
||||||
|
run_build_and_start() {
|
||||||
|
if [[ "$SKIP_BUILD" -eq 1 ]]; then
|
||||||
|
step "启动服务(跳过镜像重新构建)"
|
||||||
|
run docker compose up -d
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
step "构建镜像并启动服务"
|
||||||
|
run docker compose up -d --build
|
||||||
|
}
|
||||||
|
|
||||||
|
run_migrations() {
|
||||||
|
if [[ "$SKIP_MIGRATE" -eq 1 ]]; then
|
||||||
|
step "跳过数据库迁移"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
step "执行数据库迁移"
|
||||||
|
run docker compose run --rm backend python -m alembic upgrade head
|
||||||
|
}
|
||||||
|
|
||||||
|
check_container_status() {
|
||||||
|
step "检查容器状态"
|
||||||
|
run docker compose ps
|
||||||
|
local ps_output
|
||||||
|
ps_output="$(docker compose ps --services --filter status=running 2>/dev/null || true)"
|
||||||
|
printf '%s\n' "$ps_output" | grep -qx 'db' || fail "db 容器未运行"
|
||||||
|
printf '%s\n' "$ps_output" | grep -qx 'backend' || fail "backend 容器未运行"
|
||||||
|
printf '%s\n' "$ps_output" | grep -qx 'nginx' || fail "nginx 容器未运行"
|
||||||
|
log "容器状态检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_backend_environment() {
|
||||||
|
step "检查后端环境变量"
|
||||||
|
local expected_env="$1"
|
||||||
|
local expected_key_id="$2"
|
||||||
|
local expect_rsa="$3"
|
||||||
|
local check_code
|
||||||
|
|
||||||
|
check_code=$(cat <<'PY'
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.core.login_crypto import _normalize_pem
|
||||||
|
|
||||||
|
expected_env = "__EXPECTED_ENV__"
|
||||||
|
expected_key_id = "__EXPECTED_KEY_ID__"
|
||||||
|
expect_rsa = "__EXPECT_RSA__" == "1"
|
||||||
|
|
||||||
|
print("ENV=" + settings.ENV)
|
||||||
|
print("JWT_SECRET_IS_DEV=" + str(settings.JWT_SECRET_KEY == "dev-secret"))
|
||||||
|
print("LOGIN_RSA_PRIVATE_KEY_SET=" + str(bool(settings.LOGIN_RSA_PRIVATE_KEY)))
|
||||||
|
print("LOGIN_RSA_KEY_ID=" + settings.LOGIN_RSA_KEY_ID)
|
||||||
|
|
||||||
|
if settings.ENV != expected_env:
|
||||||
|
raise SystemExit(f"ENV mismatch: {settings.ENV} != {expected_env}")
|
||||||
|
if settings.LOGIN_RSA_KEY_ID != expected_key_id:
|
||||||
|
raise SystemExit("LOGIN_RSA_KEY_ID mismatch")
|
||||||
|
if expected_env == "production" and settings.JWT_SECRET_KEY == "dev-secret":
|
||||||
|
raise SystemExit("production JWT_SECRET_KEY must not be dev-secret")
|
||||||
|
if expect_rsa:
|
||||||
|
if not settings.LOGIN_RSA_PRIVATE_KEY:
|
||||||
|
raise SystemExit("LOGIN_RSA_PRIVATE_KEY is required")
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
serialization.load_pem_private_key(
|
||||||
|
_normalize_pem(settings.LOGIN_RSA_PRIVATE_KEY).encode("utf-8"),
|
||||||
|
password=None,
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
check_code="${check_code//__EXPECTED_ENV__/$expected_env}"
|
||||||
|
check_code="${check_code//__EXPECTED_KEY_ID__/$expected_key_id}"
|
||||||
|
check_code="${check_code//__EXPECT_RSA__/$expect_rsa}"
|
||||||
|
run docker compose exec -T backend python -c "$check_code"
|
||||||
|
log "后端环境变量检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_http_endpoint() {
|
||||||
|
local path="$1"
|
||||||
|
local url="${BASE_URL%/}$path"
|
||||||
|
log "检查接口: $url"
|
||||||
|
if ! curl -fsS "$url" >/dev/null; then
|
||||||
|
if [[ "$path" == "/api/v1/auth/login-key" ]]; then
|
||||||
|
printf '[CTMS] 登录公钥接口失败。请检查 .env 中 LOGIN_RSA_PRIVATE_KEY 是否是完整 PEM 单行转义文本。\n' >&2
|
||||||
|
fi
|
||||||
|
fail "接口检查失败: $url"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_progress_note() {
|
||||||
|
log "当前阶段: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_health_checks() {
|
||||||
|
local runtime_env="$1"
|
||||||
|
local login_key_id="$2"
|
||||||
|
local expect_rsa=0
|
||||||
|
if [[ "$runtime_env" == "production" ]]; then
|
||||||
|
expect_rsa=1
|
||||||
|
fi
|
||||||
|
check_container_status
|
||||||
|
check_backend_environment "$runtime_env" "$login_key_id" "$expect_rsa"
|
||||||
|
step "检查 HTTP 接口"
|
||||||
|
check_http_endpoint "/health"
|
||||||
|
check_http_endpoint "/api/v1/auth/login-key"
|
||||||
|
log "HTTP 接口检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
local project_name
|
||||||
|
local runtime_env
|
||||||
|
local login_key_id
|
||||||
|
project_name="$(compose_project_name)"
|
||||||
|
runtime_env="$(app_env)"
|
||||||
|
login_key_id="$(key_id)"
|
||||||
|
|
||||||
|
resolve_base_url
|
||||||
|
check_dependencies
|
||||||
|
show_progress_note "准备写入环境配置"
|
||||||
|
confirm_install "$project_name" "$runtime_env"
|
||||||
|
prepare_env_file "$project_name" "$runtime_env" "$login_key_id"
|
||||||
|
show_progress_note "校验并启动 Docker 流程"
|
||||||
|
run_compose_config
|
||||||
|
run_backend_init
|
||||||
|
run_build_and_start
|
||||||
|
run_migrations
|
||||||
|
show_progress_note "执行健康检查"
|
||||||
|
run_health_checks "$runtime_env" "$login_key_id"
|
||||||
|
|
||||||
|
step "安装完成"
|
||||||
|
log "访问地址: $BASE_URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user