File: //bigscoots/wpo/cluster/cluster-mgr.sh
#!/bin/bash
# Source BigScoots common functions
source /bigscoots/includes/common.sh
CLUSTER_DIR="/root/.bigscoots/cluster"
NODES_FILE="$CLUSTER_DIR/nodes"
ROLE_FILE="$CLUSTER_DIR/role"
DB_SERVER_FILE="$CLUSTER_DIR/db_server"
LOG_FILE="/var/log/cluster-mgr.log"
SSH_PORT=2222
# Ensure local state dir exists
mkdir -p "$CLUSTER_DIR"
# --- Internal Helpers ---
get_master_ip() { [[ -f "$ROLE_FILE" ]] && cat "$ROLE_FILE" | cut -d':' -f2; }
status_done() { echo -e " -> $1... \033[0;32m[ OK ]\033[0m"; }
status_fail() { echo -e " -> $1... \033[0;31m[ FAIL ]\033[0m (Check $LOG_FILE)"; }
# --- Helper: Safely update /etc/hosts ---
update_hosts_entry() {
local ip=$1
local hostname=$2
local comment="# [cluster-managed]"
if grep -q "$hostname" /etc/hosts; then
sed -i "/$hostname/c\\$ip $hostname $comment" /etc/hosts
else
echo "$ip $hostname $comment" >> /etc/hosts
fi
}
# --- Function: Sync cluster config ---
sync_cluster_config() {
local master_ip=$(get_master_ip)
[[ -z "$master_ip" ]] && { echo "Set master first."; exit 1; }
echo "Broadcasting cluster configuration (Master: $master_ip)..."
while read -r line; do
[[ -z "$line" || "$line" == "#"* ]] && continue
local node_ip=$(echo "$line" | cut -d':' -f1)
local node_port=$(echo "$line" | grep ":" | cut -d':' -f2); node_port=${node_port:-2222}
[[ "$node_ip" == "$master_ip" ]] && continue
echo "Processing Node: $node_ip"
# 1. Prep & Install Lsyncd
ssh -p "$node_port" -o ConnectTimeout=5 "$node_ip" "yum install -y lsyncd && mkdir -p $CLUSTER_DIR /var/log/lsyncd" < /dev/null >> "$LOG_FILE" 2>&1
# 2. Sync Files
rsync -avz -q -e "ssh -p $node_port" --exclude='role' "$CLUSTER_DIR/" "${node_ip}:${CLUSTER_DIR}/" >> "$LOG_FILE" 2>&1
rsync -avz -q -e "ssh -p $node_port" "/root/.my.cnf" "${node_ip}:/root/.my.cnf" >> "$LOG_FILE" 2>&1
[[ $? -eq 0 ]] && status_done "Config Sync" || status_fail "Config Sync"
# 3. Provision Role and Network
ssh -p "$node_port" "$node_ip" "echo 'slave:$master_ip' > $ROLE_FILE; sed -i '/internal.bscoots/d' /etc/hosts; echo '$master_ip objectcache.internal.bscoots # [cluster-managed]' >> /etc/hosts; echo '$master_ip sessions.internal.bscoots # [cluster-managed]' >> /etc/hosts; systemctl stop lsyncd 2>/dev/null; systemctl disable lsyncd 2>/dev/null;" < /dev/null >> "$LOG_FILE" 2>&1
[[ $? -eq 0 ]] && status_done "Role & Network Provisioning" || status_fail "Role & Network Provisioning"
done < "$NODES_FILE"
# 4. Update Lsyncd Master Config Targets
if [[ -f "/etc/lsyncd.conf" ]] && grep -q -e "-- TARGET_LIST_MARKER" /etc/lsyncd.conf; then
echo "Updating Lsyncd target list on Master..."
local new_targets=$(grep -v "$master_ip" "$NODES_FILE" | cut -d':' -f1 | sed 's/.*/"&"/' | paste -sd, - | sed 's/,/, /g')
sed -i "/-- TARGET_LIST_MARKER/,/local targets/ s/local targets = {.*}/local targets = { $new_targets }/" /etc/lsyncd.conf
status_done "Master Lsyncd Config Updated"
fi
echo "Restarting Lsyncd on Master..."
systemctl restart lsyncd >> "$LOG_FILE" 2>&1
}
# --- Function: Sync DB permissions ---
sync_all_dbs() {
local db_host="localhost"
[[ -f "$DB_SERVER_FILE" ]] && db_host=$(head -n 1 "$DB_SERVER_FILE" | cut -d':' -f1)
echo "Syncing DB permissions to $db_host..."
find_wp_installs | while read -r wp_path; do
local config="$wp_path/wp-config.php"
[[ ! -f "$config" ]] && continue
local db_name=$(grep "DB_NAME" "$config" | cut -d"'" -f4)
local db_user=$(grep "DB_USER" "$config" | cut -d"'" -f4)
local db_pass=$(grep "DB_PASSWORD" "$config" | cut -d"'" -f4)
while read -r line; do
[[ -z "$line" || "$line" == "#"* ]] && continue
local node_ip=$(echo "$line" | cut -d':' -f1)
# Using IDENTIFIED BY ensures the user is created for that IP if it doesn't exist
mysql -h "$db_host" -e "GRANT ALL PRIVILEGES ON \`${db_name}\`.* TO '${db_user}'@'${node_ip}' IDENTIFIED BY '${db_pass}';" >> "$LOG_FILE" 2>&1
done < "$NODES_FILE"
[[ $? -eq 0 ]] && status_done "DB Grant: $db_name" || status_fail "DB Grant: $db_name"
done
mysql -h "$db_host" -e "FLUSH PRIVILEGES;" >> "$LOG_FILE" 2>&1
}
# --- Function: Comprehensive Status ---
show_status() {
local role_raw=$(cat "$ROLE_FILE" 2>/dev/null)
local role=$(echo "$role_raw" | cut -d':' -f1)
local master_ip=$(echo "$role_raw" | cut -d':' -f2)
local db_host="localhost"
[[ -f "$DB_SERVER_FILE" ]] && db_host=$(head -n 1 "$DB_SERVER_FILE" | cut -d':' -f1)
local local_host=$(hostname)
local local_load=$(cat /proc/loadavg | awk '{print $1}')
echo -e "\033[1m--- Local Node Identity ---\033[0m"
echo "Hostname: $local_host"
echo "Role: ${role^^}"
echo -e "\n\033[1m--- Local Services ---\033[0m"
pgrep lsyncd > /dev/null && echo -e "Lsyncd: \033[0;32m[ RUNNING ]\033[0m" || echo -e "Lsyncd: \033[0;31m[ STOPPED ]\033[0m"
ss -tulpn | grep -q ":6380" && echo -e "Redis ObjCache: \033[0;32m[ RUNNING ]\033[0m (6380)" || echo -e "Redis ObjCache: \033[0;31m[ STOPPED ]\033[0m"
ss -tulpn | grep -q ":6381" && echo -e "Redis Sessions: \033[0;32m[ RUNNING ]\033[0m (6381)" || echo -e "Redis Sessions: \033[0;31m[ STOPPED ]\033[0m"
echo -e "\n\033[1m--- Cluster: Database Tier ---\033[0m"
if [[ -f "$DB_SERVER_FILE" ]]; then
while read -r db_line; do
[[ -z "$db_line" || "$db_line" == "#"* ]] && continue
local d_ip=$(echo "$db_line" | cut -d':' -f1)
local d_role=$(echo "$db_line" | cut -d':' -f2); d_role=${d_role:-master}
local d_data=$(ssh -p $SSH_PORT -o ConnectTimeout=2 "$d_ip" "echo \$(hostname):\$(systemctl is-active mysql 2>/dev/null || systemctl is-active mariadb 2>/dev/null):\$(cat /proc/loadavg | awk '{print \$1}')" < /dev/null 2>/dev/null)
local d_host=$(echo $d_data | cut -d':' -f1)
local d_status=$(echo $d_data | cut -d':' -f2)
local d_load=$(echo $d_data | cut -d':' -f3)
if [[ "$d_status" == "active" ]]; then
echo -e "DB Node ($d_ip): \033[0;32m[ ONLINE / ${d_role^^} ]\033[0m - Load: $d_load - $d_host"
else
echo -e "DB Node ($d_ip): \033[0;31m[ OFFLINE / UNREACHABLE ]\033[0m - $d_host"
fi
done < "$DB_SERVER_FILE"
fi
echo -e "\n\033[1m--- Cluster: Web Tier ---\033[0m"
echo -e "Web Node ($(hostname -I | awk '{print $1}')): \033[0;36m[ ONLINE / ${role^^} ]\033[0m - Load: $local_load (Local)"
if [[ "$role" == "master" ]]; then
while read -r line; do
[[ -z "$line" || "$line" == "#"* ]] && continue
local node_ip=$(echo "$line" | cut -d':' -f1)
local node_port=$(echo "$line" | grep ":" | cut -d':' -f2); node_port=${node_port:-2222}
if [[ "$node_ip" != "$master_ip" ]]; then
local remote_data=$(ssh -p "$node_port" -o ConnectTimeout=2 "$node_ip" "echo \$(hostname):\$(cat $ROLE_FILE 2>/dev/null | cut -d':' -f1):\$(cat /proc/loadavg | awk '{print \$1}')" < /dev/null 2>/dev/null)
local r_host=$(echo $remote_data | cut -d':' -f1)
local r_role=$(echo $remote_data | cut -d':' -f2)
local r_load=$(echo $remote_data | cut -d':' -f3)
if [[ "$r_role" == "slave" ]]; then
echo -e "Web Node ($node_ip): \033[0;32m[ ONLINE / SLAVE ]\033[0m - Load: $r_load - $r_host"
else
echo -e "Web Node ($node_ip): \033[0;31m[ UNREACHABLE / UNCONFIGURED ]\033[0m"
fi
fi
done < "$NODES_FILE"
fi
}
# --- Function: Promote to Master ---
promote_master() {
local master_ip=$1
echo "master:$master_ip" > "$ROLE_FILE"
update_hosts_entry "127.0.0.1" "objectcache.internal.bscoots"
update_hosts_entry "127.0.0.1" "sessions.internal.bscoots"
systemctl enable --now redis-objectcache redis-sessions > /dev/null 2>&1
for port in 6380 6381; do
redis-cli -p $port CONFIG SET protected-mode no 2>/dev/null
redis-cli -p $port CONFIG REWRITE 2>/dev/null
done
systemctl restart lsyncd > /dev/null 2>&1
echo "This node is now MASTER and Redis is open to the cluster."
}
case "$1" in
set-master) promote_master "$2" ;;
sync-cluster) sync_cluster_config ;;
sync-dbs) sync_all_dbs ;;
status) show_status ;;
*) echo "Usage: $0 {set-master <ip>|sync-cluster|sync-dbs|status}"; exit 1 ;;
esac