Files
hops/lib/common.sh
T
Stephen Klein 60bf2054bd Release v3.2.0: Major macOS compatibility improvements and bug fixes
### Major macOS Compatibility Improvements
- Enhanced Docker Desktop installation and startup process for macOS
- Fixed Docker authentication with macOS keychain integration
- Resolved user directory issues - all directories now use actual user home instead of root
- Fixed password generation issues with missing shuf command and encoding errors
- Improved container creation with proper working directory context
- Enhanced healthcheck monitoring, particularly for Jellyseerr service

### Bug Fixes
- Fixed Docker Compose version warnings by removing obsolete version attribute
- Resolved container startup issues with proper directory navigation
- Fixed file permission issues for config and media directories
- Enhanced cross-platform path handling functions
- Improved error handling and user feedback throughout installation process

### Code Quality Improvements
- Updated all version references to v3.2.0
- Enhanced documentation with macOS-specific improvements
- Improved cross-platform compatibility across all components
- Better error messages and troubleshooting guidance

This release significantly improves the macOS user experience and resolves
numerous compatibility issues that were preventing successful installation
and operation on macOS systems.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 05:07:32 -04:00

307 lines
6.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# HOPS - Common Utility Functions
# Shared functions for logging, error handling, and UI
# Version: 3.2.0
# Prevent multiple sourcing
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
return 0
fi
readonly HOPS_COMMON_LOADED=1
# Color codes for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly NC='\033[0m' # No Color
# Global variables (set by setup_logging)
LOG_DIR=""
LOG_FILE=""
# Initialize logging system
setup_logging() {
local log_prefix="$1"
if [[ -z "$log_prefix" ]]; then
echo "ERROR: setup_logging requires a log prefix" >&2
return 1
fi
# Set platform-specific log directory
if [[ "$(uname -s)" == "Darwin" ]]; then
LOG_DIR="/usr/local/var/log/hops"
else
LOG_DIR="/var/log/hops"
fi
LOG_FILE="$LOG_DIR/${log_prefix}-$(date +%Y%m%d-%H%M%S).log"
if [[ $EUID -eq 0 ]]; then
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
else
echo "WARNING: Not running as root, logging to console only" >&2
fi
}
# Unified logging function
log() {
local message="$1"
local timestamp="$(date '+%Y-%m-%d %T')"
# Write to log file if available
if [[ -n "$LOG_FILE" && -w "$LOG_FILE" ]]; then
echo "$timestamp - $message" >> "$LOG_FILE"
fi
# Always output to console
echo -e "$message"
}
# Error handling with exit
error_exit() {
log "${RED}❌ ERROR: $1${NC}"
if [[ -n "$LOG_FILE" ]]; then
log "${RED}❌ Operation failed. Check logs at: $LOG_FILE${NC}"
fi
exit 1
}
# Warning function
warning() {
log "${YELLOW}⚠️ WARNING: $1${NC}"
}
# Success function
success() {
log "${GREEN}$1${NC}"
}
# Info function
info() {
log "${BLUE}$1${NC}"
}
# Debug function (only shows if DEBUG=1)
debug() {
if [[ "${DEBUG:-0}" == "1" ]]; then
log "${PURPLE}🐛 DEBUG: $1${NC}"
fi
}
# Show HOPS header
show_hops_header() {
local version="$1"
local subtitle="$2"
if [[ -z "$version" ]]; then
version="3.2.0"
fi
clear
cat << "EOF"
_ _ ____ ____ ____
| | | || _ \| _ \/ ___|
| |__| || |_) | |_) \___ \
| __ || __/| __/ ___) |
|_| |_||_| |_| |____/
EOF
echo -e "${CYAN}🚀 Homelab Orchestration Provisioning Script v${version}${NC}"
if [[ -n "$subtitle" ]]; then
echo -e "${WHITE}${subtitle}${NC}"
fi
echo -e "${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
}
# Progress indicator
show_progress() {
local current="$1"
local total="$2"
local message="$3"
local width=50
local percentage=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
printf "\r${BLUE}[${NC}"
printf "%${filled}s" | tr ' ' '='
printf "%${empty}s" | tr ' ' '-'
printf "${BLUE}] %3d%% %s${NC}" "$percentage" "$message"
if [[ $current -eq $total ]]; then
echo
fi
}
# Confirmation prompt
confirm() {
local message="$1"
local default="${2:-n}"
local prompt
case "$default" in
[Yy]|[Yy][Ee][Ss]) prompt="[Y/n]" ;;
[Nn]|[Nn][Oo]) prompt="[y/N]" ;;
*) prompt="[y/n]" ;;
esac
while true; do
read -r -p "${message} ${prompt}: " response
# Use default if empty response
if [[ -z "$response" ]]; then
response="$default"
fi
case "$response" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
[Nn]|[Nn][Oo]) return 1 ;;
*) echo "Please answer yes or no." ;;
esac
done
}
# Spinner for long operations
spinner() {
local pid=$1
local message="$2"
local spin='-\|/'
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r${BLUE}%s %s${NC}" "${spin:i++%${#spin}:1}" "$message"
sleep 0.1
done
printf "\r"
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
error_exit "This script must be run as root or with sudo."
fi
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Wait for user input
pause() {
local message="${1:-Press any key to continue...}"
read -n 1 -s -r -p "$message"
echo
}
# Format bytes to human readable
format_bytes() {
local bytes=$1
local units=("B" "KB" "MB" "GB" "TB")
local unit=0
while [[ $bytes -gt 1024 && $unit -lt 4 ]]; do
bytes=$((bytes / 1024))
((unit++))
done
echo "${bytes}${units[$unit]}"
}
# Check if string is a valid IP address
is_valid_ip() {
local ip=$1
local regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
if [[ $ip =~ $regex ]]; then
# Split IP into octets using parameter expansion
local octet1="${ip%%.*}"
local temp="${ip#*.}"
local octet2="${temp%%.*}"
temp="${temp#*.}"
local octet3="${temp%%.*}"
local octet4="${temp#*.}"
# Check each octet
for octet in "$octet1" "$octet2" "$octet3" "$octet4"; do
if [[ $octet -gt 255 ]]; then
return 1
fi
done
return 0
fi
return 1
}
# Check if port is available
is_port_available() {
local port=$1
! ss -tuln | grep -q ":$port "
}
# Get available port starting from given port
get_available_port() {
local start_port=$1
local port=$start_port
while ! is_port_available "$port"; do
((port++))
if [[ $port -gt 65535 ]]; then
error_exit "No available ports found starting from $start_port"
fi
done
echo "$port"
}
# Cross-platform shuffle function (alternative to shuf)
shuffle_string() {
local input_string="$1"
local chars=()
local i
# Convert string to array of characters
for (( i=0; i<${#input_string}; i++ )); do
chars+=("${input_string:$i:1}")
done
# Fisher-Yates shuffle algorithm
for (( i=${#chars[@]}-1; i>0; i-- )); do
local j=$((RANDOM % (i+1)))
local temp="${chars[i]}"
chars[i]="${chars[j]}"
chars[j]="$temp"
done
# Convert back to string
printf '%s' "${chars[@]}"
}
# Cross-platform character generation (alternative to tr with better encoding)
generate_chars() {
local char_set="$1"
local count="$2"
local result=""
local i
for (( i=0; i<count; i++ )); do
local char_index=$((RANDOM % ${#char_set}))
result+="${char_set:$char_index:1}"
done
echo "$result"
}