deploy_tongweb_708.sh

用途

部署tongweb708和配置各个用户的密码,信任ip

流程图

📋 TongWeb 7.0.8 自动部署流程
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: 检查旧安装                                               │
│ 检查 TARGET_DIR 目录是否已存在                                   │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 已存在            │               │ 不存在            │
 │                   │               │                   │
 │ ⚠️ 警告提示       │               │ 继续 Step 2       │
 │ 退出脚本          │               │                   │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: 检查 JDK 环境                                            │
│ java -version 检测版本 (要求 1.8-21)                             │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 版本不兼容        │               │ 版本兼容          │
 │ 或 Java 未找到    │               │                   │
 │                   │               │ 继续 Step 3       │
 │ ❌ 退出           │               │                   │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 检查文件属主一致性                                       │
│ 检查安装包/授权文件/脚本的属主是否一致                           │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 属主不一致        │               │ 属主一致          │
 │                   │               │                   │
 │ ❌ 退出           │               │ 继续 Step 4       │
 │ 提示 chown 修正   │               │                   │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: 配置 JDK 路径                                            │
│ 自动探测或手动输入 JDK 安装路径                                  │
│ 写入 JAVA_HOME.txt                                               │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 5: 解压安装包                                               │
│ tar -zxvf TongWeb7.0.8.909.tar.gz -C $TARGET_DIR                │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 解压失败          │               │ 解压成功          │
 │                   │               │                   │
 │ ❌ 退出           │               │ 继续 Step 6       │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 6: 部署授权文件                                             │
│ cp license.dat $TARGET_DIR/$INSTALL_NAME/                       │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 7: 端口冲突检查                                             │
│ netstat -tuln 检查 9060/8088 端口                                │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 端口被占用        │               │ 端口可用          │
 │                   │               │                   │
 │ 用户输入新端口    │               │ 继续 Step 8       │
 │ sed 替换配置      │               │                   │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 8: 配置信任 IP                                              │
│ 修改 console.xml 的 trustedIP 属性                               │
│ 支持单 IP/IP 段/范围/多个 IP                                     │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 9: 启动 TongWeb 服务                                        │
│ ./startd.sh                                                      │
│ 等待日志文件生成 (最长 5 分钟)                                     │
└─────────────────────────────────────────────────────────────────┘
 │
 ┌─────────────────┴─────────────────┐
 ▼                                   ▼
 ┌───────────────────┐               ┌───────────────────┐
 │ 启动失败          │               │ 启动成功          │
 │                   │               │                   │
 │ ❌ 退出           │               │ 继续 Step 10      │
 │ 检查日志          │               │                   │
 └───────────────────┘               └───────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 10: 修改管理员密码                                          │
│ commandstool.sh --model=password --action=update                │
│ 用户:thanos  原密码:thanos123.com                              │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 11: 修改其他用户密码                                        │
│ security/auditor/monitor 三个用户分别设置密码                    │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 12: 生成安装摘要                                            │
│ 输出 installation_message.txt                                    │
│ 包含:访问地址/账号密码/路径信息/启停指令                        │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
 ╔═══════════════════════════════════════════════════════════════╗
 ║                    🎉 部署完成                                 ║
 ║  管理控制台:https://IP:9060/console                          ║
 ║  管理员账号:thanos / 用户设置的密码                           ║
 ╚═══════════════════════════════════════════════════════════════╝

使用说明

将脚本跟安装包,license文件放入到同一目录下,如下图所示:
在这里插入图片描述
修改deploy_tongweb_708.sh,主要是截图所示的内容:

在这里插入图片描述
给该脚本赋予权限:

chmod +x deploy_tongweb_708.sh

之后直接执行该脚本:

./deploy_tongweb_708.sh

然后按照步骤一直输入所需信息和确认即可,安装后会输出txt,请妥善保存,里面有安装信息,账号密码和启停指令。
在这里插入图片描述

完整脚本内容如下(也可以下载本帖上的资源):

#!/bin/bash

# ================================================
#      TongWeb 7.0.8 自动部署程序 
#  ================================================
#使用说明:
  #安装前请配置好jdk环境变量,jdk版本建议在1.8-21之间
  #请确保部署脚本,产品授权,安装包均属于同一个用户,并且所在目录(包括父级目录和子级目录均属于同一个用户)
  #请确保有安装unzip指令,不然可能会安装失败
  #文件准备统一在同级目录下:
#/部署目录/
#├── deploy_TongWeb708.sh  部署脚本
#├── license.dat      产品授权
#└── TongWeb7.0.8.905.tar.gz  安装包
#执行权限:chmod +x  deploy_TongWeb708.sh
#运行部署:sudo ./ deploy_TongWeb708.sh
  #安装成功后,会同步输出一个安装摘要到安装目录下,并提示该文件所在路径和名称,建议保留该文件,方便后续使用。


# 配置参数
TARGET_DIR="/opt/tongweb708test"
INSTALL_PACKAGE="TongWeb7.0.8.909.tar.gz"
LICENSE_FILE="license.dat"
INSTALL_NAME=$(basename "$INSTALL_PACKAGE" .tar.gz)
LOG_DIR="${TARGET_DIR}/${INSTALL_NAME}/domains/domain1/logs/server"
LOG_FILE="${LOG_DIR}/server.log"

# 全局变量 - 每个用户独立密码
THANOS_PASSWORD=""
SECURITY_PASSWORD=""
AUDITOR_PASSWORD=""
MONITOR_PASSWORD=""
ADMIN_PORT=9060  # 默认管理端口
CURRENT_USER=$(whoami)

# 检查旧安装
check_old_installation() {
    if [ -d "${TARGET_DIR}/${INSTALL_NAME}" ]; then
        echo "⚠️ 警告:检测到已存在的TongWeb安装"
        echo "安装目录:${TARGET_DIR}/${INSTALL_NAME}"
        echo "安全建议:修改本脚本安装路径"
        echo "修改本脚本安装路径:"
        echo "  编辑 TARGET_DIR 变量的值"
        echo "本次安装将退出,请修改后重新执行安装脚本"
        exit 1
    fi
    return 0
}

# 检查JDK环境
check_jdk() {
    echo "检查JDK环境..."
    
    if command -v java &>/dev/null; then
        java_version=$(java -version 2>&1 | head -n 1 | awk -F'"' '{print $2}')
        major_version=$(echo $java_version | cut -d'.' -f1)
        
        if [ "$major_version" = "1" ]; then
            minor_version=$(echo $java_version | cut -d'.' -f2)
            if [ "$minor_version" -lt 8 ]; then
                echo "❌ 错误:检测到不兼容的JDK版本"
                echo "TongWeb 7.0.8.9要求JDK 8-21版本"
                echo "当前版本: $java_version"
                return 1
            else
                echo "✅ JDK版本兼容: 1.$minor_version.x (JDK 8)"
            fi
        elif [ "$major_version" -lt 8 ] || [ "$major_version" -gt 21 ]; then
            echo "❌ 错误:检测到不兼容的JDK版本"
            echo "TongWeb 7.0.8.9要求JDK 8-21版本"
            echo "当前版本: $java_version"
            return 1
        else
            echo "✅ JDK版本兼容: $major_version"
        fi
    else
        echo "❌ Java命令未找到"
        return 1
    fi
    
    return 0
}

# 配置JAVA_HOME.txt
setup_java_home_txt() {
    local jdk_path="$1"
    local java_home_txt="${TARGET_DIR}/${INSTALL_NAME}/bin/JAVA_HOME.txt"
    
    echo "设置JAVA_HOME.txt: $java_home_txt"
    echo "$jdk_path" > "$java_home_txt"
    
    if [ $? -eq 0 ]; then
        echo "✅ 成功写入JAVA_HOME.txt"
    else
        echo "❌ 错误:写入JAVA_HOME.txt失败"
        return 1
    fi
    
    return 0
}

# 检查文件属主一致性
check_file_ownership() {
    echo "检查文件属主一致性..."
    
    # 检查安装包属主
    if [ ! -f "$INSTALL_PACKAGE" ]; then
        echo "❌ 错误:安装包 $INSTALL_PACKAGE 不存在"
        return 1
    fi
    pkg_owner=$(stat -c "%U" "$INSTALL_PACKAGE")
    if [ "$pkg_owner" != "$CURRENT_USER" ]; then
        echo "❌ 错误:安装包属主不一致"
        echo "安装包: $INSTALL_PACKAGE"
        echo "当前用户: $CURRENT_USER"
        echo "文件属主: $pkg_owner"
        echo "请使用正确用户执行脚本或修改文件属主:"
        echo "  sudo chown $CURRENT_USER $INSTALL_PACKAGE"
        return 1
    fi
    
    # 检查授权文件属主
    if [ ! -f "$LICENSE_FILE" ]; then
        echo "❌ 错误:授权文件 $LICENSE_FILE 不存在"
        return 1
    fi
    license_owner=$(stat -c "%U" "$LICENSE_FILE")
    if [ "$license_owner" != "$CURRENT_USER" ]; then
        echo "❌ 错误:授权文件属主不一致"
        echo "授权文件: $LICENSE_FILE"
        echo "当前用户: $CURRENT_USER"
        echo "文件属主: $license_owner"
        echo "请使用正确用户执行脚本或修改文件属主:"
        echo "  sudo chown $CURRENT_USER $LICENSE_FILE"
        return 1
    fi
    
    # 检查脚本自身属主
    script_owner=$(stat -c "%U" "$0")
    if [ "$script_owner" != "$CURRENT_USER" ]; then
        echo "❌ 错误:脚本属主不一致"
        echo "脚本文件: $0"
        echo "当前用户: $CURRENT_USER"
        echo "文件属主: $script_owner"
        echo "请使用正确用户执行脚本或修改文件属主:"
        echo "  sudo chown $CURRENT_USER $0"
        return 1
    fi
    
    echo "✅ 所有文件属主一致: $CURRENT_USER"
    return 0
}

# 端口冲突检查(已移除7200端口)
check_port_conflict() {
    echo "检查端口冲突..."
    local ports=(9060 8088) # 控制台端口、应用端口
    local config_file="${TARGET_DIR}/${INSTALL_NAME}/domains/domain1/conf/tongweb.xml"
    
    for port in "${ports[@]}"; do
        if netstat -tuln | grep -q ":${port} "; then
            echo "❌ 端口冲突:端口 $port 已被占用!"
            read -p "请输入替换端口号: " new_port
            # 验证端口范围是否符合规范
            if ! [[ $new_port =~ ^[0-9]+$ ]] || [ $new_port -lt 1024 ] || [ $new_port -gt 65535 ]; then
                echo "❌ 错误:无效端口号!端口必须为1024-65535范围内的未占用端口" >&2
                echo "已退出安装,请确认端口后重新执行脚本"
                exit 1
            fi
            # 检查新端口是否已被占用
            if netstat -tuln | grep -q ":${new_port} "; then
                echo "❌ 错误:端口 $new_port 已被占用,请选择其他端口" >&2
                echo "已退出安装,请确认端口后重新执行脚本"
                exit 1
            fi
            
            # 替换配置文件中的端口
            sed -i "s/$port/$new_port/g" "$config_file"
            echo "✅ 已将配置文件中的端口 $port 替换为 $new_port"
            
            # 如果是管理端口,更新全局变量
            if [ "$port" == "9060" ]; then
                ADMIN_PORT=$new_port
            fi
        fi
    done
    return 0
}

# 安装流程
install_tongweb() {
    # 解压安装包
    echo "解压安装包到 ${TARGET_DIR}..."
    tar -zxvf "$INSTALL_PACKAGE" -C "$TARGET_DIR"
    if [ $? -ne 0 ]; then
        echo "❌ 解压失败"
        return 1
    fi
    
    # 验证解压目录
    if [ ! -d "${TARGET_DIR}/${INSTALL_NAME}" ]; then
        echo "❌ 错误:安装包解压后未创建预期目录 ${TARGET_DIR}/${INSTALL_NAME}"
        echo "请检查安装包内容:"
        tar -ztvf "$INSTALL_PACKAGE" | head
        echo "或者修改脚本开头的TARGET_DIR参数,本次即将退出"
        return 1
    fi
    
    # 部署授权文件
    echo "部署授权文件到 ${TARGET_DIR}/${INSTALL_NAME}..."
    cp -v "$LICENSE_FILE" "${TARGET_DIR}/${INSTALL_NAME}/"
    if [ $? -ne 0 ]; then
        echo "❌ 授权文件部署失败"
        return 1
    fi
    
    # 设置执行权限
    #echo "设置执行权限..."
    #chmod -R 755 "${TARGET_DIR}/${INSTALL_NAME}/bin"
    
    return 0
}

# 配置信任IP
configure_trusted_ip() {
    echo ""
    echo "=== 配置信任IP ==="
    echo "请设置允许访问TongWeb控制台的信任IP"
    echo "格式说明:"
    echo "1. 单个IP: 192.168.1.100"
    echo "2. IP段: 192.168.1.*"
    echo "3. IP范围: 192.168.1.50-192.168.1.100"
    echo "4. 多个IP用逗号分隔: 192.168.1.100,10.0.0.50"
    echo "5. 输入 '*' 表示信任所有IP(不推荐)"
    echo ""
    
    read -p "请输入信任IP(直接回车使用默认值 '*'): " trusted_ip
    
    # 设置默认值
    if [ -z "$trusted_ip" ]; then
        trusted_ip="*"
        echo "⚠️ 警告:已设置为信任所有IP,存在安全风险"
    fi
    
    # 更新配置文件
    local console_xml="${TARGET_DIR}/${INSTALL_NAME}/domains/domain1/conf/console.xml"
    if [ -f "$console_xml" ]; then
        # 检查是否已有trustedIP属性
        if grep -q "trustedIP=" "$console_xml"; then
            sed -i "s/trustedIP=\"[^\"]*\"/trustedIP=\"$trusted_ip\"/" "$console_xml"
        else
            sed -i "s/<console /<console trustedIP=\"$trusted_ip\" /" "$console_xml"
        fi
        echo "✅ 已更新信任IP配置: $trusted_ip"
    else
        echo "❌ 错误:未找到console.xml配置文件"
        return 1
    fi
    
    return 0
}

