#!/bin/bash #!/bin/bash # 移除 set -e,改为手动控制错误,防止意外退出 set -o pipefail # ================= 1. 加载环境变量 (.env) ================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${SCRIPT_DIR}/.env" load_env_file() { if [ -f "$ENV_FILE" ]; then while IFS= read -r line || [[ -n "$line" ]]; do # 去除首尾空白 line=$(echo "$line" | xargs) # 跳过空行和注释 [[ -z "$line" || "$line" == \#* ]] && continue # 简单的格式校验 (KEY=VALUE) if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)= ]]; then key="${BASH_REMATCH[1]}" # 只有当该变量尚未在环境中设置时,才从文件加载 (支持命令行覆盖) if [ -z "${!key}" ]; then export "$line" fi fi done < "$ENV_FILE" echo -e "\033[0;36m[CONFIG]\033[0m 已加载配置:$ENV_FILE" else # 如果文件不存在,不报错,依赖后续检查或命令行传入 : fi } # 执行加载 load_env_file # ================= 2. 颜色与日志 ================= RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "${BLUE}[STEP]${NC} $1"; } log_success() { echo -e "${CYAN}[OK]${NC} $1"; } log_header() { echo "" echo "========================================" echo -e "${BLUE}$1${NC}" echo "========================================" } # ================= 3. 配置与初始化 ================= HOST_FILE="${SCRIPT_DIR}/host_list.txt" SCRIPT_METADATA="${SCRIPT_DIR}/3.1.deploy_metadata_single.sh" # ================= 4. 关键变量预检 ================= log_header "������ metadata全自动部署启动" # 检查必要的环境变量 if [ -z "$REDIS_PASSWORD" ]; then log_error "❌ 缺少环境变量 REDIS_PASSWORD" log_info "请在 ${ENV_FILE} 中配置,或使用命令运行: REDIS_PASSWORD=xxxx ./3.1.deploy_metadata_single.sh" exit 1 fi log_info "已加载密码:REDIS_PASSWORD=****** (长度:${#REDIS_PASSWORD})" # ================= 5. 前置文件检查 ================= # 1. 检查 host_list.txt if [ ! -f "$HOST_FILE" ]; then log_error "找不到主机列表文件:$HOST_FILE" exit 1 fi log_info "主机列表:$HOST_FILE" log_info "工作目录:$SCRIPT_DIR" # ================= 6. 按区块解析主机列表 ================= log_step "解析 [metadata] 区块主机列表..." # 定义函数:提取指定区块的节点 get_section_nodes() { local target_section="$1" local current_section="" local in_target=false while IFS= read -r line || [[ -n "$line" ]]; do # 跳过空行 [[ -z "${line// /}" ]] && continue # 跳过注释行 [[ "$line" =~ ^[[:space:]]*# ]] && continue # 检测区块标记 if [[ "$line" =~ ^\[([a-zA-Z0-9_-]+)\]$ ]]; then current_section="${BASH_REMATCH[1]}" if [ "$current_section" == "$target_section" ]; then in_target=true else in_target=false fi continue fi # 只在目标区块内输出数据 if [ "$in_target" == true ]; then echo "$line" fi done < "$HOST_FILE" } declare -a NODE_NAMES declare -a NODE_IPS declare -a NODE_USERS declare -a DATA_PATHS COUNT=0 while IFS= read -r line || [[ -n "$line" ]]; do # 使用 awk 提取,避免 shell 词法解析特殊字符的问题 name=$(echo "$line" | awk '{print $1}') ip=$(echo "$line" | awk '{print $2}') user=$(echo "$line" | awk '{print $3}') data_path=$(echo "$line" | awk '{print $5}') if [ -z "$name" ] || [ -z "$data_path" ]; then log_warn "跳过格式错误的行 (缺少列): $line" continue fi # 存入数组 NODE_NAMES+=("$name") NODE_IPS+=("$ip") NODE_USERS+=("$user") DATA_PATHS+=("$data_path") ((COUNT++)) done < <(get_section_nodes "metadata") if [ $COUNT -eq 0 ]; then log_error "未找到 [metadata] 区块的有效节点配置,请检查 $HOST_FILE 格式" exit 1 fi log_success "共解析到 $COUNT 个 metadata 节点" # 显示解析到的节点 echo "" log_info "Redis 节点列表:" for i in "${!NODE_NAMES[@]}"; do echo " $i: ${NODE_NAMES[$i]} (${NODE_IPS[$i]}) - redis数据目录:${DATA_PATH[$i]}" done echo "" TOTAL_NODES=$COUNT FAILED_COUNT=0 # ================= 阶段 1 & 3: 上传并安装 ================= log_header "阶段 2/3: 上传脚本 & 阶段 3/3: 远程安装" REMOTE_SCRIPT_PATH="/tmp/metadata-install.sh" for i in "${!NODE_NAMES[@]}"; do name="${NODE_NAMES[$i]}" user="${NODE_USERS[$i]}" data_path="${DATA_PATHS[$i]}" # 关键修复:如果该节点在阶段 1 已失败,直接跳过,不再重复计数 if [[ " ${FAILED_INDICES[@]} " =~ " ${i} " ]]; then log_warn "跳过 $name (因阶段 1 已失败)" continue fi TARGET="${user}@${name}" log_step "正在部署 Redis: $name" # 1. 上传脚本 (注意:远程用户可能不是 root,但 scp 通常写入用户家目录或/tmp 没问题) # 如果 /tmp 权限受限,scp 可能会失败,但通常 /tmp 是 777 if ! scp -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 \ "$SCRIPT_METADATA" "${TARGET}:${REMOTE_SCRIPT_PATH}" 2>/dev/null; then log_error "$name 上传脚本失败" FAILED_INDICES+=("$i") FAIL_REASONS+=("阶段 2: 上传脚本失败") ((FAILED_COUNT++)) continue fi if ! scp -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 \ haproxy.cfg "${TARGET}:/tmp/haproxy.cfg" 2>/dev/null; then log_error "$name 脚本上传成功,但 haproxy.cfg 上传失败" FAILED_INDICES+=("$i") FAIL_REASONS+=("阶段 2: 上传 haproxy.cfg 失败") ((FAILED_COUNT++)) # 可选:若 haproxy.cfg 上传失败也需要终止当前循环,取消下面的注释 # continue else log_info "$name 脚本和 haproxy.cfg 均上传成功" fi # 2. 远程执行 (加上 sudo 确保权限) # 逻辑:先 chmod +x,然后 sudo 执行脚本 CMD="sudo apt update && sudo apt install haproxy && sudo mv /tmp/haproxy.cfg /etc/haproxy/haproxy.cfg && sudo chown root:root /etc/haproxy/haproxy.cfg && sudo chmod +x ${REMOTE_SCRIPT_PATH} && sudo ${REMOTE_SCRIPT_PATH} ${REDIS_PASSWORD} '${data_path}'" if ssh -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 \ -n "${TARGET}" "$CMD"; then log_success "$name Redis 部署成功!" else log_error "$name 远程执行失败" FAILED_INDICES+=("$i") FAIL_REASONS+=("阶段 3: 远程执行失败") ((FAILED_COUNT++)) fi done # ================= 总结 ================= echo "" echo "========================================" if [ $FAILED_COUNT -eq 0 ]; then log_success " 恭喜!所有 $TOTAL_NODES 个节点部署成功!" log_info " 提示: 请使用 redis-cli -a <密码> -h 测试连接" log_info " 提示: 请访问控制台:http://:9001 测试连接" else log_error "⚠️ 部署完成,共有 $FAILED_COUNT 个节点失败。" log_info " 失败详情:" for idx in "${!FAILED_INDICES[@]}"; do node_idx=${FAILED_INDICES[$idx]} reason=${FAIL_REASONS[$idx]} echo " - ${NODE_NAMES[$node_idx]} (${NODE_IPS[$node_idx]}): $reason" done echo "" log_warn "建议检查失败节点的日志或网络连通性后重试。" exit 1 fi echo "========================================"