#!/usr/bin/env bash # Shared utilities for Unix scripts # Provides: colors, progress indicators, interactive detection # Detect if running in interactive shell is_interactive() { [[ -t 0 && -t 1 ]] } # Color definitions (only used in interactive mode) if is_interactive && [[ "${NO_COLOR:-}" != "1" ]]; then COLOR_RESET='\033[0m' COLOR_RED='\033[0;31m' COLOR_GREEN='\033[0;32m' COLOR_YELLOW='\033[0;33m' COLOR_BLUE='\033[0;34m' COLOR_MAGENTA='\033[0;35m' COLOR_CYAN='\033[0;36m' COLOR_GRAY='\033[0;90m' COLOR_BOLD='\033[1m' else COLOR_RESET='' COLOR_RED='' COLOR_GREEN='' COLOR_YELLOW='' COLOR_BLUE='' COLOR_MAGENTA='' COLOR_CYAN='' COLOR_GRAY='' COLOR_BOLD='' fi # Logging functions log_info() { echo -e "${COLOR_BLUE}${1}${COLOR_RESET}" } log_success() { echo -e "${COLOR_GREEN}${1}${COLOR_RESET}" } log_warning() { echo -e "${COLOR_YELLOW}${1}${COLOR_RESET}" } log_error() { echo -e "${COLOR_RED}${1}${COLOR_RESET}" >&2 } log_step() { echo -e "${COLOR_MAGENTA}${1}${COLOR_RESET}" } log_result() { echo -e "${COLOR_CYAN}${1}${COLOR_RESET}" } # Spinner animation (only for interactive shells) SPINNER_FRAMES=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') SPINNER_PID="" start_spinner() { local message="${1:-Processing...}" if ! is_interactive; then echo "$message" return fi { local i=0 while true; do printf "\r${COLOR_CYAN}${SPINNER_FRAMES[$i]}${COLOR_RESET} %s" "$message" i=$(( (i + 1) % ${#SPINNER_FRAMES[@]} )) sleep 0.1 done } & SPINNER_PID=$! # Ensure spinner is cleaned up on script exit trap "stop_spinner" EXIT INT TERM } stop_spinner() { if [[ -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then kill "$SPINNER_PID" 2>/dev/null wait "$SPINNER_PID" 2>/dev/null SPINNER_PID="" fi if is_interactive; then printf "\r\033[K" # Clear the spinner line fi } update_spinner_message() { local message="${1:-Processing...}" if ! is_interactive; then echo "$message" return fi if [[ -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then stop_spinner start_spinner "$message" fi } # Execute command with progress indicator run_with_progress() { local message="$1" shift local cmd=("$@") if is_interactive; then start_spinner "$message" local output local exit_code # Capture output and exit code output=$("${cmd[@]}" 2>&1) exit_code=$? stop_spinner if [[ $exit_code -eq 0 ]]; then log_success "✓ $message - Done!" else log_error "✗ $message - Failed!" echo "$output" fi return $exit_code else # Non-interactive: just show message and run echo "→ $message" "${cmd[@]}" local exit_code=$? if [[ $exit_code -eq 0 ]]; then echo "✓ $message - Done!" else echo "✗ $message - Failed!" fi return $exit_code fi } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Validate system requirements check_system_requirements() { local min_cpu="${1:-4}" local min_ram_gb="${2:-6}" local min_disk_gb="${3:-20}" log_step "Checking system requirements..." # Check CPU cores local cpu_count cpu_count=$(getconf _NPROCESSORS_ONLN) if [[ "$cpu_count" -lt "$min_cpu" ]]; then log_error "Insufficient CPU cores: $cpu_count (minimum: $min_cpu)" return 1 fi # Check RAM local total_mem_gb if [[ -f /proc/meminfo ]]; then local mem_kb mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}') total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $mem_kb/1024/1024}") else local total_mem_bytes total_mem_bytes=$(sysctl -n hw.memsize 2>/dev/null || echo "0") total_mem_gb=$(awk "BEGIN {printf \"%.2f\", $total_mem_bytes/1024/1024/1024}") fi if ! awk "BEGIN {exit !($total_mem_gb >= $min_ram_gb)}"; then log_error "Insufficient RAM: ${total_mem_gb}GB (minimum: ${min_ram_gb}GB)" return 1 fi # Check disk space local avail_kb free_gb avail_kb=$(df -k . | tail -1 | awk '{print $4}') free_gb=$(awk "BEGIN {printf \"%.2f\", $avail_kb/1024/1024}") if ! awk "BEGIN {exit !($free_gb >= $min_disk_gb)}"; then log_error "Insufficient disk space: ${free_gb}GB (minimum: ${min_disk_gb}GB)" return 1 fi log_success "System requirements met: CPU cores=$cpu_count, RAM=${total_mem_gb}GB, FreeDisk=${free_gb}GB" return 0 } # Confirm action (returns 0 for yes, 1 for no) confirm() { local prompt="${1:-Are you sure?}" local response if ! is_interactive; then # In non-interactive mode, assume yes return 0 fi read -r -p "$prompt [y/N]: " response case "$response" in [yY][eE][sS]|[yY]) return 0 ;; *) return 1 ;; esac }