# 启动服务
start_server() {
    echo "启动TongWeb服务..."
    cd "${TARGET_DIR}/${INSTALL_NAME}/bin" || { echo "❌ 无法进入bin目录"; return 1; }
    
    ./startd.sh
    
    # 等待日志文件生成(增加到5分钟)
    local timeout=300
    local start_time=$(date +%s)
    local log_found=false
    
    echo "等待服务初始化(最长等待5分钟)..."
    while [ $(($(date +%s) - start_time)) -lt $timeout ]; do
        if [ -f "$LOG_FILE" ]; then
            log_found=true
            break
        fi
        sleep 10
        elapsed=$(($(date +%s) - start_time))
        echo "已等待 ${elapsed} 秒..."
    done
    
    if ! $log_found; then
        echo ""
        echo "❌ 错误:未能在 ${timeout} 秒内检测到日志文件"
        echo "可能原因:"
        echo "1. 服务启动时间过长"
        echo "2. JDK环境配置问题"
        echo "3. 端口冲突(默认管理端口9060)"
        echo ""
        
        # 检查进程状态
        echo "正在检查TongWeb进程状态..."
        tongweb_process=$(ps -ef | grep "tongweb" | grep -v "grep")
        
        if [ -n "$tongweb_process" ]; then
            echo "✅ TongWeb进程已运行:"
            echo "$tongweb_process"
            echo "说明:请检查日志配置是否正常加载"
        else
            echo "❌ 未检测到TongWeb进程"
            echo "请检查以下日志获取详细信息:"
            echo "tail -f ${TARGET_DIR}/${INSTALL_NAME}/logs/server.log"
        fi
        return 1
    fi
    
    # 检查启动状态
    echo ""
    if tail -n 50 "$LOG_FILE" | grep -q "Server startup in"; then
        echo "✅ TongWeb 启动成功"
    else
        echo "⚠️ 警告:启动日志未检测到成功标识"
        echo "请检查日志文件获取详细信息:$LOG_FILE"
    fi
    
    return 0
}

