#!/bin/bash # ============================================================================ # Virtnet.bond - Paymenter Load Balancer Git Update Script # ============================================================================ # 256-Color Gradient Palette (Blue to Teal) C1='\e[38;5;21m' # Deep Blue C2='\e[38;5;27m' # Blue C3='\e[38;5;33m' # Light Blue C4='\e[38;5;39m' # Cyan Blue C5='\e[38;5;44m' # Cyan C6='\e[38;5;43m' # Teal # Standard colors WHITE='\e[97m' GRAY='\e[90m' RED='\e[91m' YELLOW='\e[93m' GREEN='\e[92m' BOLD='\e[1m' DIM='\e[2m' NC='\e[0m' # Spinner frames SPIN=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷') # Node list NODES=( "10.0.254.100" "10.0.254.101" "10.0.254.102" "10.0.254.103" "10.0.254.104" "10.0.254.105" "10.0.254.106" "10.0.254.107" "10.0.254.108" "10.0.254.109" "10.0.254.110" ) MAX_RETRIES=3 # Get current node IP CURRENT_IP=$(hostname -I | awk '{print $1}') # Status tracking directory STATUS_DIR="/tmp/virtnet_deploy_$$" mkdir -p "$STATUS_DIR" # SSH Key SSH_KEY="/root/.ssh/virtnet_deploy" # ============================================================================ # FUNCTIONS # ============================================================================ print_header() { clear echo "" echo -e " ${C1}██╗ ██╗${C2}██╗${C3}██████╗ ${C3}████████╗${C4}███╗ ██╗${C5}███████╗${C6}████████╗${NC}" echo -e " ${C1}██║ ██║${C2}██║${C3}██╔══██╗${C3}╚══██╔══╝${C4}████╗ ██║${C5}██╔════╝${C6}╚══██╔══╝${NC}" echo -e " ${C1}██║ ██║${C2}██║${C3}██████╔╝${C3} ██║ ${C4}██╔██╗ ██║${C5}█████╗ ${C6} ██║ ${NC}" echo -e " ${C1}╚██╗ ██╔╝${C2}██║${C3}██╔══██╗${C3} ██║ ${C4}██║╚██╗██║${C5}██╔══╝ ${C6} ██║ ${NC}" echo -e " ${C1} ╚████╔╝ ${C2}██║${C3}██║ ██║${C3} ██║ ${C4}██║ ╚████║${C5}███████╗${C6} ██║ ${NC}" echo -e " ${C1} ╚═══╝ ${C2}╚═╝${C3}╚═╝ ╚═╝${C3} ╚═╝ ${C4}╚═╝ ╚═══╝${C5}╚══════╝${C6} ╚═╝ ${NC}" echo -e " ${C6}${BOLD}.bond${NC}" echo "" echo -e " ${C4}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e " ${WHITE}${BOLD} Paymenter Load Balancer - Git Update & Apply${NC}" echo -e " ${C4}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" } spinner() { local pid=$1 local msg=$2 local i=0 while kill -0 "$pid" 2>/dev/null; do printf "\r ${C3}${SPIN[$i]}${NC} %s" "$msg" i=$(( (i + 1) % 8 )) sleep 0.1 done wait "$pid" return $? } task_start() { echo -e "\n${C4}┌─${NC} ${C5}${BOLD}$1${NC}" } task_done() { printf "\r ${C6}●${NC} %s ${GREEN}✓${NC}\n" "$1" } task_fail() { printf "\r ${RED}●${NC} %s ${RED}✗${NC}\n" "$1" } countdown() { local secs=$1 for ((i=secs; i>0; i--)); do printf "\r ${C3}◐${NC} Waiting ${C5}${BOLD}%d${NC}s " "$i" sleep 0.25 printf "\r ${C3}◓${NC} Waiting ${C5}${BOLD}%d${NC}s " "$i" sleep 0.25 printf "\r ${C3}◑${NC} Waiting ${C5}${BOLD}%d${NC}s " "$i" sleep 0.25 printf "\r ${C3}◒${NC} Waiting ${C5}${BOLD}%d${NC}s " "$i" sleep 0.25 done printf "\r ${C6}●${NC} Ready! ${GREEN}✓${NC}\n" } # Run update on remote node run_remote_update() { local node=$1 local status_file="$STATUS_DIR/$node" echo "pending" > "$status_file" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o BatchMode=yes -i "$SSH_KEY" root@"$node" \ 'cd /var/www/paymenter && git pull && php artisan view:clear && php artisan cache:clear && systemctl restart php8.3-fpm' \ > "$STATUS_DIR/${node}.log" 2>&1 if [ $? -eq 0 ]; then echo "done" > "$status_file" return 0 else echo "failed" > "$status_file" return 1 fi } # Get list of remote nodes (excluding current) get_remote_nodes() { local remotes=() for node in "${NODES[@]}"; do if [[ "$node" != "$CURRENT_IP" ]]; then remotes+=("$node") fi done echo "${remotes[@]}" } # Print node grid print_node_grid() { local title=$1 shift local show_status=$1 shift local nodes=("$@") echo -e "\n${C4}┌─${NC} ${C5}${BOLD}${title}${NC}" echo -e " ${GRAY}┌──────────┬──────────┬──────────┬──────────┬──────────┐${NC}" local col=0 local row_started=0 for node in "${nodes[@]}"; do if [ $row_started -eq 0 ]; then printf " ${GRAY}│${NC}" row_started=1 fi local short="${node##*.}" local icon="○" local color="$GRAY" if [ "$show_status" = "true" ]; then local status_file="$STATUS_DIR/$node" local status="waiting" [[ -f "$status_file" ]] && status=$(cat "$status_file") case "$status" in "done") icon="●"; color="$GREEN" ;; "failed") icon="●"; color="$RED" ;; "pending") icon="◐"; color="$C3" ;; *) icon="○"; color="$GRAY" ;; esac fi printf " ${color}${icon}${NC} .%-6s${GRAY}│${NC}" "$short" col=$((col + 1)) if [ $((col % 5)) -eq 0 ]; then echo "" row_started=0 fi done # Fill remaining cells if row not complete while [ $((col % 5)) -ne 0 ]; do printf " ${GRAY}│${NC}" col=$((col + 1)) done if [ $row_started -eq 1 ]; then echo "" fi echo -e " ${GRAY}└──────────┴──────────┴──────────┴──────────┴──────────┘${NC}" } # Get failed nodes get_failed_nodes() { local failed=() for node in "${NODES[@]}"; do if [[ "$node" != "$CURRENT_IP" ]]; then local status_file="$STATUS_DIR/$node" if [[ -f "$status_file" ]] && [[ "$(cat "$status_file")" == "failed" ]]; then failed+=("$node") fi fi done echo "${failed[@]}" } # Count results count_results() { local success=0 local failed=0 for node in "${NODES[@]}"; do if [[ "$node" == "$CURRENT_IP" ]]; then success=$((success + 1)) continue fi local status_file="$STATUS_DIR/$node" if [[ -f "$status_file" ]] && [[ "$(cat "$status_file")" == "done" ]]; then success=$((success + 1)) else failed=$((failed + 1)) fi done echo "$success $failed" } print_complete() { local results results=$(count_results) local success=$(echo $results | cut -d' ' -f1) local failed=$(echo $results | cut -d' ' -f2) echo "" echo -e " ${C4}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e " ${C6}${BOLD}🚀 UPDATE COMPLETE${NC}" echo "" echo -e " ${GRAY}Local node updated successfully.${NC}" echo -e " ${GREEN}●${NC} ${success} nodes updated ${RED}●${NC} ${failed} nodes failed" echo "" echo -e " ${C4}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" } cleanup() { rm -rf "$STATUS_DIR" } trap cleanup EXIT # ============================================================================ # MAIN SCRIPT # ============================================================================ print_header echo -e " ${C3}→${NC} ${GRAY}Current Node:${NC} ${C5}${CURRENT_IP}${NC}" echo -e " ${C3}→${NC} ${GRAY}Target Nodes:${NC} ${C5}${#NODES[@]} nodes in cluster${NC}" # Get remote nodes REMOTE_NODES=($(get_remote_nodes)) # Random delay sleep_time=$((RANDOM % 5 + 1)) cd /var/www/paymenter # Prepare task_start "Preparing Update" countdown "$sleep_time" # Start remote updates in background task_start "Deploying to Remote Nodes" echo -e " ${GRAY}Starting deployment on ${#REMOTE_NODES[@]} remote nodes...${NC}" for node in "${REMOTE_NODES[@]}"; do run_remote_update "$node" & done # Show initial node grid print_node_grid "Initial Cluster Status" "false" "${REMOTE_NODES[@]}" # Local Git Pull task_start "Local: Pulling Latest Changes" printf " ${C3}○${NC} Fetching from remote..." git pull > /tmp/git_output.txt 2>&1 & spinner $! "Fetching from remote..." GIT_STATUS=$? if [ $GIT_STATUS -ne 0 ]; then task_fail "Git pull failed" echo "" cat /tmp/git_output.txt echo "" echo -e " ${YELLOW}↻${NC} ${YELLOW}Restarting script in 2 seconds...${NC}" sleep 2 exec "$0" "$@" fi task_done "Git pull completed" head -3 /tmp/git_output.txt 2>/dev/null | while IFS= read -r line; do echo -e " ${DIM}${line}${NC}" done # Clear Caches task_start "Local: Clearing Caches" printf " ${C3}○${NC} View cache..." php artisan view:clear > /dev/null 2>&1 & spinner $! "View cache..." task_done "View cache cleared" printf " ${C3}○${NC} Application cache..." php artisan cache:clear > /dev/null 2>&1 & spinner $! "Application cache..." task_done "Application cache cleared" # Restart PHP-FPM task_start "Local: Restarting Services" printf " ${C3}○${NC} PHP 8.3 FPM..." systemctl restart php8.3-fpm & spinner $! "PHP 8.3 FPM..." task_done "PHP 8.3 FPM restarted" # Wait for initial remote deployment task_start "Waiting for Remote Nodes" printf " ${C3}○${NC} Syncing cluster..." wait task_done "Initial sync complete" # Show status after first attempt print_node_grid "Cluster Status (Attempt 1/${MAX_RETRIES})" "true" "${REMOTE_NODES[@]}" # Retry failed nodes attempt=1 while [ $attempt -lt $MAX_RETRIES ]; do FAILED_NODES=($(get_failed_nodes)) if [ ${#FAILED_NODES[@]} -eq 0 ]; then break fi attempt=$((attempt + 1)) task_start "Retrying Failed Nodes (Attempt ${attempt}/${MAX_RETRIES})" echo -e " ${YELLOW}↻${NC} ${YELLOW}${#FAILED_NODES[@]} node(s) failed, retrying...${NC}" for node in "${FAILED_NODES[@]}"; do run_remote_update "$node" & done printf " ${C3}○${NC} Waiting for retry..." wait task_done "Retry attempt ${attempt} complete" print_node_grid "Cluster Status (Attempt ${attempt}/${MAX_RETRIES})" "true" "${REMOTE_NODES[@]}" done # Final status print_node_grid "Final Cluster Status" "true" "${REMOTE_NODES[@]}" # Complete print_complete