diff --git a/VERSION b/VERSION index 45da1e2..1f73eec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026.05.20-private.4 +2026.05.20-private.5 diff --git a/scripts/update.sh b/scripts/update.sh index 47317e3..7b422e8 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -1,18 +1,24 @@ #!/usr/bin/env bash # update.sh — XuqmGroup 私有化部署热更新脚本 # -# 用途:在已部署的环境上执行,无需重新配置基础信息: -# 1. 自动读取现有配置(.env / config/secrets.env) -# 2. 修复已知配置问题(SDK 地址、残留公网 URL 等) -# 3. 可选:拉取最新镜像 -# 4. 重启受影响的容器 -# 5. 重新运行全量验证 +# 执行内容(顺序): +# 1. 检测并修正 CONSOLE_DOMAIN(裸 IP → 提示输入公网域名) +# 2. 修复 SDK URL(公网平台残留地址 / 内网 IP) +# 3. 同步 SDK 初始化配置文件 +# 4. 检测并自动修复宿主机 nginx WebSocket 代理头 +# 5. 可选拉取最新镜像 +# 6. 重启受影响容器 +# 7. 等待 tenant-service 健康 +# 8. 自动处理积压的 PENDING 服务开通申请 +# 9. 全量验证 # # 前提:已执行过 install.sh 完成初始部署 set -euo pipefail -# 自动定位安装目录:优先脚本所在目录的上级,否则搜索常见路径 +# --------------------------------------------------------------------------- +# 定位安装目录 +# --------------------------------------------------------------------------- _script_parent="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" if [ -f "$_script_parent/docker-compose.yml" ]; then ROOT_DIR="$_script_parent" @@ -43,9 +49,7 @@ info() { printf " → %s\n" "$*"; } # 修改 env 文件中某个 key 的值;若 key 不存在则追加 _set_env() { local file="$1" key="$2" val="$3" - local tmp; tmp="$(mktemp)" if grep -q "^${key}=" "$file" 2>/dev/null; then - # 用 python3 替换,避免 sed 在 URL 值中的斜杠转义问题 python3 - "$file" "$key" "$val" <<'PY' import sys, re path, key, val = sys.argv[1], sys.argv[2], sys.argv[3] @@ -56,38 +60,52 @@ PY else printf '%s=%s\n' "$key" "$val" >> "$file" fi - rm -f "$tmp" +} + +# 判断一个 http/https URL 是否需要修复(空 / xuqinmin.com 残留 / 裸 IP) +_url_needs_fix() { + local val="$1" + [ -z "$val" ] && return 0 + printf '%s' "$val" | grep -qi 'xuqinmin\.com' && return 0 + local host + host="$(printf '%s' "$val" | sed 's|https\?://||; s|/.*||; s|:.*||')" + printf '%s' "$host" | grep -qE \ + '^([0-9]{1,3}\.){3}[0-9]{1,3}$' && return 0 + return 1 } # --------------------------------------------------------------------------- -# 前置检查 +# Banner # --------------------------------------------------------------------------- printf "\n${BOLD}══════════════════════════════════════════════════${RESET}\n" printf "${BOLD} XuqmGroup 私有化部署热更新${RESET}\n" printf "${BOLD}══════════════════════════════════════════════════${RESET}\n" printf " 部署目录: %s\n\n" "$ROOT_DIR" -[ -f "$ROOT_DIR/.env" ] || fail "未找到 .env,请先执行 install.sh 完成初始部署" -[ -f "$ROOT_DIR/config/xuqm.env" ] || fail "未找到 config/xuqm.env,请先执行 install.sh 完成初始部署" +# --------------------------------------------------------------------------- +# 前置检查 +# --------------------------------------------------------------------------- +[ -f "$ROOT_DIR/.env" ] || fail "未找到 .env,请先执行 install.sh 完成初始部署" +[ -f "$ROOT_DIR/config/xuqm.env" ] || fail "未找到 config/xuqm.env,请先执行 install.sh 完成初始部署" [ -f "$ROOT_DIR/config/secrets.env" ] || fail "未找到 config/secrets.env,请先执行 install.sh 完成初始部署" -command -v docker >/dev/null 2>&1 || fail "Docker 未安装" -command -v python3 >/dev/null 2>&1 || fail "python3 未安装" -docker info >/dev/null 2>&1 || fail "Docker daemon 未运行" +command -v docker >/dev/null 2>&1 || fail "Docker 未安装" +command -v python3 >/dev/null 2>&1 || fail "python3 未安装" +docker info >/dev/null 2>&1 || fail "Docker daemon 未运行,请执行: systemctl start docker" # --------------------------------------------------------------------------- # 加载现有配置 # --------------------------------------------------------------------------- log "加载现有配置" -# shellcheck disable=SC1090,SC1091 set -a +# shellcheck disable=SC1090,SC1091 . "$ROOT_DIR/.env" . "$ROOT_DIR/config/secrets.env" set +a -# 从 config/xuqm.env 中读取 CONSOLE_DOMAIN(不用 source,避免覆盖已加载值) -_CONSOLE_DOMAIN="$(grep '^CONSOLE_DOMAIN=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")" +_CONSOLE_DOMAIN="$(grep '^CONSOLE_DOMAIN=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null \ + | cut -d= -f2- | tr -d '"' | tr -d "'")" _NGINX_BIND="${NGINX_BIND:-80}" _NGINX_PORT="${_NGINX_BIND##*:}" @@ -96,45 +114,49 @@ ok "NGINX_BIND=${_NGINX_BIND}" [ -n "$_CONSOLE_DOMAIN" ] || fail "config/xuqm.env 中 CONSOLE_DOMAIN 未设置,请手动补充后重试" -# CONSOLE_DOMAIN 若为裸 IP(内网/公网)则强制要求更新为域名,否则 SDK URL 无法从外部访问 -_domain_stripped="$(printf '%s' "$_CONSOLE_DOMAIN" | sed 's|https\?://||' | cut -d: -f1)" -if printf '%s' "$_domain_stripped" | grep -qE \ - '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)'; then - warn "CONSOLE_DOMAIN 当前为 IP 地址(${_CONSOLE_DOMAIN}),客户端浏览器可能无法访问" - printf ' 外部访问域名应为公网域名(如 https://console.example.com)\n' - read -rp " 请输入正确的外部访问地址(直接回车保持不变): " _new_domain +# --------------------------------------------------------------------------- +# Step 1 — 检测 CONSOLE_DOMAIN 是否为裸 IP,提示更正 +# --------------------------------------------------------------------------- +log "检查 CONSOLE_DOMAIN" + +_domain_host="$(printf '%s' "$_CONSOLE_DOMAIN" | sed 's|https\?://||; s|/.*||; s|:.*||')" +if printf '%s' "$_domain_host" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then + warn "CONSOLE_DOMAIN 当前为 IP 地址(${_CONSOLE_DOMAIN})" + printf ' SDK 配置会把 IM WebSocket 地址设成该 IP,外网客户端无法通过域名 TLS 连接。\n' + printf ' 请输入对外访问的公网域名(含协议,如 https://console.example.com)\n' + read -rp " 新的 CONSOLE_DOMAIN(直接回车保持原值): " _new_domain if [ -n "$_new_domain" ]; then _set_env "$ROOT_DIR/config/xuqm.env" "CONSOLE_DOMAIN" "$_new_domain" _CONSOLE_DOMAIN="$_new_domain" ok "CONSOLE_DOMAIN 已更新 → ${_CONSOLE_DOMAIN}" else - warn "保持原值 ${_CONSOLE_DOMAIN},如外部无法访问请手动修改 config/xuqm.env 后重试" + warn "保持原值 ${_CONSOLE_DOMAIN},如 SDK 无法连接请修改 config/xuqm.env 后重试" fi +else + ok "CONSOLE_DOMAIN 正常: ${_CONSOLE_DOMAIN}" fi # --------------------------------------------------------------------------- -# 1. 修复配置问题 +# Step 2 — 修复 SDK URL # --------------------------------------------------------------------------- -log "检查并修复配置" +log "检查并修复 SDK URL" -# 根据 CONSOLE_DOMAIN 派生正确的 SDK 地址 if printf '%s' "$_CONSOLE_DOMAIN" | grep -q '^https://'; then _WS_SCHEME="wss" else _WS_SCHEME="ws" fi -_DEPLOY_HOST="$(printf '%s' "$_CONSOLE_DOMAIN" | sed 's|https\?://||')" +_DEPLOY_HOST="$(printf '%s' "$_CONSOLE_DOMAIN" | sed 's|https\?://||; s|/.*||')" _SDK_IM_WS_URL="${_WS_SCHEME}://${_DEPLOY_HOST}/ws/im" _SDK_IM_API_URL="${_CONSOLE_DOMAIN}" _SDK_FILE_URL="${_CONSOLE_DOMAIN}" _FIXED=0 -# 检查 SDK_IM_WS_URL(xuqinmin.com 残留 或 内网/裸 IP 均需修复) -_CURRENT_WS="$(grep '^SDK_IM_WS_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')" -# ws/wss URL 转为 http/https 格式后复用 _url_needs_fix -_CURRENT_WS_AS_HTTP="$(printf '%s' "$_CURRENT_WS" | sed 's|^wss://|https://|; s|^ws://|http://|')" -if _url_needs_fix "$_CURRENT_WS_AS_HTTP"; then +# SDK_IM_WS_URL:ws/wss → 转成 http/https 格式后复用 _url_needs_fix +_CURRENT_WS="$(grep '^SDK_IM_WS_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || true)" +_CURRENT_WS_HTTP="$(printf '%s' "$_CURRENT_WS" | sed 's|^wss://|https://|; s|^ws://|http://|')" +if _url_needs_fix "$_CURRENT_WS_HTTP"; then _set_env "$ROOT_DIR/config/xuqm.env" "SDK_IM_WS_URL" "$_SDK_IM_WS_URL" ok "SDK_IM_WS_URL 已更新 → ${_SDK_IM_WS_URL}" _FIXED=1 @@ -142,20 +164,7 @@ else ok "SDK_IM_WS_URL 正常: ${_CURRENT_WS}" fi -# 辅助函数:检查一个 URL 是否需要修复(空、xuqinmin.com 残留、或裸 IP) -_url_needs_fix() { - local val="$1" - local host - host="$(printf '%s' "$val" | sed 's|https\?://||' | cut -d/ -f1 | cut -d: -f1)" - [ -z "$val" ] && return 0 - printf '%s' "$val" | grep -qi 'xuqinmin\.com' && return 0 - printf '%s' "$host" | grep -qE \ - '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)' && return 0 - return 1 -} - -# 检查 SDK_IM_API_URL -_CURRENT_IM_API="$(grep '^SDK_IM_API_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')" +_CURRENT_IM_API="$(grep '^SDK_IM_API_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || true)" if _url_needs_fix "$_CURRENT_IM_API"; then _set_env "$ROOT_DIR/config/xuqm.env" "SDK_IM_API_URL" "$_SDK_IM_API_URL" ok "SDK_IM_API_URL 已更新 → ${_SDK_IM_API_URL}" @@ -164,8 +173,7 @@ else ok "SDK_IM_API_URL 正常: ${_CURRENT_IM_API}" fi -# 检查 SDK_FILE_SERVICE_URL -_CURRENT_FILE="$(grep '^SDK_FILE_SERVICE_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')" +_CURRENT_FILE="$(grep '^SDK_FILE_SERVICE_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || true)" if _url_needs_fix "$_CURRENT_FILE"; then _set_env "$ROOT_DIR/config/xuqm.env" "SDK_FILE_SERVICE_URL" "$_SDK_FILE_URL" ok "SDK_FILE_SERVICE_URL 已更新 → ${_SDK_FILE_URL}" @@ -174,32 +182,31 @@ else ok "SDK_FILE_SERVICE_URL 正常: ${_CURRENT_FILE}" fi -# 检查 .env 中是否有残留 OPS_DOMAIN +# 清理 .env 中残留的 OPS_DOMAIN if grep -q '^OPS_DOMAIN=' "$ROOT_DIR/.env" 2>/dev/null; then python3 - "$ROOT_DIR/.env" <<'PY' import sys, re -path = sys.argv[1] -content = open(path).read() +content = open(sys.argv[1]).read() content = re.sub(r'^OPS_DOMAIN=.*\n?', '', content, flags=re.MULTILINE) -open(path, 'w').write(content) +open(sys.argv[1], 'w').write(content) PY ok ".env 中已清理 OPS_DOMAIN" _FIXED=1 fi -# 扫描 config/xuqm.env 是否还有其它公网地址残留 +# 扫描是否还有其他 xuqinmin.com 残留 if grep -qi 'xuqinmin\.com' "$ROOT_DIR/config/xuqm.env" 2>/dev/null; then warn "config/xuqm.env 中仍有 xuqinmin.com 字样,请人工核查:" - grep -n 'xuqinmin\.com' "$ROOT_DIR/config/xuqm.env" | while IFS= read -r line; do - printf ' %s\n' "$line" + grep -n 'xuqinmin\.com' "$ROOT_DIR/config/xuqm.env" | while IFS= read -r _line; do + printf ' %s\n' "$_line" done fi -if [ "$_FIXED" -eq 0 ]; then - ok "配置无需修复" -fi +[ "$_FIXED" -eq 0 ] && ok "SDK 配置无需修复" -# 同步更新 config/sdk/xuqm-private-sdk.json(SDK 初始化配置文件) +# --------------------------------------------------------------------------- +# Step 3 — 同步 SDK 初始化配置文件 +# --------------------------------------------------------------------------- if [ -f "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" ]; then python3 - "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" \ "$_CONSOLE_DOMAIN" "$_SDK_IM_WS_URL" <<'PY' @@ -215,17 +222,192 @@ try: except Exception as e: print(f'warning: {e}', file=sys.stderr) PY - ok "config/sdk/xuqm-private-sdk.json 已同步更新" + ok "config/sdk/xuqm-private-sdk.json 已同步" fi # --------------------------------------------------------------------------- -# 2. 可选:拉取最新镜像 +# Step 4 — 检测并修复宿主机 nginx WebSocket 代理头 +# --------------------------------------------------------------------------- +log "检查宿主机 nginx WebSocket 配置" + +if ! command -v nginx >/dev/null 2>&1; then + info "宿主机未安装 nginx,跳过(流量直接进入容器 nginx)" +else + _nginx_result="$(python3 - "$_NGINX_PORT" <<'PYEOF' +import sys, os, re, shutil, subprocess + +port = sys.argv[1] + +# 获取 nginx 完整配置 +try: + r = subprocess.run(['nginx', '-T'], capture_output=True, text=True, timeout=15) + dump = r.stdout +except Exception as e: + print(f'SKIP:nginx -T 失败: {e}') + sys.exit(0) + +# 解析 nginx -T 输出,按文件分组 +file_contents = {} +cur_file = None +cur_lines = [] +for line in dump.splitlines(): + if line.startswith('# configuration file '): + if cur_file: + file_contents[cur_file] = '\n'.join(cur_lines) + cur_file = line.removeprefix('# configuration file ').rstrip(':') + cur_lines = [] + else: + cur_lines.append(line) +if cur_file: + file_contents[cur_file] = '\n'.join(cur_lines) + +proxy_re = re.compile( + r'proxy_pass\s+https?://(?:127\.0\.0\.1|localhost):' + re.escape(port) + r'\s*;', + re.IGNORECASE +) + +def has_ws_headers(text): + return (re.search(r'proxy_http_version\s+1\.1', text) and + re.search(r'proxy_set_header\s+Upgrade', text, re.IGNORECASE) and + re.search(r'proxy_set_header\s+Connection.*upgrade', text, re.IGNORECASE)) + +WS_INJECT = ( + '\n proxy_http_version 1.1;' + '\n proxy_set_header Upgrade $http_upgrade;' + '\n proxy_set_header Connection "upgrade";' + '\n proxy_read_timeout 3600s;' +) + +def find_location_blocks(text): + """返回 (start, end) 列表,每个 location { ... } 块的范围(支持嵌套)""" + results = [] + i = 0 + while i < len(text): + m = re.search(r'\blocation\b[^{]*\{', text[i:]) + if not m: + break + abs_start = i + m.start() + depth = 1 + j = i + m.end() + while j < len(text) and depth: + if text[j] == '{': depth += 1 + elif text[j] == '}': depth -= 1 + j += 1 + results.append((abs_start, j)) + i = i + m.end() + return results + +files_fixed = [] +files_checked = [] + +for fpath, content in file_contents.items(): + if not os.path.isfile(fpath): + continue + if not proxy_re.search(content): + continue + files_checked.append(fpath) + + with open(fpath) as f: + real = f.read() + + blocks = find_location_blocks(real) + changed = False + offset = 0 + + for start, end in blocks: + block = real[start + offset: end + offset] + if not proxy_re.search(block): + continue + if has_ws_headers(block): + continue + # 在 proxy_pass 行后注入 WebSocket 头 + patched = re.sub( + r'(proxy_pass\s+[^;]+;)', + r'\1' + WS_INJECT, + block, + count=1 + ) + real = real[:start + offset] + patched + real[end + offset:] + offset += len(patched) - len(block) + changed = True + + if not changed: + continue + + backup = fpath + '.xuqm.bak' + shutil.copy2(fpath, backup) + try: + with open(fpath, 'w') as f: + f.write(real) + except Exception as e: + shutil.copy2(backup, fpath) + print(f'WARN:写入 {fpath} 失败: {e}') + continue + + # 验证语法 + r2 = subprocess.run(['nginx', '-t'], capture_output=True, text=True) + if r2.returncode != 0: + shutil.copy2(backup, fpath) + print(f'WARN:nginx -t 失败,已回滚 {fpath}') + print(r2.stderr.strip()) + else: + files_fixed.append(fpath) + +if files_fixed: + print('FIXED:' + ','.join(files_fixed)) +elif files_checked: + print('OK:WebSocket 头已存在') +else: + print('NONE:未发现代理到容器 nginx 的配置') +PYEOF +)" + + case "${_nginx_result%%:*}" in + FIXED) + _fixed_files="${_nginx_result#FIXED:}" + ok "宿主机 nginx 已自动补全 WebSocket 代理头" + for _f in $(printf '%s' "$_fixed_files" | tr ',' '\n'); do + info "已修改: ${_f}(原文件备份为 ${_f}.xuqm.bak)" + done + info "正在重载宿主机 nginx ..." + nginx -s reload 2>/dev/null && ok "nginx reload 成功" || warn "nginx reload 失败,请手动执行: nginx -s reload" + ;; + OK) + ok "宿主机 nginx WebSocket 头已存在,无需修改" + ;; + NONE) + info "未发现代理到容器 nginx(端口 ${_NGINX_PORT})的配置,如有上层代理请手动确认:" + printf '\n' + printf ' location / {\n' + printf ' proxy_pass http://<本机IP>:%s;\n' "$_NGINX_PORT" + printf ' proxy_http_version 1.1;\n' + printf ' proxy_set_header Upgrade $http_upgrade;\n' + printf ' proxy_set_header Connection "upgrade";\n' + printf ' proxy_set_header Host $host;\n' + printf ' proxy_set_header X-Real-IP $remote_addr;\n' + printf ' proxy_set_header X-Forwarded-Proto $scheme;\n' + printf ' proxy_read_timeout 3600s;\n' + printf ' }\n\n' + ;; + SKIP) + warn "宿主机 nginx 检查跳过: ${_nginx_result#SKIP:}" + ;; + WARN*) + warn "${_nginx_result#WARN:}" + ;; + *) + warn "宿主机 nginx 检查返回未知结果,请手动核查 WebSocket 头配置" + ;; + esac +fi + +# --------------------------------------------------------------------------- +# Step 5 — 可选拉取最新镜像 # --------------------------------------------------------------------------- log "拉取最新镜像(可选)" read -rp " 是否拉取最新镜像?(y/N): " _pull_choice if [ "${_pull_choice:-n}" = "y" ] || [ "${_pull_choice:-n}" = "Y" ]; then - _REGISTRY="${REGISTRY:-}" _REGISTRY_HOST="${REGISTRY_HOST:-}" _REGISTRY_USER="${REGISTRY_USER:-}" _REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-}" @@ -234,7 +416,7 @@ if [ "${_pull_choice:-n}" = "y" ] || [ "${_pull_choice:-n}" = "Y" ]; then printf '%s' "$_REGISTRY_PASSWORD" | \ docker login "$_REGISTRY_HOST" -u "$_REGISTRY_USER" --password-stdin 2>/dev/null \ && ok "镜像仓库登录成功" \ - || warn "镜像仓库登录失败,将使用本地缓存镜像" + || warn "镜像仓库登录失败,将尝试使用本地缓存" fi docker compose \ @@ -242,73 +424,80 @@ if [ "${_pull_choice:-n}" = "y" ] || [ "${_pull_choice:-n}" = "Y" ]; then -f "$ROOT_DIR/docker-compose.yml" \ -f "$ROOT_DIR/docker-compose.infra.yml" \ pull --ignore-pull-failures 2>/dev/null \ - && ok "镜像已拉取" \ - || warn "部分镜像拉取失败,继续使用本地缓存" + && ok "镜像已更新" \ + || warn "部分镜像拉取失败,将使用本地缓存继续" else info "跳过镜像拉取" fi # --------------------------------------------------------------------------- -# 3. 重启受影响的容器 +# Step 6 — 重启受影响的容器 # --------------------------------------------------------------------------- log "重启受影响的容器" -# 加载 secrets 到环境(docker compose 需要) +# 重新加载 secrets(docker compose 需要环境变量) set -a # shellcheck disable=SC1090 . "$ROOT_DIR/config/secrets.env" set +a -_COMPOSE="docker compose --env-file ${ROOT_DIR}/.env -f ${ROOT_DIR}/docker-compose.yml -f ${ROOT_DIR}/docker-compose.infra.yml" +_COMPOSE="docker compose --env-file ${ROOT_DIR}/.env \ + -f ${ROOT_DIR}/docker-compose.yml \ + -f ${ROOT_DIR}/docker-compose.infra.yml" -# tenant-service:读取 env_file(包含 SDK_IM_WS_URL),必须重启 info "重启 tenant-service ..." $_COMPOSE restart tenant-service ok "tenant-service 已重启" -# nginx:挂载 config/nginx/conf.d(可能有 sub_filter 更新),重启生效 info "重启 nginx ..." $_COMPOSE restart nginx ok "nginx 已重启" -# 等待 tenant-service 健康 +# --------------------------------------------------------------------------- +# Step 7 — 等待 tenant-service 健康 +# --------------------------------------------------------------------------- printf ' 等待 tenant-service 就绪' -for i in $(seq 1 30); do - code="$(curl -skL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 4 \ +_healthy=0 +for i in $(seq 1 40); do + _code="$(curl -skL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 4 \ "http://127.0.0.1:11224/actuator/health" 2>/dev/null || echo 000)" - if [ "$code" = "200" ]; then + if [ "$_code" = "200" ]; then printf '\n' ok "tenant-service 健康 (HTTP 200)" + _healthy=1 break fi printf '.' sleep 3 - [ "$i" -eq 30 ] && { - printf '\n' - warn "tenant-service 未在 90s 内响应,请手动检查:docker compose logs --tail 50 tenant-service" - break - } done - -# --------------------------------------------------------------------------- -# 4. 自动处理积压的 PENDING 服务开通申请 -# --------------------------------------------------------------------------- -log "处理积压的服务开通申请" - -_approve_resp="$(curl -sk --noproxy '*' -X POST \ - "http://127.0.0.1:11224/api/private/admin/approve-pending-requests" \ - --max-time 10 2>/dev/null || echo '')" -_approved="$(printf '%s' "$_approve_resp" | \ - python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('approved',0))" \ - 2>/dev/null || echo 0)" -if [ "${_approved:-0}" -gt 0 ]; then - ok "已自动开通 ${_approved} 条积压申请" -else - ok "无积压申请" +if [ "$_healthy" -eq 0 ]; then + printf '\n' + warn "tenant-service 在 120s 内未响应,请检查: docker compose logs --tail 60 tenant-service" fi # --------------------------------------------------------------------------- -# 5. 全量验证 +# Step 8 — 自动处理积压的 PENDING 服务开通申请 +# --------------------------------------------------------------------------- +log "处理积压的服务开通申请" + +if [ "$_healthy" -eq 1 ]; then + _approve_resp="$(curl -sk --noproxy '*' -X POST \ + "http://127.0.0.1:11224/api/private/admin/approve-pending-requests" \ + --max-time 10 2>/dev/null || true)" + _approved="$(printf '%s' "$_approve_resp" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('approved',0))" \ + 2>/dev/null || echo 0)" + if [ "${_approved:-0}" -gt 0 ]; then + ok "已自动开通 ${_approved} 条积压申请" + else + ok "无积压申请" + fi +else + warn "tenant-service 未健康,跳过积压申请处理" +fi + +# --------------------------------------------------------------------------- +# Step 9 — 全量验证 # --------------------------------------------------------------------------- log "运行全量验证" @@ -325,4 +514,4 @@ printf "\n${BOLD}═════════════════════ printf "${BOLD} 热更新完成${RESET}\n" printf "${BOLD}══════════════════════════════════════════════════${RESET}\n" printf "\n 访问地址:${BOLD}%s${RESET}\n" "$_CONSOLE_DOMAIN" -printf " SDK IM WS:${BOLD}%s${RESET}\n\n" "$_SDK_IM_WS_URL" +printf " IM WS :${BOLD}%s${RESET}\n\n" "$_SDK_IM_WS_URL"