# 修改管理员密码 - 优化版
change_admin_password() {
    echo ""
    echo "=== 修改管理员密码 ==="
    echo "根据安全要求,您需要修改默认管理员密码"
    echo ""
    echo "密码要求:"
    echo "1. 长度必须在10-20个字符之间"
    echo "2. 不能包含单引号(')或双引号(\")"
    echo "3. 不能使用最近5次使用过的旧密码"
    echo "4. 建议包含大小写字母、数字和特殊字符"
    echo ""
    
    # 设置默认密码
    DEFAULT_PASSWORD="thanos123.com"
    
    # 获取新密码
    while true; do
        read -s -p "请输入管理员(thanos)的新密码: " THANOS_PASSWORD
        echo
        read -s -p "请确认管理员(thanos)的新密码: " confirm
        echo
        
        # 检查密码长度
        if [ ${#THANOS_PASSWORD} -lt 10 ] || [ ${#THANOS_PASSWORD} -gt 20 ]; then
            echo "❌ 错误:密码长度必须在10-20个字符之间"
            continue
        fi
        
        # 检查密码是否包含引号
        if [[ "$THANOS_PASSWORD" == *"'"* ]] || [[ "$THANOS_PASSWORD" == *"\""* ]]; then
            echo "❌ 错误:密码不能包含引号('或\")"
            continue
        fi
        
        # 检查两次输入是否一致
        if [ "$THANOS_PASSWORD" != "$confirm" ]; then
            echo "❌ 错误:两次输入的密码不一致"
            continue
        fi
        
        # 检查是否与旧密码相同
        if [ "$THANOS_PASSWORD" == "$DEFAULT_PASSWORD" ]; then
            echo "❌ 错误:新密码不能与默认密码相同"
            continue
        fi
        
        break
    done
    
    echo "正在修改管理员密码..."
    
    # 执行密码修改命令 - 添加端口参数
    cd "${TARGET_DIR}/${INSTALL_NAME}/bin" || { echo "❌ 无法进入bin目录"; return; }
    
    ./commandstool.sh --model=password --action=update \
         --username=thanos --password="$DEFAULT_PASSWORD" \
         --port=$ADMIN_PORT \
         --acceptAgreement=true \
         originalPassword="$DEFAULT_PASSWORD" \
         newPassword="$THANOS_PASSWORD" \
         confirmPassword="$THANOS_PASSWORD"
    
    # 检查命令执行结果
    if [ $? -eq 0 ]; then
        echo "✅ 管理员密码修改成功"
    else
        echo "⚠️ 管理员密码修改失败,请手动修改"
        echo "手动修改步骤:"
        echo "1.访问管理控制台: http://$(hostname -I | cut -d' ' -f1):${ADMIN_PORT}"
        echo "2. 使用用户名 thanos 和默认密码:thanos123.com 登录"
        echo "3. 进入安全管理 > 用户管理 > thanos"
        echo "4. 修改密码"
        echo ""
        echo "或执行以下命令修改:"
        echo "sudo -u ${CURRENT_USER} ${TARGET_DIR}/${INSTALL_NAME}/bin/commandstool.sh --model=password --action=update --username=thanos --port=${ADMIN_PORT} --acceptAgreement=true originalPassword=\"${DEFAULT_PASSWORD}\" newPassword=\"$THANOS_PASSWORD\" confirmPassword=\"$THANOS_PASSWORD\""
    fi
    return 0
}

# 修改其他用户密码
change_other_users_password() {
    echo ""
    echo "=== 修改其他用户密码 ==="
    
    # 用户列表
    users=("security" "auditor" "monitor")
    default_passwords=("security123.com" "auditor123.com" "monitor123.com")
    
    # 提示用户输入每个用户的新密码
    read -sp "请输入安全保密管理员(security)的新密码: " SECURITY_PASSWORD
    echo
    read -sp "请确认安全保密管理员(security)的新密码: " confirm
    echo
    if [ "$SECURITY_PASSWORD" != "$confirm" ]; then
        echo "❌ 错误:两次输入的密码不一致"
        return 1
    fi
    
    read -sp "请输入安全审计员(auditor)的新密码: " AUDITOR_PASSWORD
    echo
    read -sp "请确认安全审计员(auditor)的新密码: " confirm
    echo
    if [ "$AUDITOR_PASSWORD" != "$confirm" ]; then
        echo "❌ 错误:两次输入的密码不一致"
        return 1
    fi
    
    read -sp "请输入系统资源监视管理员(monitor)的新密码: " MONITOR_PASSWORD
    echo
    read -sp "请确认系统资源监视管理员(monitor)的新密码: " confirm
    echo
    if [ "$MONITOR_PASSWORD" != "$confirm" ]; then
        echo "❌ 错误:两次输入的密码不一致"
        return 1
    fi
    
    # 为每个用户设置独立密码
    passwords=("$SECURITY_PASSWORD" "$AUDITOR_PASSWORD" "$MONITOR_PASSWORD")
    
    for i in "${!users[@]}"; do
        echo "修改 ${users[$i]} 用户的密码..."
        cd "${TARGET_DIR}/${INSTALL_NAME}/bin" || { echo "❌ 无法进入bin目录"; return; }
        
        ./commandstool.sh --model=password --action=update \
             --username=${users[$i]} --password=${default_passwords[$i]} \
             --port=$ADMIN_PORT \
             --acceptAgreement=true \
             originalPassword="${default_passwords[$i]}" \
             newPassword="${passwords[$i]}" \
             confirmPassword="${passwords[$i]}"
        
        if [ $? -eq 0 ]; then
            echo "✅ ${users[$i]} 用户密码修改成功"
        else
            echo "⚠️ ${users[$i]} 用户密码修改失败,请手动修改"
            echo "手动修改步骤:"
            echo "1.访问管理控制台: https://$(hostname -I | cut -d' ' -f1):${ADMIN_PORT}"
            echo "2. 使用用户名 ${users[$i]} 和默认密码:${default_passwords[$i]} 登录"
            echo "3. 进入安全管理 > 用户管理 > ${users[$i]}"
            echo "4. 修改密码"
        fi
    done
    
    return 0
}

# --= 主流程 =--
echo " ================================================="
echo "      TongWeb 7.0.8.9 自动部署程序 (支持JDK 8-21)"
echo "  ================================================="

# 1. 检查旧安装
if ! check_old_installation; then
    exit 1
fi

# 2. 检查JDK兼容性
if ! check_jdk; then
    echo "❌ 错误:未检测到兼容的JDK环境"
    echo "解决方案:"
    echo "1. 安装JDK 8-21版本"
    echo "2. 创建JAVA_HOME.txt文件并配置JDK路径"
    echo "   在${TARGET_DIR}/${INSTALL_NAME}/bin目录创建JAVA_HOME.txt"
    echo "   写入JDK安装路径,例如:/usr/lib/jvm/jdk-11.0.15"
    exit 1
fi

# 3. 检查文件属主
if ! check_file_ownership; then
    exit 1
fi

# 4. JDK路径配置
echo ""
echo "=== JDK路径配置 ==="
echo "TongWeb需要Java环境运行"
echo "请提供JDK安装路径(该路径将被写入JAVA_HOME.txt)"
echo "示例: /usr/lib/jvm/jdk-1.8.0_371"
echo ""

# 尝试从环境变量获取
if [ -n "$JAVA_HOME" ]; then
    echo "✅ 检测到环境变量 JAVA_HOME:$JAVA_HOME"
    jdk_path="$JAVA_HOME"
else
    # 自动探测可能的JDK路径
    echo "正在尝试自动探测JDK安装路径..."
    possible_paths=(
        "/usr/lib/jvm"
        "/usr/java"
        "/opt/jdk"
        "/usr/local/jdk"
    )
    
    found=false
    for path in "${possible_paths[@]}"; do
        if [ -d "$path" ]; then
            # 查找第一个JDK目录
            for jdk_dir in $(ls -d "$path"/* 2>/dev/null); do
                if [ -d "$jdk_dir/bin" ] && [ -f "$jdk_dir/bin/java" ]; then
                    jdk_path="$jdk_dir"
                    found=true
                    echo "✅ 自动检测到JDK路径:$jdk_path"
                    break
                fi
            done
            if $found; then break; fi
        fi
    done
    
    if ! $found; then
        echo "⚠️ 未能自动检测到JDK安装路径"
        read -p "请输入JDK安装目录的完整路径: " jdk_path
    fi
fi

# 验证JDK路径有效性
if [ ! -d "$jdk_path" ] || [ ! -f "$jdk_path/bin/java" ]; then
    echo "❌ 错误:提供的JDK路径无效"
    echo "请确保提供的目录包含有效的JDK安装(有bin/java可执行文件)"
    exit 1
fi

# 5. 执行安装
echo ""
echo "=== 开始安装 TongWeb ==="
if ! install_tongweb; then
    exit 1
fi

# 6. 配置JAVA_HOME.txt
setup_java_home_txt "$jdk_path"

# 7. 端口冲突检查
check_port_conflict

# 8. 配置信任IP
configure_trusted_ip

# 9. 启动服务
start_server

# 10. 修改管理员密码
change_admin_password

# 11. 修改其他用户密码
change_other_users_password

# 12. 完成信息
echo ""
echo "🎉 安装已成功完成!"
echo "=================================================="
echo "重要信息:"
echo "1. 管理员用户名: thanos"
echo "2. 访问管理控制台: https://$(hostname -I | cut -d' ' -f1):${ADMIN_PORT}/console"
echo "3. 信任IP配置:  ${TARGET_DIR}/${INSTALL_NAME}/domains/domain1/conf/console.xml "
echo "4. 日志位置:${LOG_DIR}"
echo "5. 安装目录:${TARGET_DIR}/${INSTALL_NAME}"
echo "6. JDK配置文件:${TARGET_DIR}/${INSTALL_NAME}/bin/JAVA_HOME.txt"
echo "7. 用户账号密码清单:"
echo "   - thanos(系统管理员): $THANOS_PASSWORD"
echo "   - security(安全保密管理员): $SECURITY_PASSWORD"
echo "   - auditor(安全审计员): $AUDITOR_PASSWORD"
echo "   - monitor(系统资源监视管理员): $MONITOR_PASSWORD"
echo "8.启停信息:"
echo " 启停用户:$CURRENT_USER"
echo " 启动指令:"
echo " cd ${TARGET_DIR}/${INSTALL_NAME}/bin"
echo " ./startd.sh"
echo " 停止指令:"
echo " cd ${TARGET_DIR}/${INSTALL_NAME}/bin"
echo " ./stopserver.sh"
echo "9. 安全起见,已启用配置文件防篡改功能,禁用文件上传功能,如需解除,可参考产品手册:TongWeb_V7.0.8 控制台使用手册"
echo "更多详细内容,请参考安装摘要文件:${TARGET_DIR}/${INSTALL_NAME}/installation_message.txt"
echo "已启动tongweb7.0.8.905!"
echo "=================================================="

# 13. 生成安装摘要文件
SUMMARY_FILE="${TARGET_DIR}/${INSTALL_NAME}/installation_message.txt"
cat > "$SUMMARY_FILE" <<EOF
=============== TongWeb 安装摘要 ================
安装日期: $(date)
安装版本: TongWeb 7.0.8.9

=== 访问信息 ===
管理控制台: https://$(hostname -I | cut -d' ' -f1):${ADMIN_PORT}/console
访问账号:
thanos(系统管理员): ${THANOS_PASSWORD}

=== 用户信息 ===  
用户账号密码清单:
   thanos(系统管理员): ${THANOS_PASSWORD}
   security(安全保密管理员): ${SECURITY_PASSWORD}
   auditor(安全审计员): ${AUDITOR_PASSWORD}
   monitor(系统资源监视管理员): ${MONITOR_PASSWORD}

正常情况下,登录控制台,建议使用thanos用户。
如需配置监控(例如jmx),建议使用monitor。
   
关于各用户的介绍,可参考:	

TongWeb_V7.0.8产品介绍手册第6章节:权限管理
TongWeb_V7.0.8 控制台使用手册第4.4章节:安全配置

=== 产品手册 ===
存放路径:${TARGET_DIR}/${INSTALL_NAME}/version7.0.8.905/docs

=== 安全配置 ===
信任IP配置文件: ${TARGET_DIR}/${INSTALL_NAME}/domains/domain1/conf/console.xml 

=== 路径信息 ===
安装目录: ${TARGET_DIR}/${INSTALL_NAME}
日志路径: ${LOG_DIR}
JDK配置: ${TARGET_DIR}/${INSTALL_NAME}/bin/JAVA_HOME.txt
授权文件:${TARGET_DIR}/${INSTALL_NAME}/license.dat

=============== 重要指令 ================
启停用户:$CURRENT_USER

启动指令:
cd ${TARGET_DIR}/${INSTALL_NAME}/bin
./startd.sh

停止指令:
cd ${TARGET_DIR}/${INSTALL_NAME}/bin
./stopserver.sh

查看进程:
ps -ef |grep tongweb


查看授权到期时间:
cd ${TARGET_DIR}/${INSTALL_NAME}/bin
./version.sh

=============================================
EOF

echo "安装摘要已保存至: $SUMMARY_FILE"
echo ""


tongweb-security-baseline.sh(可选)

用途

安全基线整改(按需决定是否使用)

流程图

┌─────────────────────────────────────────────────────────────┐
│                      脚本启动                               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  前置检查 (check_prerequisites)                             │
│  - 检查安装目录是否存在                                      │
│  - 确认已备份 (交互式)                                       │
│  - 输入 Thanos 密码、Security 密码                            │
│  - 检查当前用户与安装目录属主一致性                           │
│  - 检查必要文件 (forcestop.sh, startd.sh, commandstool.sh)   │
│  - 确认管理主机 IP                                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 1: 设置管理端口 (set_management_port)                  │
│  - 从 tongweb.xml 解析当前端口                               │
│  - 询问是否修改端口                                         │
│  - 如修改:停止服务 → 修改配置 → 启动服务                    │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 2: 用户管理 (manage_users)                            │
│  - 查询当前用户列表                                         │
│  - 询问是否新建用户                                         │
│    → 是:输入用户名 → 密码复杂度校验 → 创建用户 → 赋予 role  │
│    → 否:选择使用 thanos 或其他用户                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 3: 修改实例通道端口 (modify_connector_port)            │
│  - 查询 domain1 实例的 server 通道当前端口                     │
│  - 询问是否调整端口                                         │
│  - 端口校验(不能是 9060/8080,不能冲突)                    │
│  - 更新通道配置                                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 4: 设置控制台信任 IP (set_console_trusted_ips)         │
│  - 输入信任 IP(支持单 IP、IP 段、范围、多个 IP)              │
│  - 调用 commandstool.sh 更新配置                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 5: 配置自定义异常页面 (configure_custom_error_pages)   │
│  - 查询 domain1 实例的虚拟主机列表                            │
│  - 对每个虚拟主机询问是否配置                               │
│  - 输入错误码 + 页面路径 → 加密 → 更新配置                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 6: 配置远程 JMX (configure_remote_jmx)                │
│  - 输入 JMX 通信端口                                        │
│  - 输入 JMX 绑定 IP                                          │
│  - 更新 JMX 配置                                            │
│  - 添加 JMX 启动参数到 tongweb.xml                           │
│  - 询问是否立即重启                                         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  脚本结束                                                   │
│  - 输出日志文件路径                                         │
└─────────────────────────────────────────────────────────────┘

使用说明

先赋予执行权限

chmod +x tongweb-security-baseline.sh

赋权后可以执行指令看看说明:

./tongweb-security-baseline.sh   --help

在这里插入图片描述
可以直接执行进入到交互页面,然后根据要求进行操作:

./tongweb-security-baseline.sh  

在这里插入图片描述
脚本内容如下:

#!/bin/bash

# ==================== 全局变量定义 ====================
install_dir=""
output_file=""
timestamp=$(date "+%Y%m%d%H%M%S%3N")
host=""
current_user=""
install_owner=""
thanos_username="thanos"
security_username="security"
thanos_password=""
security_password=""
new_username=""
new_user_password=""
port=""
instance="domain1"
connector_name="server"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLUSTER_SCRIPT="$SCRIPT_DIR/create_tongweb_cluster2.sh"
#tongweb自带脚本
required_files=("forcestop.sh" "startd.sh" "commandstool.sh")


# ==================== 日志输出函数 ====================
log_with_timestamp(){
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" | tee -a "$output_file"
}

mask_password_in_cmd(){
    local cmd="$1"
    # 匹配 --password="任意内容" 并替换为 --password="******"
    echo "$cmd" | sed -E 's/(--password=")[^"]*/\1******/g'
}

# ==================== 通用命令执行函数(修复版) ====================
# 参数1: 要执行的命令
# 参数2(可选): 是否在函数内删除临时文件,默认 true(删除)
execute_and_log(){
    local cmd="$1"
    local cleanup=${2:-true}
    local masked_cmd
    masked_cmd=$(mask_password_in_cmd "$cmd")
    log_with_timestamp "执行命令: $masked_cmd"
    eval "$cmd" > temp_output.txt 2>&1
    local status=$?
    log_with_timestamp "命令执行结束,状态码: $status"
    cat temp_output.txt | tee -a "$output_file"
    if $cleanup; then
        rm -f temp_output.txt
    fi
    return $status
}

# ==================== JMX专用执行函数(带脱敏) ====================
execute_and_log_jmx(){
    local cmd="$1"
    local masked_cmd
    masked_cmd=$(mask_password_in_cmd "$cmd")
    log_with_timestamp "执行JMX命令: $masked_cmd"
    eval "$cmd" > temp_jmx_output.txt 2>&1
    local status=$?
    log_with_timestamp "JMX命令执行结束,状态码: $status"
    cat temp_jmx_output.txt | tee -a "$output_file"
    rm -f temp_jmx_output.txt
    return $status
}


# ==================== 使用说明 ====================

show_usage() {
 cat <<'EOF'
TongWeb 7.0.8.909 安全基线核查脚本(交互式)

【用法】
  ./tongweb-security-baseline.sh

【参数说明】
  --install-dir       TongWeb 安装目录
  --thanos-password   Thanos 用户密码
  --security-password Security 用户密码
  --host              管理主机 IP
  --port              管理端口(默认从配置文件读取)
  --output-file       日志文件路径(默认:安装目录/output_时间戳.txt)
  --help, -h, help    显示帮助信息

【注意】
  1. 执行前请确保已备份 TongWeb 安装目录
  2. 执行期间会重启 TongWeb 服务,生产环境请提前准备
  3. 当前用户必须与安装目录属主一致
EOF
}


# ==================== 命令行参数解析 ====================
parse_args() {
 while [[ $# -gt 0 ]]; do
   case "$1" in
     --help|-h|help) show_usage; exit 0 ;;
     *) echo "未知参数:$1"; show_usage; exit 1 ;;
   esac
 done
}

# ==================== 前置检查 ====================
check_prerequisites(){
    echo " ================================================="
    echo "     TongWeb 7.0.8.909安全基线核查清单执行脚本 "
    echo " ================================================="
    read -p "请输入Tongweb安装目录: " install_dir
    if [ ! -d "$install_dir" ]; then
        log_with_timestamp "目录不存在或无法访问。"
        exit 1
    fi
    output_file="$install_dir/output_$timestamp.txt"
    log_with_timestamp "本次操作记录到文件:$output_file"
    echo "Tongweb中间件安全基线核查清单操作日志" > "$output_file"

    log_with_timestamp "重要提示:在继续操作之前,请确保Tongweb已启动,操作期间需要重启Tongweb,生产环境请提前做好准备!!! 如需退出,按ctrl+c"
    log_with_timestamp "提示:在继续操作之前,请确保已对Tongweb的安装目录进行了备份。"
    read -p "是否已备份?(输入 'yes' 确认继续,或 'no' 退出脚本): " backup_confirm
    log_with_timestamp "用户确认备份状态: $backup_confirm"
    if [ "$backup_confirm" != "yes" ]; then
        log_with_timestamp "请先备份Tongweb的安装目录,然后重新运行脚本。"
        exit 1
    fi

    log_with_timestamp "开始执行:Tongweb中间件安全基线核查清单,请根据提示,输入相关信息,并回车确认。"
    read -sp "请输入Thanos用户的密码,用于后续新建用户和其他配置项操作: " thanos_password
    echo
    log_with_timestamp "Thanos密码已输入(不显示)"
    read -sp "请输入Security用户的密码,用于后续配置用户角色: " security_password
    echo
    log_with_timestamp "Security用户的密码已输入(不显示)"

    install_owner=$(stat -c '%U' "$install_dir" 2>/dev/null)
    current_user=$(whoami)
    if [ "$current_user" != "$install_owner" ]; then
        log_with_timestamp "当前用户 ($current_user) 和安装目录属主 ($install_owner) 不一致。请切换到属主用户并重新执行脚本。"
        exit 1
    fi

    for file in "${required_files[@]}"; do
        if [ ! -f "$install_dir/bin/$file" ]; then
            log_with_timestamp "$file文件不存在或无法访问。"
            exit 1
        fi
    done

    host=$(hostname -I | awk '{print $1}')
    log_with_timestamp "检测到的当前主机IP地址为:$host"
    read -p "请确认IP地址是否正确(输入 'yes' 确认或 'no' 修改): " ip_confirm
    log_with_timestamp "用户确认IP地址: $ip_confirm"
    if [ "$ip_confirm" != "yes" ]; then
        read -p "请输入正确的IP地址: " host
        log_with_timestamp "修改后的IP地址: $host"
    fi
}

# ==================== 步骤1:设置管理端口 ====================
set_management_port(){
    log_with_timestamp "步骤1:设置Tongweb管理端口"
    local config_file="$install_dir/domains/domain1/conf/tongweb.xml"
    port=""
    if [ -f "$config_file" ]; then
        port=$(grep -A5 'service systemManaged="true"' "$config_file" | grep -oE 'port="[^"]+"' | sed -E 's/port="([^"]+)"/\1/' | head -n1)
        [ -n "$port" ] && log_with_timestamp "当前的Tongweb管理端口为: $port"
    fi
    if [ -z "$port" ]; then
        log_with_timestamp "未找到Tongweb的管理端口号,请通过 $config_file 文件进行确认。"
        read -p "请输入Tongweb的管理端口号: " port
        log_with_timestamp "用户输入的端口号: $port"
    fi
    read -p "当前Tongweb的管理端口号为 $port,您需要修改端口号吗?(y/n) " answer
    log_with_timestamp "用户选择修改端口号: $answer"
    if [[ "$answer" =~ ^[Yy]$ ]]; then
        while true; do
            read -p "请输入新的端口号: " new_port
            if [[ "$new_port" =~ ^[0-9]+$ ]] && (( new_port > 0 && new_port < 65536 )); then
                if ! lsof -i :"$new_port" &>/dev/null; then
                    log_with_timestamp "新端口号 $new_port 可用。"
                    log_with_timestamp "正在停止服务..."
                    "$install_dir/bin/forcestop.sh"
                    log_with_timestamp "服务已停止,正在等待10秒以确保所有进程关闭。"
                    sleep 10
                    awk -v old_port="$port" -v new_port="$new_port" '{gsub("port=\"" old_port "\"", "port=\"" new_port "\""); print}' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
                    log_with_timestamp "配置文件已更新为新的端口号: $new_port"
                    port="$new_port"
                    log_with_timestamp "正在启动服务..."
                    "$install_dir/bin/startd.sh"
                    log_with_timestamp "已执行启动脚本,请等待10秒。"
                    sleep 10
                    log_with_timestamp "服务器启动完成。"
                    break
                else
                    log_with_timestamp "端口号 $new_port 已被占用,请选择其他端口号。"
                fi
            else
                log_with_timestamp "请输入有效的端口号(1-65535)。"
            fi
        done
    else
        log_with_timestamp "端口号保持不变。"
    fi
}

# ==================== 步骤2:用户管理 ====================
manage_users(){
    log_with_timestamp "步骤2:查询当前已有的用户列表"
    cd "$install_dir/bin" || { log_with_timestamp "目录不存在或无法访问"; exit 1; }
    local query_users_command="./commandstool.sh --host=\"$host\" --port=\"$port\" --model=user --action=list --acceptAgreement=true --username=\"$thanos_username\" --password=\"$thanos_password\""
    local masked_query_cmd
    masked_query_cmd=$(mask_password_in_cmd "$query_users_command")
    log_with_timestamp "执行命令: $masked_query_cmd"
    user_list_output=$(eval "$query_users_command")
    if [[ $? -ne 0 ]]; then
        log_with_timestamp "❌ 查询用户列表失败,请检查命令或环境配置。"
        exit 1
    fi
    log_with_timestamp "当前用户列表查询结果:"
    echo "$user_list_output" | tee -a "$output_file"
    user_info=$(echo "$user_list_output" | grep -oP '"name":"[^"]+",?"info":"[^"]+"' | sed -E 's/"name":"([^"]+)","info":"([^"]+)"/名称:\1 信息:\2/')
    log_with_timestamp "当前用户信息列表(提取后的):"
    echo "$user_info" | tee -a "$output_file"
    if [ -z "$user_info" ]; then
        log_with_timestamp "⚠️ 无法提取用户信息,可能JSON格式有误或解析失败,请检查原始输出。"
    else
        log_with_timestamp "✅ 用户信息提取完毕,已输出至日志文件。"
    fi
    read -p "是否需要新增用户?(输入 'yes' 进入新建用户流程,或 'no' 跳过): " create_user_confirm
    log_with_timestamp "用户选择是否新增用户: $create_user_confirm"
    new_username=""
    new_user_password=""
    if [ "$create_user_confirm" == "yes" ]; then
        log_with_timestamp "步骤2:新建用户"
        while true; do
            read -p "从客户处输入需要新建的用户名(仅限英文字符): " new_username
            if [[ "$new_username" =~ ^[a-zA-Z]+$ ]]; then
                log_with_timestamp "新建用户名: $new_username"
                break
            else
                log_with_timestamp "用户名不合法,请仅使用英文字符。"
            fi
        done
        change_decision="yes"
        if [ "$change_decision" == "yes" ]; then
            while true; do
                read -sp "请输入新的密码(长度至少为8位以上,口令必须从字符(a-z,A-Z)、数字(0-9)、符号(~!@#$%^&*()_<>-)中至少选择两种进行组合,由非纯数字或字母组成): " new_user_password
                echo
                read -sp "请确认新的密码: " confirm_password
                echo
                if [ ${#new_user_password} -lt 8 ]; then
                    log_with_timestamp "❌ 错误:密码长度必须至少为8位。"
                    continue
                fi
                contains_letter=false
                [[ "$new_user_password" =~ [a-zA-Z] ]] && contains_letter=true
                contains_number=false
                [[ "$new_user_password" =~ [0-9] ]] && contains_number=true
                contains_special=false
                [[ "$new_user_password" =~ [~!@#$%^\&\*\(\)_\<\>\-] ]] && contains_special=true
                if ! ( $contains_letter && $contains_number ) && ! ( $contains_letter && $contains_special ) && ! ( $contains_number && $contains_special ); then
                    log_with_timestamp "❌ 错误:密码必须包含字母、数字和特殊符号中的两种。"
                    continue
                fi
                if [ "$new_user_password" != "$confirm_password" ]; then
                    log_with_timestamp "❌ 错误:两次输入的密码不一致。"
                    continue
                fi
                break
            done
        fi
        log_with_timestamp "通过Thanos新建用户账号。"
        add_cmd="./commandstool.sh --host=\"$host\" --port=\"$port\" --model=user --action=add --username=\"$thanos_username\" --password=\"$thanos_password\" --acceptAgreement=true digestAlg=SHA-256 active=true saltLength=4 limitRepeats=5 iterations=5 enablePasswordAge=true password=\"$new_user_password\" confirmPassword=\"$new_user_password\" name=\"$new_username\" passwordMinAge=0 minLen=8 changeInitPwd=false passwordMaxAge=90 info=\"业务使用账号\""
        masked_add_cmd=$(mask_password_in_cmd "$add_cmd")
        log_with_timestamp "执行命令: $masked_add_cmd"
        eval "$add_cmd"
        log_with_timestamp "新用户账号已创建."
        log_with_timestamp "正在通过Security赋予新用户管理者角色system..."
        role_cmd="./commandstool.sh --host=\"$host\" --port=\"$port\" --model=userrole --action=update --username=\"$security_username\" --password=\"$security_password\" --acceptAgreement=true name=\"$new_username\" roles=system nodes=default"
        masked_role_cmd=$(mask_password_in_cmd "$role_cmd")
        log_with_timestamp "执行命令: $masked_role_cmd"
        eval "$role_cmd"
        log_with_timestamp "已赋予新用户管理者角色system."
    else
        log_with_timestamp "用户选择不创建新用户。"
        read -p "后续操作是否使用 thanos 用户执行?(yes 使用 thanos / no 输入其他用户): " use_thanos_confirm
        if [ "$use_thanos_confirm" == "yes" ]; then
            log_with_timestamp "后续操作将使用 thanos 用户执行。"
            new_username="$thanos_username"
            new_user_password="$thanos_password"
        else
            read -p "请输入要使用的用户名: " new_username
            read -sp "请输入该用户的密码: " new_user_password
            echo
            log_with_timestamp "后续操作将使用用户 $new_username 执行。"
        fi
    fi
}

# ==================== 步骤3:修改实例通道端口号 ====================
modify_connector_port(){
    log_with_timestamp "步骤3:修改实例通道端口号(仅默认实例 domain1 的 server 通道)"
    log_with_timestamp "当前用户名: $new_username"
    read -p "是否执行步骤3?请输入 'yes' 执行或 'no' 跳过: " step3_confirm
    log_with_timestamp "用户选择执行步骤3: $step3_confirm"
    if [ "$step3_confirm" = "yes" ]; then
        if [ ! -d "$install_dir/bin" ] || [ "$current_user" != "$install_owner" ]; then
            log_with_timestamp "目录检查失败或当前用户与目录属主不一致,请检查。"
            exit 1
        fi
        cd "$install_dir/bin" || { log_with_timestamp "目录不存在或无法访问"; exit 1; }
        instance="domain1"
        connector_name="server"
        log_with_timestamp "=== 检查实例: $instance 的通道 '$connector_name' 配置 ==="
        cmd="./commandstool.sh --host=\"$host\" --port=\"$port\" \
               --username=\"$new_username\" --password=\"$new_user_password\" --acceptAgreement=true \
               --model=connector --action=list --targetType=instance --targetName=\"$instance\" name=\"$connector_name\""
        execute_and_log "$cmd" false # 不自动删除,后续要读取
        connector_output=$(<temp_output.txt)
        rm -f temp_output.txt # 读取后立即删除
        if [[ $? -eq 0 ]] && echo "$connector_output" | grep -q '"success":"true"'; then
            current_port=$(echo "$connector_output" | awk -v name="$connector_name" '
                 /"name":"'"$connector_name"'"/ {found=1}
                 found && /"port":"[0-9]+"/ {
                     match($0,/"port":"([0-9]+)"/,arr);
                     if(arr[1]!=""){print arr[1]; found=0}
                 }')
            if [ -z "$current_port" ]; then
                log_with_timestamp "❌ 未能在实例 '$instance' 中找到通道 '$connector_name' 的端口信息。"
                exit 1
            fi
            log_with_timestamp "实例 '$instance' 的通道 '$connector_name' 当前端口为: $current_port"
            if [ "$current_port" = "9060" ] && [ "$connector_name" = "admin" ]; then
                log_with_timestamp "⚠️ 检测到控制台端口(admin 9060),根据TongWeb安全规范,控制台端口不允许修改,已跳过。"
                exit 0
            elif [ "$current_port" = "8080" ]; then
                log_with_timestamp "⚠️ 检测到应用默认端口8080,根据安全规范必须修改。"
                adjust_confirm="yes"
            else
                read -p "是否调整实例 '$instance' 的通道 '$connector_name' 的端口号(当前端口 $current_port)?请输入 'yes' 或 'no': " adjust_confirm
                log_with_timestamp "用户选择调整实例 '$instance' 的通道 '$connector_name' 的端口: $adjust_confirm"
            fi
            if [ "$adjust_confirm" = "yes" ]; then
                while true; do
                    if [ "$current_port" = "8080" ]; then
                        read -p "请输入实例 '$instance' 的通道 '$connector_name' 的新端口号(当前为默认端口8080,必须修改。输入1-65535的数字,不得使用9060或8080): " new_port
                    else
                        read -p "请输入实例 '$instance' 的通道 '$connector_name' 的新端口号(当前端口 $current_port,输入1-65535的数字,不得使用9060或8080): " new_port
                    fi
                    log_with_timestamp "用户为实例 '$instance' 的通道 '$connector_name' 输入新的端口号: $new_port"
                    if ! [[ "$new_port" =~ ^[0-9]+$ ]] || [ "$new_port" -lt 1 ] || [ "$new_port" -gt 65535 ] || [ "$new_port" -eq 9060 ] || [ "$new_port" -eq 8080 ]; then
                        log_with_timestamp "无效端口: $new_port,请输入1到65535的数字,且不能为9060或8080。"
                        continue
                    fi
                    if ss -tuln | awk '{print $5}' | grep -q ":$new_port$"; then
                        log_with_timestamp "⚠️ 端口 $new_port 已被占用,请选择其他未使用的端口。"
                        continue
                    fi
                    cmd="./commandstool.sh --host=\"$host\" --port=\"$port\" \
                                 --username=\"$new_username\" --password=\"$new_user_password\" --acceptAgreement=true \
                                 --model=connector --action=update \
                                  name=\"$connector_name\" port=\"$new_port\""
                    execute_and_log "$cmd"
                    if [[ $? -eq 0 ]] && ! grep -q '"success":"false"' temp_output.txt; then
                        log_with_timestamp "✅ 实例 '$instance' 的通道 '$connector_name' 的端口已更新为 $new_port."
                        break
                    else
                        log_with_timestamp "❌ 实例 '$instance' 的通道 '$connector_name' 端口更新失败!请检查日志。"
                        cat temp_output.txt | tee -a "$output_file"
                    fi
                done
            else
                if [ "$current_port" = "8080" ]; then
                    log_with_timestamp "⚠️ 警告:应用默认端口8080未修改,不符合安全规范!"
                else
                    log_with_timestamp "跳过实例 '$instance' 的通道 '$connector_name' 的端口更改。"
                fi
            fi
        else
            log_with_timestamp "❌ 获取实例 '$instance' 的通道信息失败!原始输出:"
            cat temp_output.txt | tee -a "$output_file"
        fi
    else
        log_with_timestamp "步骤3已被用户跳过。"
    fi
}

# ==================== 步骤4:设置控制台信任IP ====================
set_console_trusted_ips(){
    log_with_timestamp "步骤4:设置Tongweb控制台信任IP"
    read -p "是否执行步骤4?请输入 'yes' 执行或 'no' 跳过: " step4_confirm
    log_with_timestamp "用户选择执行步骤4: $step4_confirm"
    if [ "$step4_confirm" == "yes" ]; then
        if [ ! -d "$install_dir/bin" ] || [ "$current_user" != "$install_owner" ]; then
            log_with_timestamp "目录检查失败或当前用户与目录属主不一致,请检查。"
            exit 1
        fi
        cd "$install_dir/bin" || { log_with_timestamp "目录不存在或无法访问"; exit 1; }
        log_with_timestamp "=== 配置信任IP ==="
        log_with_timestamp "请设置允许访问TongWeb控制台的信任IP"
        log_with_timestamp "格式说明:"
        log_with_timestamp "1. 单个IP: 192.168.1.100"
        log_with_timestamp "2. IP段: 192.168.1.*"
        log_with_timestamp "3. IP范围: 192.168.1.50-192.168.1.100"
        log_with_timestamp "4. 多个IP用逗号分隔: 192.168.1.100,10.0.0.50"
        log_with_timestamp "5. 输入 '*' 表示信任所有IP(不推荐)"
        read -p "请输入需要信任的IP地址(用逗号分隔多个IP): " trusted_ips
        log_with_timestamp "用户输入的信任IP: $trusted_ips"
        set_trusted_output=$(
            ./commandstool.sh --model=consolesecurity --action=update \
            --username="$new_username" --password="$new_user_password" --host="$host" --port="$port" \
            --acceptAgreement=true serverVendor=TongAuth trustedIP="$trusted_ips")
        masked_set_trusted=$(mask_password_in_cmd "./commandstool.sh --model=consolesecurity --action=update --username=$new_username --password=****** --host=$host --port=$port --acceptAgreement=true serverVendor=TongAuth trustedIP=$trusted_ips")
        log_with_timestamp "执行命令: $masked_set_trusted"
        if [ $? -eq 0 ]; then
            log_with_timestamp "✅ 信任IP设置成功: $trusted_ips"
        else
            log_with_timestamp "⚠️ 信任IP设置失败,请检查输入格式和相关权限。"
        fi
    fi
}

# ==================== 步骤5:配置虚拟主机自定义异常页面 ====================
configure_custom_error_pages(){
    log_with_timestamp "步骤5:配置虚拟主机自定义异常页面(仅默认实例 domain1)"
    read -p "是否执行步骤5?请输入 'yes' 执行或 'no' 跳过: " step5_confirm
    log_with_timestamp "用户选择执行步骤5: $step5_confirm"
    if [ "$step5_confirm" == "yes" ]; then
        if [ ! -d "$install_dir/bin" ] || [ "$current_user" != "$install_owner" ]; then
            log_with_timestamp "目录检查失败或当前用户与目录属主不一致,请检查。"
            exit 1
        fi
        cd "$install_dir/bin" || { log_with_timestamp "目录不存在或无法访问"; exit 1; }
        instance="domain1"
        log_with_timestamp "=== 配置实例 '$instance' 的虚拟主机自定义异常页面 ==="
        host_output=$(
            ./commandstool.sh --host="$host" --port="$port" \
            --username="$new_username" --password="$new_user_password" --acceptAgreement=true \
            --model=host --action=list --targetType=instance --targetName="$instance")
        if [[ $? -eq 0 ]] && echo "$host_output" | grep -q '"success":"true"'; then
            host_names=$(echo "$host_output" | grep -oE '"name":"[^"]+"' | sed -E 's/"name":"([^"]+)"/\1/')
            if [ -n "$host_names" ]; then
                log_with_timestamp "虚拟主机列表:"
                echo "$host_names" | tr ' ' '\n' | tee -a "$output_file"
                for host_name in $host_names; do
                    read -p "是否需要为虚拟主机 '$host_name' 配置自定义异常页面?请输入 'yes' 或 'no': " host_confirm
                    log_with_timestamp "用户选择配置虚拟主机 '$host_name': $host_confirm"
                    if [ "$host_confirm" == "yes" ]; then
                        plain_entries=""
                        while true; do
                            read -p "请输入要设置自定义页面的HTTP错误码: " error_code
                            read -p "请输入异常码 '$error_code' 的自定义页面路径: " error_page_path
                            if [[ "$error_page_path" != /* ]]; then
                                log_with_timestamp "⚠️ 请提供绝对路径。"
                                continue
                            fi
                            entry="${error_code}=${error_page_path}"
                            if [ -n "$plain_entries" ]; then
                                plain_entries="$plain_entries,$entry"
                            else
                                plain_entries="$entry"
                            fi
                            read -p "是否继续配置虚拟主机 '$host_name' 的其他异常页面?请输入 'yes' 或 'no': " config_continue
                            log_with_timestamp "用户选择继续配置虚拟主机 '$host_name': $config_continue"
                            if [ "$config_continue" != "yes" ]; then
                                break
                            fi
                        done
                        encrypted_block=$(./admin.sh encrypt id "$plain_entries" | awk '{print $NF}')
                        command="./commandstool.sh --host=$host --port=$port --model=host --action=update \
                                         --password=\"$new_user_password\" --acceptAgreement=true --username=$new_username \
                                          customErrorPage=\"$encrypted_block\" name=$host_name"
                        masked_command=$(mask_password_in_cmd "$command")
                        log_with_timestamp "执行指令:$masked_command"
                        command_output=$(eval "$command")
                        log_with_timestamp "执行结果:$command_output"
                        if [[ $? -eq 0 ]] && ! echo "$command_output" | grep -q '"success": "false"'; then
                            log_with_timestamp "✅ 虚拟主机 '$host_name' 的异常页面已成功配置。"
                        else
                            log_with_timestamp "⚠️ 配置虚拟主机 '$host_name' 的异常页面失败,请检查输入或权限。"
                        fi
                    else
                        log_with_timestamp "跳过虚拟主机 '$host_name' 的配置。"
                    fi
                done
            else
                log_with_timestamp "实例 '$instance' 无虚拟主机可配置。"
            fi
        else
            log_with_timestamp "获取实例 '$instance' 的虚拟主机列表失败,请检查权限或连接状态。"
        fi
    fi
}

# ==================== 步骤6:配置远程JMX ====================
configure_remote_jmx(){
    log_with_timestamp "步骤6:配置远程 JMX"
    read -p "是否执行步骤6(配置远程JMX)?请输入 'yes' 执行或 'no' 跳过: " step6_confirm
    log_with_timestamp "用户选择执行步骤6: $step6_confirm"
    if [ "$step6_confirm" = "yes" ]; then
        if [ ! -d "$install_dir/bin" ] || [ "$current_user" != "$install_owner" ]; then
            log_with_timestamp "目录检查失败或当前用户与目录属主不一致,请检查。"
            exit 1
        fi
        cd "$install_dir/bin" || { log_with_timestamp "目录不存在或无法访问"; exit 1; }
        log_with_timestamp "步骤6将使用用户 $new_username 执行JMX配置(来自步骤2的选择)"
        while true; do
            read -p "请输入JMX通信端口(用于RMI注册,范围1-65535): " jmx_comm_port
            if ! [[ "$jmx_comm_port" =~ ^[0-9]+$ ]]; then
                log_with_timestamp "❌ 错误:JMX通信端口必须为数字。"
                continue
            fi
            if (( jmx_comm_port < 1 || jmx_comm_port > 65535 )); then
                log_with_timestamp "❌ 错误:JMX通信端口必须在1-65535之间。"
                continue
            fi
            if lsof -i :"$jmx_comm_port" &>/dev/null; then
                log_with_timestamp "⚠️ 端口 $jmx_comm_port 已被占用,请选择其他端口。"
                continue
            fi
            log_with_timestamp "✅ JMX通信端口设置为: $jmx_comm_port"
            break
        done
        while true; do
            read -p "请输入JMX绑定的IP地址(用于RMI server hostname,建议使用本机IP): " jmx_ip
            if [ -z "$jmx_ip" ]; then
                log_with_timestamp "❌ 错误:JMX绑定IP不能为空。"
                continue
            fi
            if ! [[ "$jmx_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                log_with_timestamp "⚠️ 警告:IP格式可能不正确(需为IPv4格式,如192.168.1.100)。"
                read -p "是否继续使用该IP?(yes/no): " ip_continue
                if [ "$ip_continue" != "yes" ]; then
                    continue
                fi
            fi
            log_with_timestamp "✅ JMX绑定IP设置为: $jmx_ip"
            break
        done

        log_with_timestamp "开始配置远程JMX参数(使用用户 $new_username..."
        cmd_jmx_update="./commandstool.sh --host=$host --port=$port \
             --username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
             --model=jmx --action=update \
             supportJConsole=true sslNeedClientAuth=false port=$jmx_comm_port ip=$jmx_ip enabled=true useSsl=false"
        execute_and_log_jmx "$cmd_jmx_update"
        if [[ $? -ne 0 ]]; then
            log_with_timestamp "⚠️ JMX基础参数配置失败,请检查权限或命令格式。"
        fi
        jmx_startup_args=(
            "-Dcom.sun.management.jmxremote=true"
            "-Dcom.sun.management.jmxremote.ssl=false"
            "-Dcom.sun.management.jmxremote.authenticate=false"
            "-Djava.rmi.server.hostname=$jmx_ip"
        )
        config_path="$install_dir/domains/domain1/conf/tongweb.xml"
        for arg in "${jmx_startup_args[@]}"; do
            log_with_timestamp "即将添加的JMX启动参数: $arg ,该参数将在配置生效后写入文件:$config_path"
            cmd_arg="./commandstool.sh --host=$host --port=$port \
                    --username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
                    --model=startupargs --action=add name=\"$arg\" onlyForLinux=false enabled=true"
            execute_and_log_jmx "$cmd_arg"
            if [[ $? -ne 0 ]]; then
                log_with_timestamp "⚠️ 添加JMX启动参数($arg)失败。"
            fi
        done
        log_with_timestamp "⚠️ JMX配置已添加,但需重启TongWeb才能生效。配置内容可在 $config_path 中查看。"
        read -p "是否需要立即重启TongWeb以使JMX配置生效?(yes/no): " restart_confirm
        log_with_timestamp "用户选择重启TongWeb: $restart_confirm"
        if [ "$restart_confirm" = "yes" ]; then
            log_with_timestamp "正在停止TongWeb服务..."
            "$install_dir/bin/forcestop.sh"
            log_with_timestamp "服务已停止,等待10秒确保进程关闭。"
            sleep 10
            log_with_timestamp "正在启动TongWeb服务..."
            "$install_dir/bin/startd.sh"
            log_with_timestamp "已执行启动脚本,等待10秒。"
            sleep 10
            log_with_timestamp "✅ TongWeb服务重启完成,JMX配置已生效。"
        else
            log_with_timestamp "⚠️ 未重启TongWeb,JMX配置需重启后才会生效。"
        fi
    else
        log_with_timestamp "步骤6已被用户跳过。"
    fi
}

# ==================== 主流程 ====================
main(){
    parse_args "$@"
    check_prerequisites
    set_management_port
    manage_users
    modify_connector_port
    set_console_trusted_ips
    configure_custom_error_pages
    configure_remote_jmx
    log_with_timestamp "脚本执行完毕"
    log_with_timestamp "本次操作已记录到文件:$output_file"
}
main "$@"

setup_tongweb_cluster_single.sh

用途

通过脚本创建本机服务器的集群(非本机服务器,需通过控制台执行)

流程图

┌─────────────────────────────────────────────────────────────┐
│                      脚本启动                               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  前置检查 (check_prerequisites)                             │
│  - 检查安装目录是否存在                                      │
│  - 确认已备份 (交互式)                                       │
│  - 输入 Thanos 密码、Security 密码                            │
│  - 检查当前用户与安装目录属主一致性                           │
│  - 检查必要文件 (forcestop.sh, startd.sh, commandstool.sh)   │
│  - 确认管理主机 IP                                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 1: 设置管理端口 (set_management_port)                  │
│  - 从 tongweb.xml 解析当前端口                               │
│  - 询问是否修改端口                                         │
│  - 如修改:停止服务 → 修改配置 → 启动服务                    │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 2: 用户管理 (manage_users)                            │
│  - 查询当前用户列表                                         │
│  - 询问是否新建用户                                         │
│    → 是:输入用户名 → 密码复杂度校验 → 创建用户 → 赋予 role  │
│    → 否:选择使用 thanos 或其他用户                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 3: 修改实例通道端口 (modify_connector_port)            │
│  - 查询 domain1 实例的 server 通道当前端口                     │
│  - 询问是否调整端口                                         │
│  - 端口校验(不能是 9060/8080,不能冲突)                    │
│  - 更新通道配置                                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 4: 设置控制台信任 IP (set_console_trusted_ips)         │
│  - 输入信任 IP(支持单 IP、IP 段、范围、多个 IP)              │
│  - 调用 commandstool.sh 更新配置                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 5: 配置自定义异常页面 (configure_custom_error_pages)   │
│  - 查询 domain1 实例的虚拟主机列表                            │
│  - 对每个虚拟主机询问是否配置                               │
│  - 输入错误码 + 页面路径 → 加密 → 更新配置                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  步骤 6: 配置远程 JMX (configure_remote_jmx)                │
│  - 输入 JMX 通信端口                                        │
│  - 输入 JMX 绑定 IP                                          │
│  - 更新 JMX 配置                                            │
│  - 添加 JMX 启动参数到 tongweb.xml                           │
│  - 询问是否立即重启                                         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  脚本结束                                                   │
│  - 输出日志文件路径                                         │
└─────────────────────────────────────────────────────────────┘

使用说明

先给脚本赋权

chmod +x setup_tongweb_cluster_single.sh 

可以执行以下指令,看看帮助说明:

./setup_tongweb_cluster_single.sh  --help

在这里插入图片描述

执行的话,可以根据自身需求来,刚开始使用,建议使用完全交互:

./setup_tongweb_cluster_single.sh

在这里插入图片描述
脚本内容如下:

#!/bin/bash
set -u

# =============================================================================
# TongWeb 集群自动化部署脚本 - 单节点版
# 功能:检查集群→检查/创建单节点→创建集群→管理实例→生成启停脚本
# 版本:2.0-Single
# =============================================================================

# =============================================================================
# 使用说明
# =============================================================================
#
# 【模式 0】无参数(完全交互式)- 适合首次使用、不熟悉参数
#   ./setup_tongweb_cluster_single.sh
#   → 按提示逐步输入所有参数
#
# 【模式 1】最小化参数(半交互式)- 适合首次部署、调试
#   ./setup_tongweb_cluster_single.sh \
#     --host=192.168.10.12 --port=9060 --username=thanos \
#     --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909
#
# ----------------------------------------------------------------------------
#
# 【模式 2】完整参数(半自动)- 适合生产部署
#   ./setup_tongweb_cluster_single.sh \
#     --host=192.168.10.12 --port=9060 --username=thanos \
#     --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909 \
#     --node-ip=192.168.10.12 --ssh-user=root --ssh-password=Root123 \
#     --java-home=/opt/bisheng-jdk1.8.0_432 --install-path=/opt/tw8node \
#     --cluster-name=cluster01
#
# ----------------------------------------------------------------------------
#
# 【模式 3】全自动化(无交互)- 适合批量部署、CI/CD
#   ./setup_tongweb_cluster_single.sh \
#     --host=192.168.10.12 --port=9060 --username=thanos \
#     --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909 \
#     --node-name=node12 --node-ip=192.168.10.12 --node-port=9061 \
#     --ssh-port=22 --ssh-user=root --ssh-password=Root123 \
#     --java-home=/opt/bisheng-jdk1.8.0_432 --install-path=/opt/tw8node \
#     --cluster-name=cluster01 --instance-count=2 \
#     --skip-interactive --show-cmd-output=0
#
# =============================================================================
# 参数格式:支持 --param value 或 --param=value 两种格式
# =============================================================================
#
# 必填参数:
#   --host          TongWeb 管理主机 IP
#   --port          TongWeb 管理端口
#   --username      管理员用户名
#   --password      管理员密码(明文传入,日志自动脱敏)
#   --install-dir   TongWeb 安装目录(包含 bin/commandstool.sh)
#
# 可选参数:
#   --node-name       节点名称
#   --node-ip         节点 IP(默认自动获取当前服务器 IP)
#   --node-port       节点管理端口(交互式不传会提示输入,自动化模式必填)
#   --ssh-port        SSH 端口(交互式不传会提示输入,自动化模式必填)
#   --ssh-user        SSH 用户名(必填,无默认值)
#   --ssh-password    SSH 密码
#   --java-home       JDK 路径
#   --install-path    实例安装路径(必须是空目录)
#   --cluster-name    集群名称(默认:cluster01)
#   --instance-count  每节点实例数上限(默认:2)
#   --output-file     日志文件路径
#   --skip-interactive 跳过交互,全自动化
#   --show-cmd-output 命令输出开关(0/1,默认:1)
#   --help, help      显示帮助信息
#
# =============================================================================

# ==================== 输出开关 ====================
SHOW_CMD_OUTPUT="${SHOW_CMD_OUTPUT:-1}"

# ==================== 全局变量定义 ====================
install_dir=""
timestamp=$(date "+%Y%m%d%H%M%S%3N")
output_file="/tmp/tongweb_setup_${timestamp}.log"
host=""
port=""
new_username=""
new_user_password=""
required_files=("forcestop.sh" "startd.sh" "commandstool.sh")

# ==================== 配置参数 ====================
PAGE_SIZE=100
MAX_INSTANCES_PER_NODE=2
INSTANCE_NEED_CONSOLE_PORT="true"
DEFAULT_STARTWITHNODE="true"
DEFAULT_AUTOSTART="true"
DEFAULT_MAXRETRYCOUNT="0"
DEFAULT_INSTALLATION_PATH="${DEFAULT_INSTALLATION_PATH:-/opt/tw8node}"

# 全局变量
cluster_name=""
nodes_str=""

# 命令行参数变量
ARG_NODE_NAME=""
ARG_NODE_IP=""
ARG_NODE_PORT=""
ARG_SSH_PORT=""
ARG_SSH_USER=""
ARG_SSH_PASSWORD=""
ARG_JAVA_HOME=""
ARG_INSTALL_PATH=""
ARG_CLUSTER_NAME=""
ARG_INSTANCE_COUNT=""
ARG_SKIP_INTERACTIVE="false"

# ==================== 日志函数 ====================
log_with_timestamp() {
 local msg="$*"
 local line
 line="$(date '+%Y-%m-%d %H:%M:%S') - ${msg}"
 echo "$line" >&2
 echo "$line" >>"$output_file"
}

log_phase() { log_with_timestamp "========== [PHASE] $* =========="; }
log_step() { log_with_timestamp "---- [STEP] $*"; }
log_ok() { log_with_timestamp "[OK] $*"; }
log_warn() { log_with_timestamp "[WARN] $*"; }
log_error() { log_with_timestamp "[ERROR] $*"; }

# ==================== 密码掩码函数 ====================
mask_password_in_cmd() {
 local cmd="$1"
 echo "$cmd" | sed -E \
   -e 's/(--password=)"[^"]*"/\1"******"/g' \
   -e 's/(--password=)[^ ]+/\1******/g' \
   -e 's/(sshPassword=)[^ ]+/\1******/g' \
   -e 's/(password=)[^ ]+/\1******/g'
}

# ==================== 密码加密函数 ====================
encrypt_password_if_needed() {
 local raw_password="$1"
 local __outvar="${2:-}"

 if [[ "$raw_password" =~ ^Tw80EncodedPrefixFlag ]]; then
   if [[ -n "$__outvar" ]]; then
     printf -v "$__outvar" '%s' "$raw_password"
   fi
   return 0
 fi

 local admin_sh="$install_dir/bin/admin.sh"
 if [[ ! -x "$admin_sh" ]]; then
   log_error "未找到加密工具:$admin_sh"
   return 1
 fi

 local enc_out
 enc_out="$(cd "$install_dir/bin" && ./admin.sh encrypt id "$raw_password" 2>/dev/null | tr -d '\r\n')"
 if [[ $? -ne 0 || -z "$enc_out" ]]; then
   log_error "密码加密失败"
   return 1
 fi

 local encrypted_part
 encrypted_part="$(echo "$enc_out" | grep -o 'Tw80EncodedPrefixFlag[^[:space:]]*' | head -n1)"
 if [[ -z "$encrypted_part" ]]; then
   encrypted_part="$(echo "$enc_out" | awk '{print $NF}')"
 fi
 if [[ -z "$encrypted_part" ]]; then
   log_error "无法从加密输出中提取密码:$enc_out"
   return 1
 fi

 log_ok "密码加密完成"
 if [[ -n "$__outvar" ]]; then
   printf -v "$__outvar" '%s' "$encrypted_part"
 fi
 return 0
}

# ==================== 通用命令执行 ====================
execute_and_log() {
 local cmd="$1"
 local __outvar="${2:-}"

 local masked_cmd
 masked_cmd=$(mask_password_in_cmd "$cmd")

 local tmp
 tmp="$(mktemp /tmp/tongweb_cmd_XXXXXX.log)"

 log_step "CMD: $masked_cmd"

 eval "$cmd" >"$tmp" 2>&1
 local status=$?

 if [[ $status -eq 0 ]]; then
   log_ok "CMD exitCode=0"
 else
   log_error "CMD exitCode=$status"
 fi

 {
   echo "----- RAW CMD: $masked_cmd"
   cat "$tmp"
   echo "----- RAW END"
 } >>"$output_file"

 if [[ "$SHOW_CMD_OUTPUT" == "1" ]]; then
   cat "$tmp" >&2
 fi

 if [[ -n "$__outvar" ]]; then
   printf -v "$__outvar" '%s' "$(cat "$tmp")"
 fi

 rm -f "$tmp"
 return $status
}

# ==================== 命令行参数解析 ====================
parse_args() {
 while [[ $# -gt 0 ]]; do
   case "$1" in
     --host=*) host="${1#*=}"; shift ;;
     --host) host="$2"; shift 2 ;;
     --port=*) port="${1#*=}"; shift ;;
     --port) port="$2"; shift 2 ;;
     --username=*) new_username="${1#*=}"; shift ;;
     --username) new_username="$2"; shift 2 ;;
     --password=*) new_user_password="${1#*=}"; shift ;;
     --password) new_user_password="$2"; shift 2 ;;
     --install-dir=*) install_dir="${1#*=}"; shift ;;
     --install-dir) install_dir="$2"; shift 2 ;;
     --node-name=*) ARG_NODE_NAME="${1#*=}"; shift ;;
     --node-name) ARG_NODE_NAME="$2"; shift 2 ;;
     --node-ip=*) ARG_NODE_IP="${1#*=}"; shift ;;
     --node-ip) ARG_NODE_IP="$2"; shift 2 ;;
     --node-port=*) ARG_NODE_PORT="${1#*=}"; shift ;;
     --node-port) ARG_NODE_PORT="$2"; shift 2 ;;
     --ssh-port=*) ARG_SSH_PORT="${1#*=}"; shift ;;
     --ssh-port) ARG_SSH_PORT="$2"; shift 2 ;;
     --ssh-user=*) ARG_SSH_USER="${1#*=}"; shift ;;
     --ssh-user) ARG_SSH_USER="$2"; shift 2 ;;
     --ssh-password=*) ARG_SSH_PASSWORD="${1#*=}"; shift ;;
     --ssh-password) ARG_SSH_PASSWORD="$2"; shift 2 ;;
     --java-home=*) ARG_JAVA_HOME="${1#*=}"; shift ;;
     --java-home) ARG_JAVA_HOME="$2"; shift 2 ;;
     --install-path=*) ARG_INSTALL_PATH="${1#*=}"; shift ;;
     --install-path) ARG_INSTALL_PATH="$2"; shift 2 ;;
     --cluster-name=*) ARG_CLUSTER_NAME="${1#*=}"; shift ;;
     --cluster-name) ARG_CLUSTER_NAME="$2"; shift 2 ;;
     --instance-count=*) ARG_INSTANCE_COUNT="${1#*=}"; shift ;;
     --instance-count) ARG_INSTANCE_COUNT="$2"; shift 2 ;;
     --output-file=*) output_file="${1#*=}"; shift ;;
     --output-file) output_file="$2"; shift 2 ;;
     --skip-interactive) ARG_SKIP_INTERACTIVE="true"; shift ;;
     --show-cmd-output=*) SHOW_CMD_OUTPUT="${1#*=}"; shift ;;
     --show-cmd-output) SHOW_CMD_OUTPUT="$2"; shift 2 ;;
     --help|-h|help) show_usage; exit 0 ;;
     *) log_error "未知参数:$1"; show_usage; exit 1 ;;
   esac
 done
}

