feat(deploy): generic deploy.sh with API-based tenant migration

- Rename deploy-szyx.sh → deploy.sh, remove all customer-specific branding
- Migrate mode: prompt for pmk_ key, call public platform export API,
  pipe to private import API — no MySQL credentials needed
- Remove bcrypt dependency (no longer used in script logic)
- Update install.sh and verify.sh references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
徐勤民 2026-05-19 15:14:23 +08:00
父节点 43a423b85c
当前提交 a6a81b0755
共有 3 个文件被更改,包括 78 次插入85 次删除

查看文件

@ -194,4 +194,4 @@ printf '\n%b 依赖安装完毕,即将进入交互式部署向导 ...%b\n\n'
export DEPLOY_HOST export DEPLOY_HOST
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
exec bash scripts/deploy-szyx.sh exec bash scripts/deploy.sh

查看文件

@ -1,15 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# deploy-szyx.sh — 数字医信私有化一键部署脚本 # deploy.sh — XuqmGroup 私有化一键部署脚本
# #
# 用途:在目标机器上完成以下全部步骤: # 用途:在目标机器上完成以下全部步骤:
# 0. 交互式选择租户初始化方式(新建 / 迁移)并收集必要信息 # 0. 交互式选择租户初始化方式(新建 / 迁移)并收集必要信息
# 1. 预检Docker、Compose、python3-bcrypt、磁盘、端口) # 1. 预检Docker、Compose、磁盘、端口)
# 2. 写入配置(.env / secrets.env / xuqm.env / bootstrap.env / nginx # 2. 写入配置(.env / secrets.env / xuqm.env / bootstrap.env / nginx
# 3. 登录 ACR 镜像仓库 # 3. 登录 ACR 镜像仓库
# 4. 启动基础设施容器MySQL、Redis并等待就绪 # 4. 启动基础设施容器MySQL、Redis并等待就绪
# 5. 拉取镜像并启动所有业务容器base + im + push + update + license # 5. 拉取镜像并启动所有业务容器base + im + push + update + license
# 6. Schema 扩展is_default / deletable 列 + 删除保护触发器)+ 系统应用初始化 # 6. Schema 扩展is_default / deletable 列 + 删除保护触发器)+ 系统应用初始化
# 7. 租户初始化(新建:验证 bootstrap 结果;迁移:执行 migrate-tenant.sh # 7. 租户初始化(新建:填写信息并验证;迁移:凭迁移密钥调用 API 导入
# 8. 运行一键验证脚本确认所有服务正常 # 8. 运行一键验证脚本确认所有服务正常
# #
# 幂等性:可重复执行。已运行的容器不会被重建。 # 幂等性:可重复执行。已运行的容器不会被重建。
@ -17,8 +17,7 @@
# #
# 前提: # 前提:
# - Docker 和 Docker Compose v2 已安装 # - Docker 和 Docker Compose v2 已安装
# - python3 和 python3-bcrypt 已安装(用于密码哈希和认证) # - python3 和 curl 已安装
# - 迁移模式还需要 mysql 客户端
# - 在 XuqmGroup-PrivateDeploy 仓库根目录下执行本脚本 # - 在 XuqmGroup-PrivateDeploy 仓库根目录下执行本脚本
# #
# 覆盖默认值(可通过环境变量传入): # 覆盖默认值(可通过环境变量传入):
@ -30,11 +29,9 @@
set -euo pipefail set -euo pipefail
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 常量 — 数字医信专属配置 # 常量
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
CUSTOMER_NAME="数字医信"
CUSTOMER_SHORT="szyx"
# 镜像仓库 # 镜像仓库
REGISTRY="crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com/xuqmgroup" REGISTRY="crpi-n44qjpuucgjt8e8c.cn-beijing.personal.cr.aliyuncs.com/xuqmgroup"
@ -57,12 +54,8 @@ MYSQL_USERNAME="xuqm"
# Redismanaged 模式) # Redismanaged 模式)
REDIS_PASSWORD="${REDIS_PASSWORD:-XuqmRedis@2026}" REDIS_PASSWORD="${REDIS_PASSWORD:-XuqmRedis@2026}"
# 源生产 MySQL迁移模式的默认值,交互时可覆盖 # 公有化平台地址(迁移时调用 export API
SRC_HOST="39.107.53.187" PUBLIC_PLATFORM_URL="https://dev.xuqinmin.com"
SRC_PORT="3306"
SRC_USER="xuqm"
SRC_PASSWORD="Xuqm@2026"
SRC_DB="xuqm_tenant"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 内部变量 # 内部变量
@ -93,7 +86,7 @@ container_running() {
cd "$ROOT_DIR" cd "$ROOT_DIR"
printf '\n\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf '\n\033[1;35m══════════════════════════════════════════════════\033[0m\n'
printf '\033[1;35m %s 私有化一键部署脚本(全量部署)\033[0m\n' "$CUSTOMER_NAME" printf '\033[1;35m XuqmGroup 私有化一键部署脚本(全量部署)\033[0m\n'
printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n'
printf ' 部署目录: %s\n' "$ROOT_DIR" printf ' 部署目录: %s\n' "$ROOT_DIR"
printf ' 目标主机: %s\n' "$DEPLOY_HOST" printf ' 目标主机: %s\n' "$DEPLOY_HOST"
@ -106,15 +99,15 @@ printf ' 服务集: base + im + push + update + license\n\n'
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
command -v python3 >/dev/null 2>&1 || \ command -v python3 >/dev/null 2>&1 || \
fail "python3 未安装(租户密码哈希需要): apt install -y python3 python3-bcrypt" fail "python3 未安装: apt install -y python3"
python3 -c "import bcrypt" 2>/dev/null || \ command -v curl >/dev/null 2>&1 || \
fail "python3-bcrypt 未安装: pip3 install bcrypt 或 apt install -y python3-bcrypt" fail "curl 未安装: apt install -y curl"
printf '\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n' printf '\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n'
printf '\033[1;33m 租户初始化方式\033[0m\n' printf '\033[1;33m 租户初始化方式\033[0m\n'
printf '\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n' printf '\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n'
printf ' 1) 新建租户 — 填写租户信息,系统将为您创建全新账号\n' printf ' 1) 新建租户 — 填写租户信息,系统将为您创建全新账号\n'
printf ' 2) 迁移租户 — 从生产 MySQL 导入现有租户数据(需主账号认证\n\n' printf ' 2) 迁移租户 — 从公有平台导入现有租户数据(需迁移密钥\n\n'
DEPLOY_MODE="" DEPLOY_MODE=""
while [ -z "$DEPLOY_MODE" ]; do while [ -z "$DEPLOY_MODE" ]; do
@ -165,49 +158,26 @@ if [ "$DEPLOY_MODE" = "new" ]; then
else else
# ── 迁移模式:先收集 SRC 配置 + 认证,再继续后续步骤 ── # ── 迁移模式:先收集 SRC 配置 + 认证,再继续后续步骤 ──
printf '\n ── 迁移源 MySQL 配置(回车使用默认值)──\n' # ── 迁移模式:获取迁移密钥 ──
read -rp " 源 MySQL 主机 [${SRC_HOST}]: " _inp; [ -n "$_inp" ] && SRC_HOST="$_inp" printf '\n\033[1;33m 请先访问租户平台安全中心生成迁移密钥:\033[0m\n'
read -rp " 源 MySQL 端口 [${SRC_PORT}]: " _inp; [ -n "$_inp" ] && SRC_PORT="$_inp" printf ' 地址: %s\n' "$PUBLIC_PLATFORM_URL"
read -rp " 源 MySQL 用户 [${SRC_USER}]: " _inp; [ -n "$_inp" ] && SRC_USER="$_inp" printf ' 路径: 安全中心 → 私有化部署迁移 → 生成迁移密钥\n'
read -rsp " 源 MySQL 密码 [(隐藏,回车保留默认)]: " _inp; printf '\n'; [ -n "$_inp" ] && SRC_PASSWORD="$_inp" printf ' (密钥仅在生成时显示一次,请及时复制后回到此处)\n\n'
read -rp " 源数据库名 [${SRC_DB}]: " _inp; [ -n "$_inp" ] && SRC_DB="$_inp"
printf '\n ── 租户主账号认证 ──\n' _MIGRATE_KEY=""
while [ -z "$DEPLOY_TENANT_EMAIL" ]; do while [ -z "$_MIGRATE_KEY" ]; do
read -rp " 租户主账号邮箱: " DEPLOY_TENANT_EMAIL read -rsp " 请粘贴迁移密钥: " _MIGRATE_KEY; printf '\n'
printf '%s' "$DEPLOY_TENANT_EMAIL" | grep -qE '^[^@]+@[^@]+\.[^@]+$' || { [[ "$_MIGRATE_KEY" == pmk_* ]] || {
warn "邮箱格式不正确,请重新输入" warn "密钥格式不正确(应以 pmk_ 开头),请重新输入"
DEPLOY_TENANT_EMAIL="" _MIGRATE_KEY=""
} }
done done
ok "迁移密钥已接收"
read -rsp " 租户主账号密码(仅用于认证,不存储明文): " _MIGRATE_PASSWORD; printf '\n' DEPLOY_TENANT_EMAIL="migrate_placeholder@private.local"
[ -n "$_MIGRATE_PASSWORD" ] || fail "密码不能为空" DEPLOY_TENANT_USERNAME="migrate_placeholder"
command -v mysql >/dev/null 2>&1 || \
fail "迁移模式需要 mysql 客户端: apt install -y mysql-client"
printf ' 正在连接生产 MySQL 进行身份认证 ...\n'
_SRC_HASH=$(MYSQL_PWD="$SRC_PASSWORD" mysql \
-h "$SRC_HOST" -P "$SRC_PORT" -u "$SRC_USER" \
--connect-timeout=10 "$SRC_DB" \
-N -e "SELECT password FROM t_tenant WHERE email='${DEPLOY_TENANT_EMAIL}' AND type='MAIN' LIMIT 1" 2>/dev/null || true)
[ -n "$_SRC_HASH" ] || \
fail "生产 MySQL 中未找到租户 ${DEPLOY_TENANT_EMAIL},或无法连接到 ${SRC_HOST}:${SRC_PORT}"
_AUTH_OK=$(MIGRATE_PW="$_MIGRATE_PASSWORD" MIGRATE_HASH="$_SRC_HASH" python3 -c "
import bcrypt, os
pw = os.environ['MIGRATE_PW'].encode('utf-8')
h = os.environ['MIGRATE_HASH'].encode('utf-8')
print('ok' if bcrypt.checkpw(pw, h) else 'fail')
")
unset _MIGRATE_PASSWORD _SRC_HASH
[ "$_AUTH_OK" = "ok" ] || fail "密码认证失败,请确认主账号密码后重试"
ok "生产租户 ${DEPLOY_TENANT_EMAIL} 身份认证通过"
DEPLOY_TENANT_USERNAME="$DEPLOY_TENANT_EMAIL"
DEPLOY_TENANT_NICKNAME="" DEPLOY_TENANT_NICKNAME=""
_BOOTSTRAP_PASSWORD="change-me-on-first-login" _BOOTSTRAP_PASSWORD="already-migrated-do-not-use"
fi fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -239,12 +209,12 @@ done
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Step 2 — 写入配置 # Step 2 — 写入配置
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
step "写入数字医信专属配置(全服务)" step "写入配置(全服务)"
# .env — 主配置文件,包含所有 profile # .env — 主配置文件,包含所有 profile
cat > "$ROOT_DIR/.env" <<EOF cat > "$ROOT_DIR/.env" <<EOF
# ============================================================================= # =============================================================================
# 数字医信私有化部署主配置 # XuqmGroup 私有化部署主配置
# 部署时间: $(date '+%Y-%m-%d %H:%M:%S') # 部署时间: $(date '+%Y-%m-%d %H:%M:%S')
# 部署主机: ${DEPLOY_HOST} # 部署主机: ${DEPLOY_HOST}
# ============================================================================= # =============================================================================
@ -296,7 +266,7 @@ ok ".env 已写入"
# config/secrets.env — 敏感信息(权限 600 # config/secrets.env — 敏感信息(权限 600
mkdir -p "$ROOT_DIR/config" mkdir -p "$ROOT_DIR/config"
cat > "$ROOT_DIR/config/secrets.env" <<EOF cat > "$ROOT_DIR/config/secrets.env" <<EOF
# 数字医信私有化部署 — 敏感凭据请妥善保管,chmod 600 # XuqmGroup 私有化部署 — 敏感凭据请妥善保管,chmod 600
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_PASSWORD=${MYSQL_PASSWORD} MYSQL_PASSWORD=${MYSQL_PASSWORD}
REDIS_PASSWORD=${REDIS_PASSWORD} REDIS_PASSWORD=${REDIS_PASSWORD}
@ -307,7 +277,7 @@ ok "config/secrets.env 已写入 (chmod 600)"
# config/xuqm.env — 业务服务容器内配置(全服务开启) # config/xuqm.env — 业务服务容器内配置(全服务开启)
cat > "$ROOT_DIR/config/xuqm.env" <<EOF cat > "$ROOT_DIR/config/xuqm.env" <<EOF
# ============================================================================= # =============================================================================
# 数字医信私有化部署 — 业务配置 # XuqmGroup 私有化部署 — 业务配置
# ============================================================================= # =============================================================================
# 私有化部署模式(必须为 PRIVATE # 私有化部署模式(必须为 PRIVATE
@ -798,44 +768,67 @@ if [ "$DEPLOY_MODE" = "new" ]; then
fi fi
else else
# ── 迁移租户:检查是否有现有数据,必要时警告确认后执行迁移 ── # ── 迁移租户:调用公有平台 export API → 私有平台 import API ──
# 检查是否有现有租户数据
_EXIST_CNT="$(docker exec "$MYSQL_CTR" \ _EXIST_CNT="$(docker exec "$MYSQL_CTR" \
mysql -u "$MYSQL_USERNAME" -p"${MYSQL_PASSWORD}" "$MYSQL_DATABASE" \ mysql -u "$MYSQL_USERNAME" -p"${MYSQL_PASSWORD}" "$MYSQL_DATABASE" \
-N -e "SELECT COUNT(*) FROM t_tenant" 2>/dev/null || echo 0)" -N -e "SELECT COUNT(*) FROM t_tenant" 2>/dev/null || echo 0)"
if [ "${_EXIST_CNT:-0}" -ge 1 ]; then if [ "${_EXIST_CNT:-0}" -ge 1 ]; then
printf '\n \033[1;31m⚠ 警告:数据库中已存在租户数据!\033[0m\n' printf '\n \033[1;31m⚠ 警告:数据库中已存在租户数据!\033[0m\n'
printf ' 迁移操作将 \033[1;31m删除所有现有租户信息\033[0m账号、应用、功能配置,\n' printf ' 迁移操作将 \033[1;31m删除所有现有租户信息\033[0m账号、应用、功能配置。\n\n'
printf ' 并替换为生产环境租户 \033[1m%s\033[0m 的数据。\n\n' "$DEPLOY_TENANT_EMAIL"
read -rp " 确认删除并继续迁移?请输入 yes: " _confirm read -rp " 确认删除并继续迁移?请输入 yes: " _confirm
[ "$_confirm" = "yes" ] || fail "操作已取消" [ "$_confirm" = "yes" ] || fail "操作已取消"
fi fi
printf ' 连通性检查生产 MySQL %s ...\n' "$SRC_HOST" printf ' 正在从公有平台导出租户数据 ...\n'
MYSQL_PWD="$SRC_PASSWORD" mysql \ _EXPORT_BODY=$(python3 -c "import json, sys; print(json.dumps({'migrationKey': sys.argv[1]}))" "$_MIGRATE_KEY")
-h "$SRC_HOST" -P "$SRC_PORT" -u "$SRC_USER" \ _EXPORT_RESP=$(curl -sf --max-time 30 -X POST \
--connect-timeout=10 "$SRC_DB" \ -H "Content-Type: application/json" \
-e "SELECT 1" >/dev/null 2>&1 \ -d "$_EXPORT_BODY" \
|| fail "无法连接生产 MySQL ${SRC_HOST}:${SRC_PORT},请检查网络或凭据" "${PUBLIC_PLATFORM_URL}/api/migrate/export") \
ok "生产 MySQL 连通" || fail "导出失败:请确认迁移密钥有效且未过期(${PUBLIC_PLATFORM_URL}"
bash "$ROOT_DIR/scripts/migrate-tenant.sh" \ _EXPORT_DATA=$(printf '%s' "$_EXPORT_RESP" | python3 -c "
--src-host "$SRC_HOST" \ import json, sys
--src-port "$SRC_PORT" \ resp = json.load(sys.stdin)
--src-user "$SRC_USER" \ code = resp.get('code', 0)
--src-password "$SRC_PASSWORD" \ if code != 200:
--src-db "$SRC_DB" \ sys.exit('导出失败: ' + resp.get('message', 'unknown error'))
--tenant "$DEPLOY_TENANT_EMAIL" print(json.dumps(resp['data']))
") || fail "解析导出数据失败"
ok "租户数据已从公有平台导出"
# 迁移完成后更新 bootstrap.env,防止重启时重新创建占位租户 printf ' 正在导入租户数据到私有部署 ...\n'
cat > "$ROOT_DIR/config/tenant/bootstrap.env" <<EOF _IMPORT_RESP=$(printf '%s' "$_EXPORT_DATA" | curl -sf --max-time 30 -X POST \
-H "Content-Type: application/json" \
--data-binary @- \
"http://127.0.0.1/api/private/migrate/import") \
|| fail "导入失败:私有平台 import API 返回错误"
# 从导出数据中提取租户信息用于最终汇总
_TENANT_INFO=$(printf '%s' "$_EXPORT_DATA" | python3 -c "
import json, sys
d = json.load(sys.stdin)
t = d.get('tenant', {})
print(t.get('email', ''), t.get('username', ''))
")
DEPLOY_TENANT_EMAIL=$(printf '%s' "$_TENANT_INFO" | awk '{print $1}')
DEPLOY_TENANT_USERNAME=$(printf '%s' "$_TENANT_INFO" | awk '{print $2}')
ok "租户 ${DEPLOY_TENANT_EMAIL} 数据已成功导入"
# 更新 bootstrap.env,防止重启时重新创建占位租户
cat > "$ROOT_DIR/config/tenant/bootstrap.env" <<BSENV
TENANT_BOOTSTRAP_EMAIL=${DEPLOY_TENANT_EMAIL} TENANT_BOOTSTRAP_EMAIL=${DEPLOY_TENANT_EMAIL}
TENANT_BOOTSTRAP_USERNAME=${DEPLOY_TENANT_EMAIL} TENANT_BOOTSTRAP_USERNAME=${DEPLOY_TENANT_USERNAME}
TENANT_BOOTSTRAP_PASSWORD=already-migrated-do-not-use TENANT_BOOTSTRAP_PASSWORD=already-migrated-do-not-use
TENANT_BOOTSTRAP_APP_KEY=ak_private_default TENANT_BOOTSTRAP_APP_KEY=ak_private_default
EOF BSENV
chmod 600 "$ROOT_DIR/config/tenant/bootstrap.env" chmod 600 "$ROOT_DIR/config/tenant/bootstrap.env"
ok "bootstrap.env 已更新(迁移后)" ok "bootstrap.env 已更新(迁移后)"
unset _MIGRATE_KEY _EXPORT_BODY _EXPORT_RESP _EXPORT_DATA _IMPORT_RESP _TENANT_INFO
fi fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -857,7 +850,7 @@ fi
# 最终汇总 # 最终汇总
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
printf '\n\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf '\n\033[1;35m══════════════════════════════════════════════════\033[0m\n'
printf '\033[1;35m %s 私有化部署完成\033[0m\n' "$CUSTOMER_NAME" printf '\033[1;35m XuqmGroup 私有化部署完成\033[0m\n'
printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n'
printf '\n \033[1m访问地址\033[0m %s\n' "${CONSOLE_BASE}" printf '\n \033[1m访问地址\033[0m %s\n' "${CONSOLE_BASE}"
printf ' \033[1m运营后台\033[0m %s/ops\n' "${CONSOLE_BASE}" printf ' \033[1m运营后台\033[0m %s/ops\n' "${CONSOLE_BASE}"

查看文件

@ -3,7 +3,7 @@
# #
# 用途: # 用途:
# 部署完成后,全面验证所有服务是否正常运行,输出通过/失败清单。 # 部署完成后,全面验证所有服务是否正常运行,输出通过/失败清单。
# 可独立运行,也可作为 deploy-szyx.sh 和 migrate-tenant.sh 的最终步骤。 # 可独立运行,也可作为 deploy.sh 的最终步骤。
# #
# 使用方法: # 使用方法:
# # 从部署目录读取配置自动推断 # # 从部署目录读取配置自动推断