#!/bin/bash # ============================================================================ # Virtnet.bond - Paymenter Load Balancer Git Update Script (FAST MODE) # ============================================================================ # This is a faster version that skips npm update/upgrade and composer upgrade # 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' # Animation characters SPIN=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷') DOTS=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') BARS=('▁' '▂' '▃' '▄' '▅' '▆' '▇' '█' '▇' '▆' '▅' '▄' '▃' '▂') PULSE=('○' '◎' '●' '◉' '●' '◎') # 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" "10.0.254.111" "10.0.254.112" "10.0.254.113" "10.0.254.114" "10.0.254.115" "10.0.254.116" "10.0.254.117" "10.0.254.118" "10.0.254.119" "10.0.254.120" "10.0.254.121" "10.0.254.122" # "10.0.254.123" "10.0.254.124" # "10.0.254.125" "10.0.254.126" "10.0.254.127" "10.0.254.128" "10.0.254.129" "10.0.254.130" # "10.0.254.131" ) MAX_RETRIES=100 # 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" # ============================================================================ # ANIMATION FUNCTIONS # ============================================================================ # Typewriter effect typewriter() { local text="$1" local delay="${2:-0.02}" for ((i=0; i<${#text}; i++)); do printf "%s" "${text:$i:1}" sleep "$delay" done } # Animated line draw draw_line() { local width=$1 local char="${2:-━}" local color="${3:-$C4}" printf " ${color}" for ((i=0; i/dev/null; do printf "\r ${C3}${SPIN[$i]}${NC} %s ${DIM}${DOTS[$((i % 10))]}${NC}" "$msg" i=$(( (i + 1) % 8 )) sleep 0.1 done wait "$pid" return $? } # Wave spinner for waiting wave_spinner() { local pid=$1 local msg=$2 local i=0 local colors=("$C1" "$C2" "$C3" "$C4" "$C5" "$C6") while kill -0 "$pid" 2>/dev/null; do printf "\r " for ((j=0; j<6; j++)); do local idx=$(( (i + j) % 14 )) local cidx=$(( (i + j) % 6 )) printf "${colors[$cidx]}${BARS[$idx]}${NC}" done printf " %s" "$msg" i=$(( (i + 1) % 14 )) sleep 0.08 done wait "$pid" return $? } # Task section header with animation task_start() { echo "" printf "${C4}┌─${NC} ${C5}${BOLD}" typewriter "$1" 0.02 printf "${NC}\n" } # Task completion with checkmark animation task_done() { local msg=$1 printf "\r " for p in "${PULSE[@]}"; do printf "\r ${C6}${p}${NC}" sleep 0.05 done printf "\r ${C6}●${NC} %s ${GREEN}✓${NC}\n" "$msg" } # Task failure task_fail() { printf "\r ${RED}●${NC} %s ${RED}✗${NC}\n" "$1" } # Animated countdown countdown() { local secs=$1 local circle=('◜' '◠' '◝' '◞' '◡' '◟') for ((i=secs; i>0; i--)); do for c in "${circle[@]}"; do printf "\r ${C3}${c}${NC} Waiting ${C5}${BOLD}%d${NC}s " "$i" sleep 0.166 done done # Completion animation printf "\r " for p in "${PULSE[@]}"; do printf "\r ${C6}${p}${NC} Ready! " sleep 0.08 done printf "\r ${C6}●${NC} Ready! ${GREEN}✓${NC}\n" } # ============================================================================ # NODE FUNCTIONS # ============================================================================ run_remote_update() { local node=$1 local status_file="$STATUS_DIR/$node" echo "pending" > "$status_file" # FAST MODE: Skip npm update, npm upgrade, and composer upgrade ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o BatchMode=yes -i "$SSH_KEY" root@"$node" \ 'cd /var/www/paymenter && git reset --hard && git pull --rebase && git pull && php artisan view:clear && php artisan cache:clear && php artisan optimize && while ! systemctl restart php8.3-fpm 2>/dev/null; do echo "PHP-FPM restart failed, running optimize and retrying..."; php artisan optimize; sleep 1; done' \ > "$STATUS_DIR/${node}.log" 2>&1 if [ $? -eq 0 ]; then echo "done" > "$status_file" return 0 else echo "failed" > "$status_file" return 1 fi } run_remote_maintenance_down() { local node=$1 ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes -i "$SSH_KEY" root@"$node" \ 'cd /var/www/paymenter && php artisan down' > /dev/null 2>&1 } run_remote_maintenance_up() { local node=$1 ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes -i "$SSH_KEY" root@"$node" \ 'cd /var/www/paymenter && php artisan up' > /dev/null 2>&1 } get_remote_nodes() { local remotes=() for node in "${NODES[@]}"; do if [[ "$node" != "$CURRENT_IP" ]]; then remotes+=("$node") fi done echo "${remotes[@]}" } # Get node status counts get_node_status_counts() { local done_count=0 local pending_count=0 local failed_count=0 local waiting_count=0 for node in "${REMOTE_NODES[@]}"; do local status_file="$STATUS_DIR/$node" local status="waiting" [[ -f "$status_file" ]] && status=$(cat "$status_file") case "$status" in "done") done_count=$((done_count + 1)) ;; "failed") failed_count=$((failed_count + 1)) ;; "pending") pending_count=$((pending_count + 1)) ;; *) waiting_count=$((waiting_count + 1)) ;; esac done echo "$done_count $pending_count $failed_count $waiting_count" } # Animated node grid print_node_grid() { local title=$1 shift local show_status=$1 shift local nodes=("$@") local animate=${1:-false} echo "" printf "${C4}┌─${NC} ${C5}${BOLD}" typewriter "$title" 0.02 printf "${NC}\n" # Animated border printf " ${GRAY}" typewriter "┌──────────┬──────────┬──────────┬──────────┬──────────┐" 0.005 printf "${NC}\n" 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 sleep 0.03 fi done while [ $((col % 5)) -ne 0 ]; do printf " ${GRAY}│${NC}" col=$((col + 1)) done if [ $row_started -eq 1 ]; then echo "" fi printf " ${GRAY}" typewriter "└──────────┴──────────┴──────────┴──────────┴──────────┘" 0.005 printf "${NC}\n" } 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() { 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" } # Animated completion message print_complete() { local results results=$(count_results) local success=$(echo $results | cut -d' ' -f1) local failed=$(echo $results | cut -d' ' -f2) echo "" draw_line 57 echo "" # Rocket animation local rocket_frames=('🚀' '✨🚀' '✨ 🚀' '✨ 🚀' ' ✨🚀' ' 🚀') for frame in "${rocket_frames[@]}"; do printf "\r ${C6}${BOLD}%s UPDATE COMPLETE (FAST MODE)${NC} " "$frame" sleep 0.12 done printf "\r ${C6}${BOLD}🚀 UPDATE COMPLETE (FAST MODE)${NC} \n" echo "" printf " ${GRAY}" typewriter "Local node updated successfully." 0.02 printf "${NC}\n" # Animated stats printf " " for ((i=0; i<=success; i++)); do printf "\r ${GREEN}●${NC} ${GREEN}%d${NC} nodes updated" "$i" sleep 0.05 done printf " ${RED}●${NC} " for ((i=0; i<=failed; i++)); do printf "\r ${GREEN}●${NC} ${success} nodes updated ${RED}●${NC} ${RED}%d${NC} nodes failed" "$i" sleep 0.05 done echo "" echo "" draw_line 57 echo "" printf " ${C5}${BOLD}All operations complete.${NC}\n" } cleanup() { rm -rf "$STATUS_DIR" } trap cleanup EXIT # ============================================================================ # MAIN SCRIPT # ============================================================================ print_header # Animated info display printf " ${C3}→${NC} ${GRAY}Current Node:${NC} ${C5}" typewriter "$CURRENT_IP" 0.03 printf "${NC}\n" printf " ${C3}→${NC} ${GRAY}Target Nodes:${NC} ${C5}" typewriter "${#NODES[@]} nodes in cluster" 0.03 printf "${NC}\n" printf " ${YELLOW}→${NC} ${YELLOW}FAST MODE:${NC} ${WHITE}" typewriter "Skipping npm update/upgrade and composer upgrade" 0.02 printf "${NC}\n" REMOTE_NODES=($(get_remote_nodes)) sleep_time=$((RANDOM % 5 + 1)) cd /var/www/paymenter # Prepare task_start "Preparing Update" countdown "$sleep_time" # Enable Maintenance Mode on All Nodes task_start "Cluster: Enabling Maintenance Mode" printf " ${C3}○${NC} Putting all nodes in maintenance mode..." # Start maintenance mode on all remote nodes in parallel for node in "${REMOTE_NODES[@]}"; do run_remote_maintenance_down "$node" & done # Also put local node in maintenance mode php artisan down > /dev/null 2>&1 & # Wait for all maintenance commands to complete wait task_done "All nodes in maintenance mode" # Deploy with progress task_start "Deploying to Remote Nodes" printf " ${GRAY}Initiating deployment to ${#REMOTE_NODES[@]} remote nodes" for ((i=0; i<3; i++)); do printf "." sleep 0.3 done printf "${NC}\n" for node in "${REMOTE_NODES[@]}"; do run_remote_update "$node" & done 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 & wave_spinner $! "Fetching from remote..." GIT_STATUS=$? if [ $GIT_STATUS -ne 0 ]; then task_fail "Git pull failed" echo "" cat /tmp/git_output.txt echo "" printf " ${YELLOW}↻${NC} ${YELLOW}" typewriter "Restarting script in 2 seconds..." 0.03 printf "${NC}\n" 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 printf " ${DIM}" typewriter "$line" 0.01 printf "${NC}\n" done # Clear Caches (FAST MODE: Skip npm and composer updates) 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" printf " ${C3}○${NC} Optimizing..." php artisan optimize > /dev/null 2>&1 & spinner $! "Optimizing" task_done "Optimization completed" # Restart PHP-FPM task_start "Local: Restarting Services" printf " ${C3}○${NC} PHP 8.3 FPM..." # Retry loop: if PHP-FPM fails to restart, run optimize and try again php_restart_success=false php_restart_attempts=0 while [ "$php_restart_success" = false ]; do php_restart_attempts=$((php_restart_attempts + 1)) if systemctl restart php8.3-fpm > /dev/null 2>&1; then php_restart_success=true else printf "\r ${YELLOW}○${NC} PHP 8.3 FPM restart failed (attempt ${php_restart_attempts}), running optimize...\n" php artisan optimize > /dev/null 2>&1 printf " ${C3}○${NC} Retrying PHP 8.3 FPM..." sleep 1 fi done task_done "PHP 8.3 FPM restarted (attempt ${php_restart_attempts})" # Wait for remote nodes with live status updates task_start "Waiting for Remote Nodes" printf " ${C3}○${NC} Syncing cluster...\n" # Live status update loop while waiting for remote nodes total_nodes=${#REMOTE_NODES[@]} while true; do # Check if any jobs are still running jobs_running=$(jobs -r | wc -l) # Get current status counts status_counts=$(get_node_status_counts) done_count=$(echo $status_counts | cut -d' ' -f1) pending_count=$(echo $status_counts | cut -d' ' -f2) failed_count=$(echo $status_counts | cut -d' ' -f3) waiting_count=$(echo $status_counts | cut -d' ' -f4) # Display live status printf "\r ${C3}○${NC} Syncing cluster... ${GREEN}●${NC} ${done_count} done ${C3}◐${NC} ${pending_count} syncing ${RED}●${NC} ${failed_count} failed ${GRAY}○${NC} ${waiting_count} waiting " # Exit loop when all jobs complete if [ $jobs_running -eq 0 ]; then break fi sleep 0.5 done # Final wait to ensure all background processes complete wait printf "\n" task_done "Initial sync complete" print_node_grid "Cluster Status (Attempt 1/${MAX_RETRIES})" "true" "${REMOTE_NODES[@]}" # Retry logic 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})" printf " ${YELLOW}↻${NC} ${YELLOW}" typewriter "${#FAILED_NODES[@]} node(s) failed, retrying..." 0.02 printf "${NC}\n" for node in "${FAILED_NODES[@]}"; do run_remote_update "$node" & done # Live status update loop for retries printf " ${C3}○${NC} Waiting for retry...\n" while true; do jobs_running=$(jobs -r | wc -l) status_counts=$(get_node_status_counts) done_count=$(echo $status_counts | cut -d' ' -f1) pending_count=$(echo $status_counts | cut -d' ' -f2) failed_count=$(echo $status_counts | cut -d' ' -f3) waiting_count=$(echo $status_counts | cut -d' ' -f4) printf "\r ${C3}○${NC} Syncing cluster... ${GREEN}●${NC} ${done_count} done ${C3}◐${NC} ${pending_count} syncing ${RED}●${NC} ${failed_count} failed ${GRAY}○${NC} ${waiting_count} waiting " if [ $jobs_running -eq 0 ]; then break fi sleep 0.5 done wait printf "\n" task_done "Retry attempt ${attempt} complete" print_node_grid "Cluster Status (Attempt ${attempt}/${MAX_RETRIES})" "true" "${REMOTE_NODES[@]}" done print_node_grid "Final Cluster Status" "true" "${REMOTE_NODES[@]}" # Disable Maintenance Mode on All Nodes task_start "Cluster: Disabling Maintenance Mode" printf " ${C3}○${NC} Bringing all nodes back online..." # Bring all remote nodes up in parallel for node in "${REMOTE_NODES[@]}"; do run_remote_maintenance_up "$node" & done # Also bring local node up php artisan up > /dev/null 2>&1 & # Wait for all nodes to come up wait task_done "All nodes are back online" print_complete