show_usage() {
 cat <<'EOF'
TongWeb 集群自动化部署脚本 - 单节点版

【模式 0】无参数(完全交互式)
  ./setup_tongweb_cluster_single.sh
  → 进入交互向导,按提示输入所有参数

【模式 1】最小化参数(半交互式)
  ./setup_tongweb_cluster_single.sh \
    --host=192.168.10.12 --port=9060 --username=thanos \
    --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909

【模式 2】完整参数(半自动)
  ./setup_tongweb_cluster_single.sh \
    --host=192.168.10.12 --port=9060 --username=thanos \
    --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909 \
    --node-ip=192.168.10.12 --ssh-user=root --ssh-password=Root123 \
    --java-home=/opt/bisheng-jdk1.8.0_432 --install-path=/opt/tw8node \
    --cluster-name=cluster01

【模式 3】全自动化(无交互)
  ./setup_tongweb_cluster_single.sh \
    --host=192.168.10.12 --port=9060 --username=thanos \
    --password=Yeyehuo163.com --install-dir=/opt/TongWeb7.0.8.909 \
    --node-name=node12 --node-ip=192.168.10.12 --node-port=9061 \
    --ssh-port=22 --ssh-user=root --ssh-password=Root123 \
    --java-home=/opt/bisheng-jdk1.8.0_432 --install-path=/opt/tw8node \
    --cluster-name=cluster01 --instance-count=2 \
    --skip-interactive --show-cmd-output=0

参数说明:
  必填:
    --host          TongWeb 管理主机 IP
    --port          TongWeb 管理端口
    --username      管理员用户名
    --password      管理员密码
    --install-dir   TongWeb 安装目录(包含 bin/commandstool.sh)

  可选:
    --node-name       节点名称(交互式可省略,自动提示输入)
    --node-ip         节点 IP(交互式可省略,自动获取当前服务器 IP)
    --node-port       节点管理端口(交互式不传会提示输入,自动化模式必填)
    --ssh-port        SSH 端口(交互式不传会提示输入,自动化模式必填)
    --ssh-user        SSH 用户名(必填,无默认值)
    --ssh-password    SSH 密码
    --java-home       JDK 路径
    --install-path    实例安装路径(必须是空目录)
    --cluster-name    集群名称(默认:cluster01)
    --instance-count  每节点实例数上限(默认:2)
    --skip-interactive 跳过交互,全自动化模式
    --show-cmd-output 命令输出开关(0=隐藏,1=显示,默认:1)
    --output-file     日志文件路径

注意:
  1. SSH 用户名(--ssh-user)必填,无默认值
  2. --node-port 和 --ssh-port 在交互模式下会提示输入,自动化模式必须传入
  3. --instance-count 控制每节点最多创建的实例数(上限 2)
  4. --show-cmd-output=0 可隐藏命令执行输出,保持日志简洁
  5. --install-path 必须是空目录
  6. 支持 --param=value 或 --param value 两种格式
EOF
}

