#!/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' # 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" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o BatchMode=yes -i "$SSH_KEY" root@"$node" \ 'cd /var/www/paymenter && git fetch --all && git reset --hard origin/main && npm update && npm upgrade && composer upgrade && 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[@]}" } # 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${NC} " "$frame" sleep 0.12 done printf "\r ${C6}${BOLD}🚀 UPDATE COMPLETE${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" 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 fetch --all > /tmp/git_output.txt 2>&1 && git reset --hard origin/main >> /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 # Update Dependencies task_start "Local: Updating Dependencies" printf " ${C3}○${NC} NPM update..." npm update > /dev/null 2>&1 & spinner $! "NPM update" task_done "NPM update completed" printf " ${C3}○${NC} NPM upgrade..." npm upgrade > /dev/null 2>&1 & spinner $! "NPM upgrade" task_done "NPM upgrade completed" printf " ${C3}○${NC} Composer upgrade..." composer upgrade > /dev/null 2>&1 & spinner $! "Composer upgrade" task_done "Composer upgrade completed" # 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" 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 task_start "Waiting for Remote Nodes" printf " ${C3}○${NC} Syncing cluster..." wait 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 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 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