#!/bin/bash set -o pipefail # ================= 颜色与日志 ================= 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 "========================================" } # ================= 配置与初始化 ================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HOST_FILE="${SCRIPT_DIR}/redis.txt" SUB_SCRIPTS=( "1.deploy_keys.sh" "2.deploy_single.sh" ) declare -A SCRIPT_URLS SCRIPT_URLS["1.deploy_keys.sh"]="https://zhengyu1992.cn/file/software/redis/1.deploy_keys.sh" SCRIPT_URLS["2.deploy_single.sh"]="https://zhengyu1992.cn/file/software/redis/2.deploy_single.sh" log_header " Redis 集群全自动部署启动 (优化版)" # ================= 前置检查 ================= if [ ! -f "$HOST_FILE" ]; then log_error "找不到主机列表文件: $HOST_FILE" echo "示例格式:" echo "#HOSTNAME HOSTIP USER PASSWORD DATA_PATH REDIS_PASSWORD" echo "metadata1 10.10.21.156 zhengyu 123456 /data/redis eame231" exit 1 fi log_info "主机列表: $HOST_FILE" log_info "工作目录: $SCRIPT_DIR" # --------------------------------------------------------- # 2. 检查并自动下载/修复子脚本 # --------------------------------------------------------- log_step "检查子脚本完整性..." for script_name in "${SUB_SCRIPTS[@]}"; do script_path="${SCRIPT_DIR}/${script_name}" if [ ! -f "$script_path" ]; then log_warn "缺少脚本: ${script_name},正在下载..." download_url="${SCRIPT_URLS[$script_name]}" if [ -z "$download_url" ]; then log_error "错误:脚本 ${script_name} 没有配置下载 URL!" exit 1 fi if ! curl -fsSL "$download_url" -o "$script_path" 2>/dev/null; then if command -v wget &> /dev/null; then wget -q "$download_url" -O "$script_path" else log_error "下载失败:${script_name}" exit 1 fi fi sudo chmod +x "$script_path" if [ -s "$script_path" ]; then log_success "脚本 ${script_name} 下载并授权成功。" else log_error "下载的文件为空:${script_name}" exit 1 fi elif [ ! -x "$script_path" ]; then log_warn "脚本 ${script_name} 缺少执行权限,正在修复..." sudo chmod +x "$script_path" log_success "权限修复成功。" else log_info "脚本 ${script_name} 已就绪。" fi done SCRIPT_KEYS="${SCRIPT_DIR}/1.deploy_keys.sh" SCRIPT_SINGLE="${SCRIPT_DIR}/2.deploy_single.sh" log_success "所有子脚本已就绪。" # ================= 解析主机列表 ================= log_step "解析主机列表..." declare -a NODE_NAMES NODE_IPS NODE_USERS NODE_PASSES NODE_DATA_PATHS NODE_REDIS_PASSES COUNT=0 while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue name=$(echo "$line" | awk '{print $1}') ip=$(echo "$line" | awk '{print $2}') user=$(echo "$line" | awk '{print $3}') pass=$(echo "$line" | awk '{print $4}') data_path=$(echo "$line" | awk '{print $5}') redis_pass=$(echo "$line" | awk '{print $6}') if [ -z "$name" ] || [ -z "$ip" ] || [ -z "$user" ] || [ -z "$pass" ] || [ -z "$data_path" ] || [ -z "$redis_pass" ]; then log_warn "跳过格式错误的行: $line" continue fi NODE_NAMES+=("$name") NODE_IPS+=("$ip") NODE_USERS+=("$user") NODE_PASSES+=("$pass") NODE_DATA_PATHS+=("$data_path") NODE_REDIS_PASSES+=("$redis_pass") ((COUNT++)) done < <(tail -n +2 "$HOST_FILE") if [ $COUNT -eq 0 ]; then log_error "未找到有效的节点配置" exit 1 fi log_success "共解析到 $COUNT 个节点" TOTAL_NODES=$COUNT FAILED_COUNT=0 # 关键优化:记录失败节点的索引,防止重复计数 declare -a FAILED_INDICES declare -a FAIL_REASONS KEY_FILE="$HOME/.ssh/id_ed25519_minio_cluster" # ================= 阶段 1: 基础环境 (SSH) ================= log_header "阶段 1/3: 初始化基础环境 (SSH/Hosts/Sudo)" for i in "${!NODE_NAMES[@]}"; do name="${NODE_NAMES[$i]}" ip="${NODE_IPS[$i]}" user="${NODE_USERS[$i]}" pass="${NODE_PASSES[$i]}" log_step "正在初始化: $name ($ip)" if "$SCRIPT_KEYS" "$name" "$ip" "$user" "$pass"; then log_success "$name 基础环境就绪" else log_error "$name 基础环境初始化失败" FAILED_INDICES+=("$i") FAIL_REASONS+=("阶段 1: SSH 初始化失败") ((FAILED_COUNT++)) fi done sleep 2 # ================= 阶段 2 & 3: 上传并安装 ================= log_header "阶段 2/3: 上传脚本 & 阶段 3/3: 远程安装" REMOTE_SCRIPT_PATH="/tmp/redis-install.sh" for i in "${!NODE_NAMES[@]}"; do name="${NODE_NAMES[$i]}" ip="${NODE_IPS[$i]}" user="${NODE_USERS[$i]}" data_path="${NODE_DATA_PATHS[$i]}" redis_pass="${NODE_REDIS_PASSES[$i]}" # 关键修复:如果该节点在阶段 1 已失败,直接跳过,不再重复计数 if [[ " ${FAILED_INDICES[@]} " =~ " ${i} " ]]; then log_warn "跳过 $name (因阶段 1 已失败)" continue fi TARGET="${user}@${ip}" log_step "正在部署 Redis: $name" # 1. 上传 (注意:远程用户可能不是 root,但 scp 通常写入用户家目录或/tmp 没问题) # 如果 /tmp 权限受限,scp 可能会失败,但通常 /tmp 是 777 if ! scp -i "$KEY_FILE" \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o ConnectTimeout=10 \ "$SCRIPT_SINGLE" \ "${TARGET}:${REMOTE_SCRIPT_PATH}" 2>/dev/null; then log_error "$name 上传脚本失败" FAILED_INDICES+=("$i") FAIL_REASONS+=("阶段 2: 上传脚本失败") ((FAILED_COUNT++)) continue fi # 2. 远程执行 (加上 sudo 确保权限) # 逻辑:先 chmod +x,然后 sudo 执行脚本 CMD="sudo chmod +x ${REMOTE_SCRIPT_PATH} && sudo ${REMOTE_SCRIPT_PATH} '${redis_pass}' '${data_path}'" if ssh -i "$KEY_FILE" \ -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 测试连接" 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 "========================================"