# ==================== 获取服务器 IP ====================
get_server_ip() {
  local ip=""
  ip="$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[0-9.]+' | head -n1)"
  if [[ -n "$ip" ]]; then echo "$ip"; return 0; fi
  ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  if [[ -n "$ip" ]]; then echo "$ip"; return 0; fi
  ip="$(hostname -i 2>/dev/null | awk '{print $1}')"
  if [[ -n "$ip" ]]; then echo "$ip"; return 0; fi
  log_error "无法自动获取服务器 IP"
  return 1
}

# ==================== 基础检查 ====================
precheck() {
  # 如果没有任何参数传入,进入完全交互模式
  if [[ -z "$host" && -z "$port" && -z "$new_username" && -z "$new_user_password" && -z "$install_dir" ]]; then
    echo " ================================================="
    echo " TongWeb 集群自动化部署脚本 - 单节点版(交互模式)"
    echo " ================================================="
    echo ""
    echo "未检测到命令行参数,将进入交互式配置向导"
    echo "请按提示输入所需参数"
    echo ""
    
    # 交互式输入必填参数
    read -p "请输入 TongWeb 管理主机 IP (host): " host
    read -p "请输入 TongWeb 管理端口 (port,默认 9060): " port_input
    port="${port_input:-9060}"
    read -p "请输入管理员用户名 (username,默认 thanos): " username_input
    new_username="${username_input:-thanos}"
    read -sp "请输入管理员密码 (password): " new_user_password
    echo
    read -p "请输入 TongWeb 安装目录 (install-dir): " install_dir
    
    # 验证必填参数
    if [[ -z "$host" || -z "$new_user_password" || -z "$install_dir" ]]; then
      log_error "必填参数不能为空,已退出"
      exit 1
    fi
  fi
  
  # 单独检查每个必填参数(针对非交互模式)
  if [[ -z "$host" ]]; then
    log_error "缺少必填参数:--host"
    exit 1
  fi
  if [[ -z "$port" ]]; then
    log_error "缺少必填参数:--port"
    exit 1
  fi
  if [[ -z "$new_username" ]]; then
    log_error "缺少必填参数:--username"
    exit 1
  fi
  if [[ -z "$new_user_password" ]]; then
    log_error "缺少必填参数:--password"
    exit 1
  fi
  if [[ -z "$install_dir" ]]; then
    log_error "缺少必填参数:--install-dir"
    exit 1
  fi

  if [[ ! -d "$install_dir" ]]; then
    log_warn "install_dir 不存在:$install_dir"
    if [[ "$ARG_SKIP_INTERACTIVE" != "true" ]]; then
      while [[ ! -d "$install_dir" ]]; do
        read -r -p "请输入正确的 TongWeb 安装目录:" install_dir
      done
    else
      log_error "自动化模式下 install_dir 必须存在:$install_dir"
      exit 1
    fi
  fi
  
  for f in "${required_files[@]}"; do
    if [[ ! -f "$install_dir/bin/$f" ]]; then
      log_error "缺少文件:$install_dir/bin/$f"
      exit 1
    fi
  done

  if ! command -v jq >/dev/null 2>&1; then
    log_error "未找到 jq,请安装后重试(yum install -y jq)"
    exit 1
  fi
  
  if [[ -n "$ARG_INSTALL_PATH" ]]; then
    DEFAULT_INSTALLATION_PATH="$ARG_INSTALL_PATH"
  fi
  
  if [[ -n "$ARG_INSTANCE_COUNT" ]]; then
    MAX_INSTANCES_PER_NODE="$ARG_INSTANCE_COUNT"
  fi

  log_ok "前置检查通过"
}

cd_bin() {
  cd "$install_dir/bin" || { log_error "无法进入 $install_dir/bin"; return 1; }
}

is_int() { [[ "${1:-}" =~ ^[0-9]+$ ]]; }

prompt_nonempty() {
  local prompt="$1"
  local v=""
  while [[ -z "$v" ]]; do
    read -r -p "$prompt: " v || true
    v="${v//[[:space:]]/}"
  done
  echo "$v"
}

prompt_yn() {
  local prompt="$1"
  local default="${2:-N}"
  local ans=""
  read -r -p "$prompt (Y/N) [default:$default]: " ans || true
  ans="${ans:-$default}"
  [[ "$ans" == "Y" || "$ans" == "y" ]]
}

# ==================== Step 1: 检查是否已有集群 ====================
check_existing_cluster() {
  log_phase "Step 1: 检查是否已有集群"
  cd_bin || return 1
  
  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=cluster --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
--outputType=json pageSize=$PAGE_SIZE"

  execute_and_log "$cmd" json_output || return 1

  local total_size
  total_size="$(echo "$json_output" | jq -r '.attachments.totalSize // "0"' 2>/dev/null)"
  
  if [[ -z "$total_size" || ! "$total_size" =~ ^[0-9]+$ ]]; then
    log_error "无法解析集群列表 totalSize"
    return 1
  fi

  if [[ "$total_size" -gt 0 ]]; then
    log_warn "检测到已有 $total_size 个集群存在"
    log_warn "请使用 TongWeb 控制台进行管理和创建"
    log_warn "脚本退出"
    exit 0
  fi

  log_ok "未检测到现有集群,继续执行"
  return 0
}

# ==================== 检查节点是否存在 ====================
get_node_info_by_ip() {
  local check_ip="$1"

  if [[ -z "${check_ip:-}" ]]; then
    log_error "get_node_info_by_ip 缺少 IP 参数"
    echo "NOT_FOUND"
    return 1
  fi
  if ! [[ "$check_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    log_error "IP 格式不正确:$check_ip"
    echo "NOT_FOUND"
    return 1
  fi

  cd_bin || { echo "NOT_FOUND"; return 1; }

  log_step "检查 IP [$check_ip] 是否已存在 TongWeb 节点"

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=node --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
--outputType=json ip=$check_ip"

  execute_and_log "$cmd" json_output || { echo "NOT_FOUND"; return 1; }

  local total_size
  total_size="$(echo "$json_output" | jq -r '.attachments.totalSize // "0"' 2>/dev/null)"
  if [[ -z "$total_size" ]]; then
    log_error "无法解析 totalSize"
    echo "NOT_FOUND"
    return 1
  fi

  if [[ "$total_size" -ne 1 ]]; then
    log_ok "IP [$check_ip] 未安装 TongWeb 节点(totalSize=$total_size)"
    echo "NOT_FOUND"
    return 1
  fi

  local node_name node_ip node_port node_running
  node_name="$(echo "$json_output" | jq -r '.models[0].name // empty' 2>/dev/null)"
  node_ip="$(echo "$json_output" | jq -r '.models[0].ip // empty' 2>/dev/null)"
  node_port="$(echo "$json_output" | jq -r '.models[0].port // empty' 2>/dev/null)"
  node_running="$(echo "$json_output" | jq -r '.models[0].running // empty' 2>/dev/null)"

  if [[ -z "$node_name" || -z "$node_ip" || -z "$node_port" || -z "$node_running" ]]; then
    log_error "totalSize=1 但未提取到必要字段"
    echo "NOT_FOUND"
    return 1
  fi

  log_ok "IP [$check_ip] 已存在节点:$node_name (running=$node_running)"
  echo "$node_name"
  echo "$node_ip"
  echo "$node_port"
  echo "$node_running"
  return 0
}

check_node_in_cluster() {
  local node_name="$1"
  if [[ -z "${node_name:-}" ]]; then
    echo "NOT_IN_CLUSTER"
    return 1
  fi

  cd_bin || { echo "NOT_IN_CLUSTER"; return 1; }

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=cluster --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
--outputType=json --targetType=node --targetName=$node_name"

  execute_and_log "$cmd" json_output || { echo "NOT_IN_CLUSTER"; return 1; }

  local total_size
  total_size="$(echo "$json_output" | jq -r '.attachments.totalSize // "0"' 2>/dev/null)"
  
  if [[ "$total_size" -gt 0 ]]; then
    echo "IN_CLUSTER"
    return 0
  fi

  echo "NOT_IN_CLUSTER"
  return 1
}

# ==================== Step 2: 检查是否已有节点 ====================
check_existing_node() {
  log_phase "Step 2: 检查是否已有节点"
  
  local node_ip=""
  if [[ -n "$ARG_NODE_IP" ]]; then
    node_ip="$ARG_NODE_IP"
    log_step "使用传入的节点 IP: $node_ip"
  else
    node_ip="$(get_server_ip)" || return 1
    log_step "自动获取服务器 IP: $node_ip"
  fi
  
  # 用户确认 IP
  if [[ "$ARG_SKIP_INTERACTIVE" != "true" ]]; then
    local confirm_ip=""
    read -r -p "当前服务器 IP 为 [$node_ip],是否正确?(Y/N) [default:Y]: " confirm_ip || true
    confirm_ip="${confirm_ip:-Y}"
    if [[ "$confirm_ip" != "Y" && "$confirm_ip" != "y" ]]; then
      node_ip="$(prompt_nonempty "请输入正确的节点 IP")"
    fi
  fi
  
  # 检查该 IP 是否已有节点
  local info=()
  if mapfile -t info < <(get_node_info_by_ip "$node_ip"); then
    :
  else
    info=("NOT_FOUND")
  fi

  if [[ "${info[0]}" != "NOT_FOUND" ]]; then
    local node_name="${info[0]}"
    log_ok "检测到已有节点:$node_name"
    
    local in_cluster
    in_cluster="$(check_node_in_cluster "$node_name")" || true
    if [[ "$in_cluster" == "IN_CLUSTER" ]]; then
      log_warn "节点 [$node_name] 已属于某个集群,跳过"
      return 1
    fi
    
    ARG_NODE_NAME="$node_name"
    ARG_NODE_IP="${info[1]}"
    ARG_NODE_PORT="${info[2]}"
    
    log_ok "使用已有节点:name=$ARG_NODE_NAME ip=$ARG_NODE_IP port=$ARG_NODE_PORT"
    return 0
  fi

  log_warn "该 IP 未检测到节点,需要创建节点"
  ARG_NODE_IP="$node_ip"
  return 1
}

# ==================== 创建节点 ====================
create_tongweb_node() {
  log_phase "创建 TongWeb 远程节点(SSH 方式)"

  local name="" ip="" node_port="" ssh_port="" ssh_user="" ssh_pass="" java_home="" install_path="" autostart=""

  if [[ -n "$ARG_NODE_NAME" ]]; then
    name="$ARG_NODE_NAME"
    log_step "使用传入的节点名称:$name"
  else
    name="$(prompt_nonempty "节点名称(唯一标识,如 node12)")"
  fi

  ip="${ARG_NODE_IP:-}"
  if [[ -z "$ip" ]]; then
    log_error "节点 IP 不能为空"
    return 1
  fi

  # 节点端口处理
  if [[ -n "$ARG_NODE_PORT" ]]; then
    node_port="$ARG_NODE_PORT"
    log_step "使用传入的节点端口:$node_port"
  elif [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    log_error "自动化模式需传入 --node-port"
    return 1
  else
    node_port="$(prompt_nonempty "节点管理端口(默认 9061)")"
    [[ -z "$node_port" ]] && node_port="9061"
    log_step "使用节点端口:$node_port"
  fi

  # SSH 端口处理
  if [[ -n "$ARG_SSH_PORT" ]]; then
    ssh_port="$ARG_SSH_PORT"
    log_step "使用传入的 SSH 端口:$ssh_port"
  elif [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    log_error "自动化模式需传入 --ssh-port"
    return 1
  else
    ssh_port="$(prompt_nonempty "SSH 端口(默认 22)")"
    [[ -z "$ssh_port" ]] && ssh_port="22"
    log_step "使用 SSH 端口:$ssh_port"
  fi

  ssh_user="${ARG_SSH_USER:-}"
  ssh_pass="${ARG_SSH_PASSWORD:-}"
  
  if [[ -z "$ssh_user" ]]; then
    if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
      log_error "SSH 用户名不能为空(自动化模式需传入 --ssh-user)"
      return 1
    fi
    ssh_user="$(prompt_nonempty "SSH 用户名")"
  fi
  
  if [[ -z "$ssh_pass" ]]; then
    if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
      log_error "SSH 密码不能为空(自动化模式需传入 --ssh-password)"
      return 1
    fi
    read -r -s -p "SSH 密码:" ssh_pass
    echo
  fi

  if [[ -n "$ARG_JAVA_HOME" ]]; then
    java_home="$ARG_JAVA_HOME"
    log_step "使用传入的 Java Home: $java_home"
  elif [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    log_error "自动化模式需传入 --java-home"
    return 1
  else
    java_home="$(prompt_nonempty "Java Home 绝对路径(如 /opt/bisheng-jdk1.8.0_432)")"
  fi

  if [[ -n "$ARG_INSTALL_PATH" ]]; then
    install_path="$ARG_INSTALL_PATH"
    log_step "使用传入的安装路径:$install_path"
    if [[ -d "$install_path" ]]; then
      local file_count
      file_count="$(ls -A "$install_path" 2>/dev/null | wc -l)"
      if [[ "$file_count" -gt 0 ]]; then
        log_error "安装路径不是空目录:$install_path(当前有 $file_count 个文件/目录)"
        if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
          return 1
        fi
        while [[ -d "$install_path" && "$file_count" -gt 0 ]]; do
          read -r -p "请输入一个空目录作为安装路径:" install_path
          if [[ -d "$install_path" ]]; then
            file_count="$(ls -A "$install_path" 2>/dev/null | wc -l)"
          fi
        done
      fi
    fi
  elif [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    log_error "自动化模式需传入 --install-path"
    return 1
  else
    while true; do
      install_path="$(prompt_nonempty "TongWeb 实例安装路径(必须是空目录,如 /opt/tw8node)")"
      if [[ -d "$install_path" ]]; then
        local file_count
        file_count="$(ls -A "$install_path" 2>/dev/null | wc -l)"
        if [[ "$file_count" -gt 0 ]]; then
          log_warn "该目录不是空目录(当前有 $file_count 个文件/目录),请重新输入"
          continue
        fi
      fi
      break
    done
  fi

  if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    autostart="false"
  else
    while true; do
      read -r -p "是否开机自启?(true/false) [default:false]: " autostart
      autostart="${autostart:-false}"
      [[ "$autostart" == "true" || "$autostart" == "false" ]] && break
      log_warn "必须输入 true 或 false"
    done
  fi

  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=node --action=add \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
name=$name ip=$ip port=$node_port sshPort=$ssh_port sshUserName=$ssh_user sshPassword=$ssh_pass \
javaHome=$java_home installationPath=$install_path autostart=$autostart keyPairType=ssh-rsa \
passwordType=PASSWORD maxretrycount=0 synchronizeData=false nodeCreationType=SSH"

  execute_and_log "$cmd" json_output || { log_error "节点创建命令执行失败"; return 1; }

  local success_flag msg_text
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"
  msg_text="$(echo "$json_output" | jq -r '.msg // empty' 2>/dev/null)"

  if [[ "$success_flag" == "true" ]]; then
    log_ok "节点 [$name] 创建成功"
    ARG_NODE_NAME="$name"
    return 0
  fi

  log_error "节点 [$name] 创建失败:${msg_text:-}"
  return 1
}

# ==================== 创建集群 ====================
create_tongweb_cluster() {
  local nodes_preset="${1:-}"

  log_phase "创建 TongWeb 集群"

  local name="" node=""

  if [[ -n "$ARG_CLUSTER_NAME" ]]; then
    name="$ARG_CLUSTER_NAME"
  else
    name="$(prompt_nonempty "集群名称(如 cluster01)")"
  fi

  if [[ -n "$nodes_preset" ]]; then
    node="$nodes_preset"
  elif [[ -n "$ARG_NODE_NAME" ]]; then
    node="$ARG_NODE_NAME"
  else
    log_error "节点名称不能为空"
    return 1
  fi

  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=cluster --action=add \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
name=$name node=$node autoUpdateLbConf=true startwithnode=false \
accessLogPath=logs/access.log maxretrycount=0 accessLogEnabled=false \
autostart=false ipHash=true rollingUpdate=false"

  execute_and_log "$cmd" json_output || { log_error "集群创建命令执行失败"; return 1; }

  local success_flag msg_text
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"
  msg_text="$(echo "$json_output" | jq -r '.msg // empty' 2>/dev/null)"

  if [[ "$success_flag" == "true" ]]; then
    log_ok "集群 [$name] 创建成功"
    cluster_name="$name"
    nodes_str="$node"
    return 0
  fi

  log_error "集群 [$name] 创建失败:${msg_text:-}"
  return 1
}

# ==================== 实例查询 ====================
list_instances_by_cluster() {
  local cluster="$1"
  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
--targetType=cluster --targetName=$cluster pageSize=$PAGE_SIZE"

  execute_and_log "$cmd" json_output || return 1
  printf '%s' "$json_output"
}

list_instances_by_node() {
  local node="$1"
  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
node=$node pageSize=$PAGE_SIZE"

  execute_and_log "$cmd" json_output || return 1
  printf '%s' "$json_output"
}

add_instance() {
  local node="$1"
  local cluster="$2"
  local name="$3"
  local appPort="$4"
  local inst_port="$5"
  local installationPath="$6"

  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=add \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
node=$node cluster=$cluster needConsolePort=$INSTANCE_NEED_CONSOLE_PORT name=$name \
startwithnode=$DEFAULT_STARTWITHNODE appPort=$appPort port=$inst_port maxretrycount=$DEFAULT_MAXRETRYCOUNT \
installationPath=$installationPath autostart=$DEFAULT_AUTOSTART"

  execute_and_log "$cmd" json_output || return 1

  local success_flag msg_text
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"
  msg_text="$(echo "$json_output" | jq -r '.msg // empty' 2>/dev/null)"

  if [[ "$success_flag" == "true" ]]; then
    log_ok "实例 [$name] 新增成功"
    return 0
  fi

  log_error "实例 [$name] 新增失败:${msg_text:-}"
  return 1
}

node_instance_count() {
  local node="$1"
  local json
  json="$(list_instances_by_node "$node")" || { echo 0; return 0; }
  echo "$json" | jq -r '.models | length' 2>/dev/null || echo 0
}

node_has_port() {
  local node="$1" p="$2"
  local json
  json="$(list_instances_by_node "$node")" || return 1
  echo "$json" | jq -e --arg p "$p" '.models[]? | select(.port==$p)' >/dev/null 2>&1
}

node_has_app_port() {
  local node="$1" ap="$2"
  local json
  json="$(list_instances_by_node "$node")" || return 1
  echo "$json" | jq -e --arg ap "$ap" '.models[]? | select(.appPort==$ap)' >/dev/null 2>&1
}

node_max_index_for_prefix() {
  local node="$1"
  local prefix="$2"
  local json
  json="$(list_instances_by_node "$node")" || { echo 0; return 0; }
  echo "$json" | jq -r '.models[].name' 2>/dev/null | awk -v pfx="$prefix" '
  index($0,pfx)==1 {
    if (match($0, /-([0-9]+)$/, a)) {
      n=a[1]+0
      if (n>max) max=n
    }
  }
  END{ if (max=="") max=0; print max }'
}

# ==================== Step 4: 查询实例并检查上限 ====================
check_and_add_instances() {
  log_phase "Step 4: 查询实例并检查上限"
  
  if [[ -z "$cluster_name" || -z "$nodes_str" ]]; then
    log_error "集群名称或节点列表为空"
    return 1
  fi
  
  local json
  json="$(list_instances_by_cluster "$cluster_name")" || {
    log_warn "无法查询实例列表"
    return 1
  }
  
  local current_count
  current_count="$(echo "$json" | jq -r '.models | length' 2>/dev/null || echo 0)"
  
  log_step "当前集群实例数:$current_count / $MAX_INSTANCES_PER_NODE"
  
  if (( current_count >= MAX_INSTANCES_PER_NODE )); then
    log_ok "实例数已达上限 ($MAX_INSTANCES_PER_NODE),跳过添加"
    return 0
  fi
  
  local remaining=$((MAX_INSTANCES_PER_NODE - current_count))
  
  log_phase "当前实例列表"
  echo "$json" | jq -r '
    if (.models|type)=="array" then
      .models[] | "  instance=\(.name) node=\(.node) port=\(.port) appPort=\(.appPort) running=\(.running)"
    else
      "  无实例"
    end
  ' 2>/dev/null | while read -r line; do
    [[ -n "$line" ]] && log_step "$line"
  done
  
  if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
    log_step "自动化模式:自动补齐实例至上限"
  else
    if ! prompt_yn "是否需要新增 $remaining 个实例?" "Y"; then
      log_warn "用户选择不添加实例"
      return 0
    fi
  fi
  
  local node
  IFS=',' read -r -a nodes <<<"$nodes_str"
  node="${nodes[0]}"
  
  local prefix="${cluster_name}-${node}-"
  local max_idx
  max_idx="$(node_max_index_for_prefix "$node" "$prefix")"
  
  local i
  for ((i=1; i<=remaining; i++)); do
    local idx=$((max_idx + i))
    local inst_name="${prefix}${idx}"
    
    local base_port="${ARG_NODE_PORT:-9061}"
    local inst_port=$((base_port + i * 10))
    local inst_app_port=$((8080 + i))
    
    while node_has_port "$node" "$inst_port"; do
      ((inst_port++))
    done
    while node_has_app_port "$node" "$inst_app_port"; do
      ((inst_app_port++))
    done
    
    log_step "新增实例:name=$inst_name port=$inst_port appPort=$inst_app_port"
    add_instance "$node" "$cluster_name" "$inst_name" "$inst_app_port" "$inst_port" "$DEFAULT_INSTALLATION_PATH" || {
      log_error "实例添加失败:$inst_name"
      continue
    }
  done
  
  log_ok "实例添加完成"
  return 0
}

# ==================== 端口展示 ====================
print_cluster_nodes_ports() {
  local cluster="$1"
  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=node --action=list \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true \
--outputType=json --targetType=cluster --targetName=$cluster pageSize=$PAGE_SIZE"

  execute_and_log "$cmd" json_output || return 1
  
  log_phase "集群 [$cluster] 节点管理端口清单"
  echo "$json_output" | jq -r '
    if (.models|type)=="array" then
      .models[] | "node=\(.name) ip=\(.ip) nodePort=\(.port) running=\(.running)"
    else
      "NO_MODELS"
    end
  ' 2>/dev/null | while read -r line; do
    [[ -n "$line" && "$line" != "NO_MODELS" ]] && log_step "$line"
  done
}

print_cluster_instances_ports() {
  local cluster="$1"
  local json
  json="$(list_instances_by_cluster "$cluster")" || { log_warn "无法查询集群实例信息"; return 1; }

  log_phase "集群 [$cluster] 实例端口清单"
  echo "$json" | jq -r '
    if (.models|type)=="array" then
      .models[] | "instance=\(.name) node=\(.node) port=\(.port) appPort=\(.appPort) running=\(.running)"
    else
      "NO_MODELS"
    end
  ' 2>/dev/null | while read -r line; do
    [[ -n "$line" && "$line" != "NO_MODELS" ]] && log_step "$line"
  done
}

# ==================== 实例启停 ====================
stop_instance() {
  local inst_name="$1"
  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=stop \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
name=$inst_name"

  execute_and_log "$cmd" json_output || return 1

  local success_flag
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"

  [[ "$success_flag" == "true" ]] && { log_ok "实例 [$inst_name] stop 成功"; return 0; }
  log_error "实例 [$inst_name] stop 失败"
  return 1
}

start_instance() {
  local inst_name="$1"
  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=start \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
name=$inst_name"

  execute_and_log "$cmd" json_output || return 1

  local success_flag
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"

  [[ "$success_flag" == "true" ]] && { log_ok "实例 [$inst_name] start 成功"; return 0; }
  log_error "实例 [$inst_name] start 失败"
  return 1
}

update_instance_ports() {
  local inst_name="$1"
  local new_port="$2"
  local new_app_port="$3"

  cd_bin || return 1

  local cmd json_output
  cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=update \
--username=$new_username --password=\"$new_user_password\" --acceptAgreement=true --outputType=json \
port=$new_port needConsolePort=$INSTANCE_NEED_CONSOLE_PORT name=$inst_name startwithnode=$DEFAULT_STARTWITHNODE \
alias=$inst_name appPort=$new_app_port maxretrycount=$DEFAULT_MAXRETRYCOUNT autostart=$DEFAULT_AUTOSTART"

  execute_and_log "$cmd" json_output || return 1

  local success_flag
  success_flag="$(echo "$json_output" | jq -r '.success // "false"' 2>/dev/null)"

  [[ "$success_flag" == "true" ]] && { log_ok "实例 [$inst_name] update 成功"; return 0; }
  log_error "实例 [$inst_name] update 失败"
  return 1
}

update_instances_ports_flow_interactive() {
  local cluster="$1"

  log_phase "实例端口更新(优化版:合并查询)"

  local json
  json="$(list_instances_by_cluster "$cluster")" || return 1

  local inst_count
  inst_count="$(echo "$json" | jq -r '.models|length' 2>/dev/null || echo 0)"
  if ! is_int "$inst_count" || (( inst_count <= 0 )); then
    log_warn "集群 [$cluster] 未查询到实例,跳过"
    return 0
  fi

  local i
  for ((i=0; i<inst_count; i++)); do
    local inst_name inst_node inst_port inst_app_port inst_running
    inst_name="$(echo "$json" | jq -r ".models[$i].name // empty" 2>/dev/null)"
    inst_node="$(echo "$json" | jq -r ".models[$i].node // empty" 2>/dev/null)"
    inst_port="$(echo "$json" | jq -r ".models[$i].port // empty" 2>/dev/null)"
    inst_app_port="$(echo "$json" | jq -r ".models[$i].appPort // empty" 2>/dev/null)"
    inst_running="$(echo "$json" | jq -r ".models[$i].running // empty" 2>/dev/null)"

    [[ -z "$inst_name" ]] && continue

    log_step "实例:name=$inst_name node=$inst_node port=$inst_port appPort=$inst_app_port running=$inst_running"

    if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
      continue
    fi

    if ! prompt_yn "是否需要更新该实例 [$inst_name] 的 port/appPort?" "N"; then
      continue
    fi

    local new_port new_app
    while true; do
      new_port="$(prompt_nonempty "请输入新 port(实例管理端口)")"
      is_int "$new_port" || { log_warn "port 必须为整数"; continue; }
      break
    done
    while true; do
      new_app="$(prompt_nonempty "请输入新 appPort(业务端口)")"
      is_int "$new_app" || { log_warn "appPort 必须为整数"; continue; }
      break
    done

    # 合并端口检查(只查询 1 次)
    local has_conflict="false"
    local conflict_msg=""
    if [[ -n "$inst_node" ]]; then
      local node_json
      node_json="$(list_instances_by_node "$inst_node")"
      
      local port_owner
      port_owner="$(echo "$node_json" | jq -r --arg p "$new_port" '.models[]? | select(.port==$p and .name!=$inst_name) | .name' 2>/dev/null | head -n1)"
      if [[ -n "$port_owner" ]]; then
        has_conflict="true"
        conflict_msg="新 port=$new_port 已被实例 [$port_owner] 占用"
      fi
      
      local appport_owner
      appport_owner="$(echo "$node_json" | jq -r --arg ap "$new_app" '.models[]? | select(.appPort==$ap and .name!=$inst_name) | .name' 2>/dev/null | head -n1)"
      if [[ -n "$appport_owner" ]]; then
        has_conflict="true"
        conflict_msg="${conflict_msg:+$conflict_msg; }新 appPort=$new_app 已被实例 [$appport_owner] 占用"
      fi
      
      if [[ "$has_conflict" == "true" ]]; then
        log_error "端口冲突:$conflict_msg,跳过:$inst_name"
        continue
      fi
    fi

    log_step "端口检查通过:port=$new_port appPort=$new_app"
    
    if ! prompt_yn "确认更新实例 [$inst_name] 为 port=$new_port appPort=$new_app,并自动 start?" "Y"; then
      log_warn "已取消更新:$inst_name"
      continue
    fi

    if [[ "$inst_running" == "true" ]]; then
      stop_instance "$inst_name" || continue
    fi

    update_instance_ports "$inst_name" "$new_port" "$new_app" || continue
    start_instance "$inst_name" || { log_warn "start 失败,请人工检查:$inst_name"; continue; }

    log_ok "实例 [$inst_name] 端口更新完成:port=$new_port appPort=$new_app"
  done

  log_phase "实例端口更新环节结束"
}

# ==================== 生成启停脚本 ====================
generate_start_stop_scripts() {
  log_phase "生成集群/节点/实例启停脚本"

  local encrypted_password=""
  encrypt_password_if_needed "$new_user_password" encrypted_password || {
    log_error "密码加密失败,无法生成脚本"
    return 1
  }

  local timestamp_dir
  timestamp_dir=$(date "+%Y%m%d%H%M%S")
  local script_root_dir="$install_dir/cluster_scripts_${timestamp_dir}"
  local cluster_dir="$script_root_dir/cluster"
  local node_dir="$script_root_dir/node"
  local instance_dir="$script_root_dir/instance"
  
  mkdir -p "$cluster_dir" "$node_dir" "$instance_dir"

  cat > "$cluster_dir/start_cluster_$cluster_name.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=cluster --action=start \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$cluster_name
EOF
  cat > "$cluster_dir/stop_cluster_$cluster_name.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=cluster --action=stop \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$cluster_name
EOF

  IFS=',' read -r -a nodes <<<"$nodes_str"
  for node in "${nodes[@]}"; do
    cat > "$node_dir/start_node_$node.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=node --action=start \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$node
EOF
    cat > "$node_dir/stop_node_$node.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=node --action=stop \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$node
EOF
  done

  local json
  json="$(list_instances_by_cluster "$cluster_name")" || return 0
  
  local inst_count
  inst_count="$(echo "$json" | jq -r '.models | length' 2>/dev/null || echo 0)"
  
  local i
  for ((i=0; i<inst_count; i++)); do
    local inst_name
    inst_name="$(echo "$json" | jq -r ".models[$i].name // empty" 2>/dev/null)"
    [[ -z "$inst_name" ]] && continue
    
    cat > "$instance_dir/start_instance_$inst_name.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=instance --action=start \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$inst_name
EOF
    cat > "$instance_dir/stop_instance_$inst_name.sh" <<EOF
#!/bin/bash
cd "$install_dir/bin" || exit 1
./commandstool.sh --host=$host --port=$port --model=instance --action=stop \
--username=$new_username --password="$encrypted_password" --acceptAgreement=true name=$inst_name
EOF
  done

  chmod +x "$cluster_dir"/*.sh "$node_dir"/*.sh "$instance_dir"/*.sh 2>/dev/null || true
  log_ok "启停脚本已生成至:$script_root_dir"
}

# ==================== 检查并启动集群/节点/实例 ====================
check_and_start_all_interactive(){
  log_phase "检查并启动集群/节点/实例"

  if [[ "$ARG_SKIP_INTERACTIVE" != "true" ]]; then
    if ! prompt_yn "是否需要检查集群、节点、实例的运行状态?" "Y"; then
      log_warn "跳过状态检查与启动环节"
      return 0
    fi
  fi

  local encrypted_password=""
  encrypt_password_if_needed "$new_user_password" encrypted_password || return 1

  if [[ -z "$cluster_name" ]]; then
    if [[ -n "$ARG_CLUSTER_NAME" ]]; then
      cluster_name="$ARG_CLUSTER_NAME"
    elif [[ "$ARG_SKIP_INTERACTIVE" == "true" ]]; then
      log_error "自动化模式需传入 --cluster-name"
      return 1
    else
      cluster_name="$(prompt_nonempty "请输入要检查的集群名称")"
    fi
  fi

  log_phase "查询集群 [$cluster_name] 信息"
  cd_bin || return 1
  
  local cluster_list_json
  local cmd
  cmd="./commandstool.sh --host=$host --port=$port --model=cluster --action=list \
--username=$new_username --password=\"$encrypted_password\" --acceptAgreement=true \
--outputType=json pageSize=100"
  
  execute_and_log "$cmd" cluster_list_json || return 1
  cluster_list_json="${cluster_list_json//$'\r'/}"

  if [[ ${#cluster_list_json} -eq 0 ]]; then
    log_error "cluster_list_json 为空"
    return 1
  fi

  local found_count
  found_count="$(echo "$cluster_list_json" | jq --arg cn "$cluster_name" '[.models[]? | select(.name == $cn)] | length')"
  if [[ "$found_count" -eq 0 ]]; then
    log_error "未找到集群 [$cluster_name]"
    return 1
  fi

  log_ok "找到集群:$cluster_name"

  local cluster_node_csv
  cluster_node_csv="$(echo "$cluster_list_json" | jq -r --arg cn "$cluster_name" '.models[]? | select(.name == $cn) | .node // empty')"
  local nodes=()
  if [[ -n "$cluster_node_csv" ]]; then
    IFS=',' read -ra nodes <<< "$cluster_node_csv"
    log_ok "集群节点:${nodes[*]}"
  fi

  local cluster_instances_csv instance_count
  cluster_instances_csv="$(echo "$cluster_list_json" | jq -r --arg cn "$cluster_name" '.models[]? | select(.name == $cn) | .instances // empty')"
  instance_count="$(echo "$cluster_list_json" | jq -r --arg cn "$cluster_name" '.models[]? | select(.name == $cn) | .instanceCount // "0"')"
  local instances=()
  if [[ -n "$cluster_instances_csv" ]]; then
    IFS=',' read -ra instances <<< "$cluster_instances_csv"
    log_ok "集群实例 ($instance_count 个): ${instances[*]}"
  fi

  # 启动集群
  local cluster_running
  cluster_running="$(echo "$cluster_list_json" | jq -r --arg cn "$cluster_name" '.models[]? | select(.name == $cn) | .running // "false"')"
  if [[ "$cluster_running" != "true" ]]; then
    if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]] || prompt_yn "集群 [$cluster_name] 未运行,是否启动?" "Y"; then
      local retry=0 max_retry=3
      while (( retry < max_retry )); do
        local cmd="./commandstool.sh --host=$host --port=$port --model=cluster --action=start \
--username=$new_username --password=\"$encrypted_password\" --acceptAgreement=true --outputType=json name=$cluster_name"
        execute_and_log "$cmd" && { log_ok "集群启动成功"; break; }
        log_error "集群启动失败 (尝试 $((retry+1))/$max_retry)"
        ((retry++)); sleep 2
      done
    fi
  else
    log_ok "集群已运行"
  fi

  # 启动节点
  for node in "${nodes[@]}"; do
    local nodes_json node_running
    nodes_json="$(list_instances_by_cluster "$cluster_name")"
    node_running="$(echo "$nodes_json" | jq -r --arg n "$node" '.models[]? | select(.name==$n) | .running // "false"')"
    
    if [[ "$node_running" != "true" ]]; then
      if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]] || prompt_yn "节点 [$node] 未运行,是否启动?" "Y"; then
        local retry=0 max_retry=3
        while (( retry < max_retry )); do
          local cmd="./commandstool.sh --host=$host --port=$port --model=node --action=start \
--username=$new_username --password=\"$encrypted_password\" --acceptAgreement=true --outputType=json name=$node"
          execute_and_log "$cmd" && { log_ok "节点 [$node] 启动成功"; break; }
          log_error "节点启动失败 (尝试 $((retry+1))/$max_retry)"
          ((retry++)); sleep 2
        done
      fi
    else
      log_ok "节点 [$node] 已运行"
    fi
  done

  # 启动实例
  for inst in "${instances[@]}"; do
    local inst_json inst_running
    inst_json="$(list_instances_by_cluster "$cluster_name")"
    inst_running="$(echo "$inst_json" | jq -r --arg i "$inst" '.models[]? | select(.name==$i) | .running // "false"')"
    
    if [[ "$inst_running" != "true" ]]; then
      if [[ "$ARG_SKIP_INTERACTIVE" == "true" ]] || prompt_yn "实例 [$inst] 未运行,是否启动?" "Y"; then
        local retry=0 max_retry=3
        while (( retry < max_retry )); do
          local cmd="./commandstool.sh --host=$host --port=$port --model=instance --action=start \
--username=$new_username --password=\"$encrypted_password\" --acceptAgreement=true --outputType=json name=$inst"
          execute_and_log "$cmd" && { log_ok "实例 [$inst] 启动成功"; break; }
          log_error "实例启动失败 (尝试 $((retry+1))/$max_retry)"
          ((retry++)); sleep 2
        done
      fi
    else
      log_ok "实例 [$inst] 已运行"
    fi
  done

  generate_start_stop_scripts || true
}

# ==================== 主流程 ====================
main() {
  parse_args "$@"
  precheck
  
  log_phase "TongWeb 集群自动化部署开始(单节点版)"
  log_ok "日志文件:$output_file"

  check_existing_cluster || exit 1

  local node_exists="false"
  check_existing_node && node_exists="true"

  if [[ "$node_exists" == "false" ]]; then
    create_tongweb_node || exit 1
  fi

  if [[ -n "$ARG_NODE_NAME" ]]; then
    create_tongweb_cluster "$ARG_NODE_NAME" || exit 1
  else
    log_error "节点名称为空,无法创建集群"
    exit 1
  fi

  check_and_add_instances || true

  print_cluster_nodes_ports "$cluster_name" || true
  print_cluster_instances_ports "$cluster_name" || true

  if [[ "$ARG_SKIP_INTERACTIVE" != "true" ]]; then
    if prompt_yn "是否进入【实例端口更新】环节?" "N"; then
      update_instances_ports_flow_interactive "$cluster_name" || true
      print_cluster_instances_ports "$cluster_name" || true
    fi
  fi

  check_and_start_all_interactive

  log_phase "脚本结束"
  log_ok "日志文件:$output_file"
}

main "$@"

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