d2e9a69313
- Add comprehensive debug output to trace Docker installation execution paths - Fix early return issue that was skipping Linux Mint repository fix - Reorder Docker removal to clean repositories before running apt commands - Add specific cleanup for Linux Mint codenames (xia, vera, vanessa) - Prevent apt errors during existing Docker removal process 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1227 lines
42 KiB
Bash
1227 lines
42 KiB
Bash
#!/bin/bash
|
|
|
|
# HOPS - System Validation Functions
|
|
# Functions for system checks, OS detection, and requirements validation
|
|
# Version: 3.2.0
|
|
|
|
# Source common functions
|
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$LIB_DIR/common.sh"
|
|
|
|
# Global variables for system info
|
|
OS_NAME=""
|
|
OS_VERSION=""
|
|
OS_NAME_LOWER=""
|
|
|
|
# Detect operating system
|
|
detect_os() {
|
|
info "🔍 Detecting operating system..."
|
|
|
|
# Check if we're on macOS
|
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
OS_NAME="macOS"
|
|
OS_VERSION=$(sw_vers -productVersion)
|
|
OS_NAME_LOWER="macos"
|
|
success "Detected supported OS: $OS_NAME $OS_VERSION"
|
|
return 0
|
|
fi
|
|
|
|
# Linux detection
|
|
if command_exists lsb_release; then
|
|
OS_NAME=$(lsb_release -is)
|
|
OS_VERSION=$(lsb_release -rs)
|
|
elif [[ -f /etc/os-release ]]; then
|
|
OS_NAME=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
OS_VERSION=$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
else
|
|
error_exit "Unable to detect operating system"
|
|
fi
|
|
|
|
OS_NAME_LOWER=$(echo "$OS_NAME" | tr '[:upper:]' '[:lower:]')
|
|
|
|
# Validate supported OS
|
|
case "$OS_NAME_LOWER" in
|
|
ubuntu|debian|linuxmint|mint)
|
|
success "Detected supported OS: $OS_NAME $OS_VERSION"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS: $OS_NAME $OS_VERSION. Only Ubuntu/Debian/Linux Mint/macOS are supported."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Check system requirements
|
|
check_system_requirements() {
|
|
local min_ram_gb=${1:-2}
|
|
local min_disk_gb=${2:-10}
|
|
local target_dir="${3:-/}"
|
|
|
|
info "🔍 Checking system requirements..."
|
|
|
|
# Check architecture
|
|
local arch=$(uname -m)
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS supports both x86_64 and arm64 (Apple Silicon)
|
|
if [[ "$arch" != "x86_64" && "$arch" != "arm64" ]]; then
|
|
error_exit "Unsupported architecture: $arch. Only x86_64 and arm64 are supported on macOS."
|
|
fi
|
|
else
|
|
# Linux only supports x86_64
|
|
if [[ "$arch" != "x86_64" ]]; then
|
|
error_exit "Unsupported architecture: $arch. Only x86_64 is supported."
|
|
fi
|
|
fi
|
|
|
|
# Check RAM
|
|
local ram_gb
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS memory check
|
|
ram_gb=$(sysctl -n hw.memsize | awk '{print int($1/1024/1024/1024)}')
|
|
elif command_exists free; then
|
|
ram_gb=$(free -g | awk '/^Mem:/{print $2}')
|
|
else
|
|
ram_gb=$(awk '/MemTotal/ {print int($2/1024/1024)}' /proc/meminfo)
|
|
fi
|
|
|
|
if [[ $ram_gb -lt $min_ram_gb ]]; then
|
|
error_exit "Insufficient RAM: ${ram_gb}GB detected, ${min_ram_gb}GB required"
|
|
fi
|
|
|
|
# Check disk space
|
|
local disk_avail_gb
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS disk space check
|
|
disk_avail_gb=$(df -g "$target_dir" | tail -n 1 | awk '{print $4}')
|
|
elif command_exists df; then
|
|
disk_avail_gb=$(df -BG --output=avail "$target_dir" | tail -n 1 | tr -d 'G')
|
|
else
|
|
error_exit "Unable to check disk space - 'df' command not available"
|
|
fi
|
|
|
|
if [[ $disk_avail_gb -lt $min_disk_gb ]]; then
|
|
error_exit "Insufficient disk space: ${disk_avail_gb}GB available in $target_dir, ${min_disk_gb}GB required"
|
|
fi
|
|
|
|
# Check CPU cores
|
|
local cpu_cores
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
cpu_cores=$(sysctl -n hw.ncpu)
|
|
else
|
|
cpu_cores=$(nproc)
|
|
fi
|
|
|
|
if [[ $cpu_cores -lt 2 ]]; then
|
|
warning "Only ${cpu_cores} CPU core(s) detected. 2+ cores recommended for optimal performance."
|
|
fi
|
|
|
|
success "System requirements met: ${ram_gb}GB RAM, ${disk_avail_gb}GB disk space, ${cpu_cores} CPU cores"
|
|
}
|
|
|
|
# Check if running in a container
|
|
check_container_environment() {
|
|
if [[ -f /.dockerenv ]] || grep -q 'container=docker' /proc/1/environ 2>/dev/null; then
|
|
warning "Running inside a container. Some features may not work correctly."
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Check internet connectivity
|
|
check_internet() {
|
|
local test_urls=(
|
|
"google.com"
|
|
"github.com"
|
|
"docker.com"
|
|
)
|
|
|
|
info "🌐 Checking internet connectivity..."
|
|
|
|
for url in "${test_urls[@]}"; do
|
|
if ping -c 1 -W 5 "$url" >/dev/null 2>&1; then
|
|
success "Internet connectivity verified"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
error_exit "No internet connectivity detected. Please check your network connection."
|
|
}
|
|
|
|
# Check Docker requirements
|
|
check_docker_requirements() {
|
|
info "🐳 Checking Docker requirements..."
|
|
|
|
# Check if Docker is installed
|
|
if ! command_exists docker; then
|
|
warning "Docker not installed. Will be installed automatically."
|
|
return 1
|
|
fi
|
|
|
|
# Check if Docker daemon is running
|
|
if ! docker info >/dev/null 2>&1; then
|
|
warning "Docker daemon not running. Will be started automatically."
|
|
return 1
|
|
fi
|
|
|
|
# Check Docker version
|
|
local docker_version=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
|
|
local min_version="20.10.0"
|
|
|
|
if ! version_compare "$docker_version" "$min_version"; then
|
|
error_exit "Docker version $docker_version is too old. Minimum required: $min_version"
|
|
fi
|
|
|
|
# Check Docker Compose
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
error_exit "Docker Compose not available. Please install Docker Compose v2+"
|
|
fi
|
|
|
|
success "Docker requirements met"
|
|
return 0
|
|
}
|
|
|
|
# Compare version strings (returns 0 if version1 >= version2)
|
|
version_compare() {
|
|
local version1="$1"
|
|
local version2="$2"
|
|
|
|
# Convert versions to arrays
|
|
local IFS='.'
|
|
local -a ver1=($version1)
|
|
local -a ver2=($version2)
|
|
|
|
# Compare each component
|
|
for i in {0..2}; do
|
|
local v1=${ver1[$i]:-0}
|
|
local v2=${ver2[$i]:-0}
|
|
|
|
if [[ $v1 -gt $v2 ]]; then
|
|
return 0
|
|
elif [[ $v1 -lt $v2 ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Check if user has sudo privileges
|
|
check_sudo() {
|
|
if [[ $EUID -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! sudo -n true 2>/dev/null; then
|
|
error_exit "This script requires sudo privileges. Please run with sudo or as root."
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Get system timezone
|
|
get_system_timezone() {
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS timezone detection
|
|
readlink /etc/localtime | sed 's|/var/db/timezone/zoneinfo/||' 2>/dev/null || \
|
|
ls -la /etc/localtime | awk '{print $NF}' | sed 's|/var/db/timezone/zoneinfo/||' 2>/dev/null || \
|
|
echo "UTC"
|
|
elif [[ -f /etc/timezone ]]; then
|
|
cat /etc/timezone
|
|
elif [[ -L /etc/localtime ]]; then
|
|
readlink /etc/localtime | sed 's|/usr/share/zoneinfo/||'
|
|
else
|
|
timedatectl show --property=Timezone --value 2>/dev/null || echo "UTC"
|
|
fi
|
|
}
|
|
|
|
# Validate timezone
|
|
validate_timezone() {
|
|
local timezone="$1"
|
|
|
|
if [[ -z "$timezone" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS timezone validation
|
|
if [[ -f "/var/db/timezone/zoneinfo/$timezone" ]]; then
|
|
return 0
|
|
fi
|
|
else
|
|
# Linux timezone validation
|
|
if [[ -f "/usr/share/zoneinfo/$timezone" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Check available storage space for specific path
|
|
check_storage_space() {
|
|
local path="$1"
|
|
local required_gb="$2"
|
|
|
|
# Create directory if it doesn't exist
|
|
mkdir -p "$path" 2>/dev/null || true
|
|
|
|
local available_gb
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
available_gb=$(df -g "$path" | tail -n 1 | awk '{print $4}')
|
|
else
|
|
available_gb=$(df -BG --output=avail "$path" | tail -n 1 | tr -d 'G')
|
|
fi
|
|
|
|
if [[ $available_gb -lt $required_gb ]]; then
|
|
error_exit "Insufficient storage space in $path: ${available_gb}GB available, ${required_gb}GB required"
|
|
fi
|
|
|
|
success "Storage space check passed: ${available_gb}GB available in $path"
|
|
}
|
|
|
|
# Check if directory is writable
|
|
check_directory_writable() {
|
|
local dir="$1"
|
|
|
|
# Try to create directory if it doesn't exist
|
|
if ! mkdir -p "$dir" 2>/dev/null; then
|
|
error_exit "Cannot create directory: $dir"
|
|
fi
|
|
|
|
# Check if writable
|
|
if ! [[ -w "$dir" ]]; then
|
|
error_exit "Directory not writable: $dir"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Get current user info (handles sudo correctly)
|
|
get_user_info() {
|
|
local -A user_info
|
|
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
user_info["username"]="$SUDO_USER"
|
|
user_info["uid"]=$(id -u "$SUDO_USER")
|
|
user_info["gid"]=$(id -g "$SUDO_USER")
|
|
user_info["home"]=$(eval echo "~$SUDO_USER")
|
|
else
|
|
user_info["username"]="$USER"
|
|
user_info["uid"]=$(id -u)
|
|
user_info["gid"]=$(id -g)
|
|
user_info["home"]="$HOME"
|
|
fi
|
|
|
|
# Return as key=value pairs
|
|
for key in "${!user_info[@]}"; do
|
|
echo "${key}=${user_info[$key]}"
|
|
done
|
|
}
|
|
|
|
# Check if firewall is available and configured
|
|
check_firewall() {
|
|
info "🔥 Checking firewall status..."
|
|
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# macOS uses pfctl/firewall, but we'll skip automatic configuration
|
|
warning "macOS firewall detected. Automatic firewall configuration skipped."
|
|
info "💡 You may need to manually configure firewall rules if needed."
|
|
return 0
|
|
fi
|
|
|
|
if command_exists ufw; then
|
|
local ufw_status=$(ufw status | head -n1 | awk '{print $2}')
|
|
|
|
case "$ufw_status" in
|
|
"active")
|
|
success "UFW firewall is active"
|
|
return 0
|
|
;;
|
|
"inactive")
|
|
warning "UFW firewall is inactive. Will be configured automatically."
|
|
return 1
|
|
;;
|
|
*)
|
|
warning "UFW firewall status unknown: $ufw_status"
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
warning "UFW not installed. Will be installed automatically."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Comprehensive system check
|
|
run_system_checks() {
|
|
local min_ram_gb=${1:-2}
|
|
local min_disk_gb=${2:-10}
|
|
local target_dir="${3:-/}"
|
|
|
|
info "🔍 Running comprehensive system checks..."
|
|
|
|
check_root
|
|
detect_os
|
|
check_system_requirements "$min_ram_gb" "$min_disk_gb" "$target_dir"
|
|
check_internet
|
|
check_docker_requirements
|
|
|
|
# Skip firewall check for macOS (handled differently)
|
|
if [[ "$OS_NAME_LOWER" != "macos" ]]; then
|
|
check_firewall
|
|
fi
|
|
|
|
# Check for container environment (warning only)
|
|
check_container_environment
|
|
|
|
success "All system checks passed"
|
|
}
|
|
|
|
# Get platform-specific default paths
|
|
get_default_media_path() {
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# Use actual user, not root when running via sudo
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$USER"
|
|
fi
|
|
echo "/Users/$actual_user/hops/media"
|
|
else
|
|
# Use actual user, not root when running via sudo
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$USER"
|
|
fi
|
|
echo "/home/$actual_user/hops/media"
|
|
fi
|
|
}
|
|
|
|
get_default_config_path() {
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# Use actual user, not root when running via sudo
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$USER"
|
|
fi
|
|
echo "/Users/$actual_user/hops/config"
|
|
else
|
|
echo "/opt/appdata"
|
|
fi
|
|
}
|
|
|
|
get_default_homelab_path() {
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
# Use actual user, not root when running via sudo
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$USER"
|
|
fi
|
|
echo "/Users/$actual_user/hops"
|
|
else
|
|
# Use actual user, not root when running via sudo
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$USER"
|
|
fi
|
|
echo "/home/$actual_user/hops"
|
|
fi
|
|
}
|
|
|
|
# Get Docker socket path for current platform
|
|
get_docker_socket_path() {
|
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
|
echo "/var/run/docker.sock"
|
|
else
|
|
echo "/var/run/docker.sock"
|
|
fi
|
|
}
|
|
|
|
# Package management abstraction
|
|
install_package() {
|
|
local package="$1"
|
|
|
|
if [[ -z "$package" ]]; then
|
|
error_exit "install_package requires a package name"
|
|
fi
|
|
|
|
info "📦 Installing package: $package"
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
if ! command_exists brew; then
|
|
error_exit "Homebrew not found. Please install Homebrew first: https://brew.sh/"
|
|
fi
|
|
|
|
# Get the actual user (not root) to run brew commands
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$(whoami)"
|
|
fi
|
|
|
|
sudo -u "$actual_user" brew install "$package"
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
apt-get update && apt-get install -y "$package"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS for package installation: $OS_NAME"
|
|
;;
|
|
esac
|
|
|
|
success "Package installed: $package"
|
|
}
|
|
|
|
# Service management abstraction
|
|
start_service() {
|
|
local service="$1"
|
|
|
|
if [[ -z "$service" ]]; then
|
|
error_exit "start_service requires a service name"
|
|
fi
|
|
|
|
info "🚀 Starting service: $service"
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
if [[ "$service" == "docker" ]]; then
|
|
# On macOS, Docker Desktop handles this
|
|
info "Docker Desktop should be started manually or via Docker Desktop app"
|
|
else
|
|
# Use launchctl for other services
|
|
launchctl start "$service" 2>/dev/null || true
|
|
fi
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
systemctl start "$service"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS for service management: $OS_NAME"
|
|
;;
|
|
esac
|
|
|
|
success "Service started: $service"
|
|
}
|
|
|
|
# Check if service is running
|
|
is_service_running() {
|
|
local service="$1"
|
|
|
|
if [[ -z "$service" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
if [[ "$service" == "docker" ]]; then
|
|
# Check if Docker daemon is responding
|
|
docker info >/dev/null 2>&1
|
|
else
|
|
# Check with launchctl
|
|
launchctl list | grep -q "$service"
|
|
fi
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
systemctl is-active --quiet "$service"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Enable service to start on boot
|
|
enable_service() {
|
|
local service="$1"
|
|
|
|
if [[ -z "$service" ]]; then
|
|
error_exit "enable_service requires a service name"
|
|
fi
|
|
|
|
info "⚙️ Enabling service: $service"
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
if [[ "$service" == "docker" ]]; then
|
|
info "Docker Desktop auto-start should be configured in Docker Desktop settings"
|
|
else
|
|
# Use launchctl for other services
|
|
launchctl enable "$service" 2>/dev/null || true
|
|
fi
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
systemctl enable "$service"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS for service management: $OS_NAME"
|
|
;;
|
|
esac
|
|
|
|
success "Service enabled: $service"
|
|
}
|
|
|
|
# Get network interface IP address
|
|
get_primary_ip() {
|
|
local ip=""
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
# macOS network interface detection
|
|
ip=$(route get default | grep interface | awk '{print $2}' | head -1)
|
|
if [[ -n "$ip" ]]; then
|
|
ip=$(ifconfig "$ip" | grep 'inet ' | awk '{print $2}' | head -1)
|
|
fi
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
# Linux network interface detection
|
|
ip=$(hostname -I | awk '{print $1}')
|
|
;;
|
|
*)
|
|
# Fallback method
|
|
ip=$(ip route get 8.8.8.8 2>/dev/null | grep -oP 'src \K\S+' | head -1)
|
|
;;
|
|
esac
|
|
|
|
# Validate IP address
|
|
if is_valid_ip "$ip"; then
|
|
echo "$ip"
|
|
else
|
|
echo "localhost"
|
|
fi
|
|
}
|
|
|
|
# Remove existing Docker installation on Linux
|
|
remove_docker_linux() {
|
|
echo "DEBUG: remove_docker_linux() function called"
|
|
info "🗑️ Removing existing Docker installation..."
|
|
|
|
# Stop Docker service if running
|
|
if systemctl is-active --quiet docker; then
|
|
info "🛑 Stopping Docker service..."
|
|
systemctl stop docker
|
|
fi
|
|
|
|
# Stop Docker socket if running
|
|
if systemctl is-active --quiet docker.socket; then
|
|
info "🛑 Stopping Docker socket..."
|
|
systemctl stop docker.socket
|
|
fi
|
|
|
|
# Disable Docker service
|
|
if systemctl is-enabled --quiet docker; then
|
|
info "🔧 Disabling Docker service..."
|
|
systemctl disable docker
|
|
fi
|
|
|
|
# Remove Docker repository and GPG keys FIRST to prevent apt errors
|
|
info "🗑️ Removing Docker repository and keys..."
|
|
rm -f /etc/apt/sources.list.d/docker.list
|
|
rm -f /etc/apt/sources.list.d/*docker*
|
|
rm -f /etc/apt/keyrings/docker.gpg
|
|
rm -f /usr/share/keyrings/docker*
|
|
rm -f /etc/apt/keyrings/docker*
|
|
|
|
# Also check for and remove any repositories that might contain Linux Mint codenames
|
|
if grep -r "download.docker.com.*xia\|download.docker.com.*vera\|download.docker.com.*vanessa" /etc/apt/sources.list.d/ 2>/dev/null; then
|
|
info "🗑️ Found Docker repositories with Linux Mint codenames, removing..."
|
|
grep -l "download.docker.com.*xia\|download.docker.com.*vera\|download.docker.com.*vanessa" /etc/apt/sources.list.d/* 2>/dev/null | xargs rm -f
|
|
fi
|
|
|
|
# Clean apt cache to remove any cached repository data
|
|
apt-get clean
|
|
|
|
# Remove Docker packages
|
|
info "🗑️ Removing Docker packages..."
|
|
apt-get remove -y docker docker-engine docker.io containerd runc docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>/dev/null || true
|
|
apt-get purge -y docker docker-engine docker.io containerd runc docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>/dev/null || true
|
|
|
|
# Remove Docker Compose standalone if installed
|
|
if [[ -f "/usr/local/bin/docker-compose" ]]; then
|
|
info "🗑️ Removing Docker Compose standalone..."
|
|
rm -f "/usr/local/bin/docker-compose"
|
|
fi
|
|
|
|
# Remove Docker data directories
|
|
info "🗑️ Removing Docker data directories..."
|
|
local docker_dirs=(
|
|
"/var/lib/docker"
|
|
"/var/lib/containerd"
|
|
"/etc/docker"
|
|
"/etc/containerd"
|
|
"/run/docker"
|
|
"/run/containerd"
|
|
"/opt/containerd"
|
|
)
|
|
|
|
for dir in "${docker_dirs[@]}"; do
|
|
if [[ -d "$dir" ]]; then
|
|
rm -rf "$dir"
|
|
fi
|
|
done
|
|
|
|
# Remove Docker group
|
|
if getent group docker >/dev/null 2>&1; then
|
|
info "🗑️ Removing Docker group..."
|
|
groupdel docker 2>/dev/null || true
|
|
fi
|
|
|
|
# Repository and GPG keys already removed above
|
|
|
|
# Remove any remaining Docker processes
|
|
pkill -f docker 2>/dev/null || true
|
|
pkill -f containerd 2>/dev/null || true
|
|
|
|
# Clean up package manager cache
|
|
apt-get autoremove -y 2>/dev/null || true
|
|
apt-get autoclean 2>/dev/null || true
|
|
|
|
success "Docker removal completed"
|
|
}
|
|
|
|
# Remove existing Docker installation on macOS
|
|
remove_docker_macos() {
|
|
info "🗑️ Removing existing Docker installation..."
|
|
|
|
# Get the actual user (not root) for operations
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$(whoami)"
|
|
fi
|
|
|
|
# Stop Docker Desktop if running
|
|
if pgrep -f "Docker Desktop" >/dev/null 2>&1; then
|
|
info "🛑 Stopping Docker Desktop..."
|
|
sudo -u "$actual_user" osascript -e 'quit app "Docker Desktop"' 2>/dev/null || true
|
|
sleep 3
|
|
fi
|
|
|
|
# Remove Docker Desktop application
|
|
if [[ -d "/Applications/Docker.app" ]]; then
|
|
info "🗑️ Removing Docker Desktop application..."
|
|
rm -rf "/Applications/Docker.app"
|
|
fi
|
|
|
|
# Remove Docker CLI tools installed via Homebrew
|
|
if command_exists brew; then
|
|
info "🗑️ Removing Docker via Homebrew..."
|
|
sudo -u "$actual_user" brew uninstall --cask docker 2>/dev/null || true
|
|
sudo -u "$actual_user" brew uninstall docker 2>/dev/null || true
|
|
sudo -u "$actual_user" brew uninstall docker-compose 2>/dev/null || true
|
|
sudo -u "$actual_user" brew uninstall docker-machine 2>/dev/null || true
|
|
sudo -u "$actual_user" brew uninstall docker-buildx 2>/dev/null || true
|
|
sudo -u "$actual_user" brew uninstall containerd 2>/dev/null || true
|
|
fi
|
|
|
|
# Remove Docker data directories
|
|
local docker_dirs=(
|
|
"/Users/$actual_user/.docker"
|
|
"/Users/$actual_user/Library/Preferences/com.docker.docker.plist"
|
|
"/Users/$actual_user/Library/Saved Application State/com.electron.docker-frontend.savedState"
|
|
"/Users/$actual_user/Library/Group Containers/group.com.docker"
|
|
"/Users/$actual_user/Library/Containers/com.docker.docker"
|
|
"/Users/$actual_user/Library/Application Support/Docker Desktop"
|
|
"/Users/$actual_user/Library/Logs/Docker Desktop"
|
|
"/Users/$actual_user/Library/Preferences/com.electron.docker-frontend.plist"
|
|
"/Users/$actual_user/Library/Caches/com.docker.docker"
|
|
)
|
|
|
|
for dir in "${docker_dirs[@]}"; do
|
|
if [[ -e "$dir" ]]; then
|
|
info "🗑️ Removing: $dir"
|
|
rm -rf "$dir"
|
|
fi
|
|
done
|
|
|
|
# Remove Docker symlinks and binaries
|
|
local docker_links=(
|
|
"/usr/local/bin/docker"
|
|
"/usr/local/bin/docker-compose"
|
|
"/usr/local/bin/docker-machine"
|
|
"/usr/local/bin/docker-buildx"
|
|
"/usr/local/bin/containerd"
|
|
"/usr/local/bin/containerd-shim"
|
|
"/usr/local/bin/containerd-shim-runc-v2"
|
|
"/usr/local/bin/ctr"
|
|
"/usr/local/bin/runc"
|
|
"/usr/local/bin/docker-credential-desktop"
|
|
"/usr/local/bin/docker-credential-ecr-login"
|
|
"/usr/local/bin/docker-credential-osxkeychain"
|
|
"/usr/local/bin/kubectl"
|
|
"/usr/local/bin/kubectl.docker"
|
|
"/usr/local/bin/vpnkit"
|
|
"/usr/local/bin/com.docker.cli"
|
|
)
|
|
|
|
for link in "${docker_links[@]}"; do
|
|
if [[ -L "$link" ]] || [[ -f "$link" ]]; then
|
|
info "🗑️ Removing: $link"
|
|
rm -f "$link"
|
|
fi
|
|
done
|
|
|
|
# Kill any remaining Docker processes
|
|
pkill -f docker 2>/dev/null || true
|
|
pkill -f com.docker 2>/dev/null || true
|
|
pkill -f containerd 2>/dev/null || true
|
|
|
|
success "Docker removal completed"
|
|
}
|
|
|
|
# Check existing Docker installation on macOS
|
|
check_existing_docker_macos() {
|
|
local docker_version=""
|
|
local docker_desktop_version=""
|
|
local installation_method=""
|
|
|
|
# Check for Docker command
|
|
if command_exists docker; then
|
|
docker_version=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
|
|
fi
|
|
|
|
# Check for Docker Desktop
|
|
if [[ -d "/Applications/Docker.app" ]]; then
|
|
docker_desktop_version=$(defaults read /Applications/Docker.app/Contents/Info.plist CFBundleShortVersionString 2>/dev/null || echo "unknown")
|
|
installation_method="Docker Desktop"
|
|
fi
|
|
|
|
# Check if installed via Homebrew
|
|
if command_exists brew; then
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$(whoami)"
|
|
fi
|
|
|
|
if sudo -u "$actual_user" brew list --cask docker >/dev/null 2>&1; then
|
|
installation_method="Homebrew Cask"
|
|
elif sudo -u "$actual_user" brew list docker >/dev/null 2>&1; then
|
|
installation_method="Homebrew"
|
|
fi
|
|
fi
|
|
|
|
# Return results
|
|
echo "docker_version=$docker_version"
|
|
echo "docker_desktop_version=$docker_desktop_version"
|
|
echo "installation_method=$installation_method"
|
|
}
|
|
|
|
# Install Docker for the current platform
|
|
install_docker() {
|
|
echo "DEBUG: install_docker() function called from lib/system.sh"
|
|
info "🐳 Installing Docker..."
|
|
|
|
case "$OS_NAME_LOWER" in
|
|
"macos")
|
|
# Check for existing Docker installation
|
|
local docker_info
|
|
docker_info=$(check_existing_docker_macos)
|
|
|
|
local docker_version=$(echo "$docker_info" | grep "docker_version=" | cut -d'=' -f2)
|
|
local docker_desktop_version=$(echo "$docker_info" | grep "docker_desktop_version=" | cut -d'=' -f2)
|
|
local installation_method=$(echo "$docker_info" | grep "installation_method=" | cut -d'=' -f2)
|
|
|
|
# If Docker is already installed, ask for confirmation to reinstall
|
|
if [[ -n "$docker_version" ]] || [[ -n "$docker_desktop_version" ]] || [[ -n "$installation_method" ]]; then
|
|
warning "Existing Docker installation detected:"
|
|
if [[ -n "$docker_version" ]]; then
|
|
info " Docker CLI version: $docker_version"
|
|
fi
|
|
if [[ -n "$docker_desktop_version" ]]; then
|
|
info " Docker Desktop version: $docker_desktop_version"
|
|
fi
|
|
if [[ -n "$installation_method" ]]; then
|
|
info " Installation method: $installation_method"
|
|
fi
|
|
|
|
echo
|
|
warning "⚠️ To ensure a clean HOPS installation, we recommend removing the existing Docker installation."
|
|
warning " This will remove all Docker data, containers, images, and volumes."
|
|
echo
|
|
|
|
read -p "❓ Do you want to remove the existing Docker installation and reinstall? (y/N): " -n 1 -r
|
|
echo
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
remove_docker_macos
|
|
|
|
# Double-check removal was successful
|
|
sleep 2
|
|
if command_exists docker && docker info >/dev/null 2>&1; then
|
|
error_exit "Docker removal failed. Please manually remove Docker and try again."
|
|
fi
|
|
else
|
|
info "Keeping existing Docker installation. Checking if it's compatible..."
|
|
|
|
# Check if existing Docker is compatible
|
|
if ! docker info >/dev/null 2>&1; then
|
|
error_exit "Existing Docker installation is not running. Please start Docker Desktop manually or choose to reinstall."
|
|
fi
|
|
|
|
# Check Docker Compose
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
error_exit "Docker Compose not available in existing installation. Please reinstall Docker Desktop."
|
|
fi
|
|
|
|
success "Existing Docker installation is compatible"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Install fresh Docker Desktop
|
|
info "📦 Installing Docker Desktop for Mac..."
|
|
|
|
# Check if Homebrew is available
|
|
if ! command_exists brew; then
|
|
warning "Homebrew not found. Installing Homebrew first..."
|
|
|
|
# Get the actual user (not root) to install Homebrew
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$(whoami)"
|
|
fi
|
|
|
|
# Install Homebrew as the actual user, not root
|
|
sudo -u "$actual_user" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
|
|
# Add Homebrew to PATH for current session
|
|
if [[ -f "/opt/homebrew/bin/brew" ]]; then
|
|
eval "$(/opt/homebrew/bin/brew shellenv)"
|
|
elif [[ -f "/usr/local/bin/brew" ]]; then
|
|
eval "$(/usr/local/bin/brew shellenv)"
|
|
fi
|
|
fi
|
|
|
|
# Install Docker Desktop via Homebrew Cask
|
|
info "📦 Installing Docker Desktop via Homebrew..."
|
|
|
|
# Get the actual user (not root) to run brew commands
|
|
local actual_user
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
actual_user="$SUDO_USER"
|
|
else
|
|
actual_user="$(whoami)"
|
|
fi
|
|
|
|
# Check for and handle conflicting files
|
|
local conflicting_files=()
|
|
local potential_conflicts=(
|
|
"/usr/local/bin/compose-bridge"
|
|
"/usr/local/bin/docker"
|
|
"/usr/local/bin/docker-compose"
|
|
"/usr/local/bin/docker-credential-desktop"
|
|
"/usr/local/bin/docker-credential-osxkeychain"
|
|
"/usr/local/bin/hub-tool"
|
|
"/usr/local/bin/kubectl"
|
|
"/Applications/Docker.app"
|
|
"/usr/local/Caskroom/docker"
|
|
"/usr/local/Caskroom/docker-desktop"
|
|
"/opt/homebrew/Caskroom/docker"
|
|
"/opt/homebrew/Caskroom/docker-desktop"
|
|
)
|
|
|
|
for file in "${potential_conflicts[@]}"; do
|
|
if [[ -e "$file" ]]; then
|
|
conflicting_files+=("$file")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#conflicting_files[@]} -gt 0 ]]; then
|
|
warning "⚠️ Conflicting Docker files detected:"
|
|
for file in "${conflicting_files[@]}"; do
|
|
info " - $file"
|
|
done
|
|
echo
|
|
|
|
read -p "❓ Do you want to remove these conflicting files before installing Docker? (y/N): " -n 1 -r
|
|
echo
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
for file in "${conflicting_files[@]}"; do
|
|
info "🗑️ Removing conflicting file: $file"
|
|
if [[ -d "$file" ]]; then
|
|
rm -rf "$file" 2>/dev/null || {
|
|
warning "Failed to remove $file - you may need to remove it manually"
|
|
}
|
|
else
|
|
rm -f "$file" 2>/dev/null || {
|
|
warning "Failed to remove $file - you may need to remove it manually"
|
|
}
|
|
fi
|
|
done
|
|
success "Conflicting files removed"
|
|
else
|
|
warning "Keeping existing files. Installation may fail due to conflicts."
|
|
fi
|
|
fi
|
|
|
|
# Install Docker Desktop with force flag to overwrite existing files
|
|
info "📦 Installing Docker Desktop via Homebrew..."
|
|
sudo -u "$actual_user" brew install --cask docker --force
|
|
|
|
# Start Docker Desktop with user interaction guidance
|
|
info "🚀 Starting Docker Desktop..."
|
|
open -a "Docker Desktop"
|
|
|
|
echo
|
|
warning "📋 IMPORTANT: Docker Desktop First-Time Setup Required"
|
|
warning "=============================================="
|
|
info "Docker Desktop will now open and may require user interaction:"
|
|
info " 1. ✅ Click 'Open' if macOS asks to confirm opening Docker Desktop"
|
|
info " 2. ✅ Accept the Docker Desktop license agreement"
|
|
info " 3. ✅ Complete the Docker Desktop onboarding/tutorial"
|
|
info " 4. ✅ Sign in to Docker Hub (optional, can skip)"
|
|
info " 5. ✅ Configure Docker settings if prompted"
|
|
info " 6. ⚠️ Do NOT close Docker Desktop during setup"
|
|
echo
|
|
warning "The installation will wait for you to complete these steps..."
|
|
echo
|
|
|
|
# Wait for user to complete setup
|
|
read -p "❓ Press ENTER after you've completed the Docker Desktop setup and it's running..."
|
|
echo
|
|
|
|
# Wait for Docker to start
|
|
info "⏳ Verifying Docker Desktop is running..."
|
|
local max_wait=60
|
|
local wait_time=0
|
|
local docker_started=false
|
|
|
|
while [[ $wait_time -lt $max_wait ]]; do
|
|
# Try to connect to Docker daemon
|
|
if docker info >/dev/null 2>&1; then
|
|
docker_started=true
|
|
break
|
|
fi
|
|
|
|
sleep 2
|
|
((wait_time += 2))
|
|
echo -n "."
|
|
done
|
|
|
|
echo
|
|
|
|
if [[ "$docker_started" == true ]]; then
|
|
success "✅ Docker Desktop is running and ready!"
|
|
else
|
|
warning "❌ Docker Desktop doesn't appear to be running."
|
|
warning "Please ensure Docker Desktop is:"
|
|
info " 1. Completely started (not just the app, but the Docker engine)"
|
|
info " 2. Shows 'Docker Desktop is running' in the menu bar"
|
|
info " 3. The whale icon in the menu bar is solid (not animated)"
|
|
echo
|
|
|
|
read -p "❓ Try verification again? (y/N): " -n 1 -r
|
|
echo
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
if docker info >/dev/null 2>&1; then
|
|
success "✅ Docker Desktop is now running!"
|
|
else
|
|
error_exit "Docker Desktop still not responding. Please resolve the issue and re-run HOPS installation."
|
|
fi
|
|
else
|
|
error_exit "Installation cancelled. Please ensure Docker Desktop is running and try again."
|
|
fi
|
|
fi
|
|
;;
|
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
|
echo "DEBUG: Linux/Ubuntu/Mint Docker installation path"
|
|
# Check for existing Docker installation
|
|
if command_exists docker; then
|
|
echo "DEBUG: Existing Docker installation detected"
|
|
local docker_version=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
|
|
|
|
warning "Existing Docker installation detected:"
|
|
if [[ -n "$docker_version" ]]; then
|
|
info " Docker version: $docker_version"
|
|
fi
|
|
|
|
echo
|
|
warning "⚠️ To ensure a clean HOPS installation, we recommend removing the existing Docker installation."
|
|
warning " This will remove all Docker data, containers, images, and volumes."
|
|
echo
|
|
|
|
read -p "❓ Do you want to remove the existing Docker installation and reinstall? (y/N): " -n 1 -r
|
|
echo
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
remove_docker_linux
|
|
|
|
# Double-check removal was successful
|
|
sleep 2
|
|
if command_exists docker && docker info >/dev/null 2>&1; then
|
|
error_exit "Docker removal failed. Please manually remove Docker and try again."
|
|
fi
|
|
else
|
|
info "Keeping existing Docker installation. Checking if it's compatible..."
|
|
|
|
# Check if existing Docker is compatible
|
|
if ! docker info >/dev/null 2>&1; then
|
|
error_exit "Existing Docker installation is not running. Please start Docker service manually or choose to reinstall."
|
|
fi
|
|
|
|
# Check Docker Compose
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
error_exit "Docker Compose not available in existing installation. Please reinstall Docker."
|
|
fi
|
|
|
|
success "Existing Docker installation is compatible"
|
|
echo "DEBUG: Returning early - skipping Linux Mint repository fix"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
echo "DEBUG: No existing Docker found, proceeding with fresh installation"
|
|
|
|
# Check for and handle conflicting files
|
|
local conflicting_files=()
|
|
local potential_conflicts=(
|
|
"/usr/bin/docker"
|
|
"/usr/bin/docker-compose"
|
|
"/usr/local/bin/docker"
|
|
"/usr/local/bin/docker-compose"
|
|
"/etc/docker"
|
|
"/var/lib/docker"
|
|
)
|
|
|
|
for file in "${potential_conflicts[@]}"; do
|
|
if [[ -e "$file" ]]; then
|
|
conflicting_files+=("$file")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#conflicting_files[@]} -gt 0 ]]; then
|
|
warning "⚠️ Conflicting Docker files detected:"
|
|
for file in "${conflicting_files[@]}"; do
|
|
info " - $file"
|
|
done
|
|
echo
|
|
|
|
read -p "❓ Do you want to remove these conflicting files before installing Docker? (y/N): " -n 1 -r
|
|
echo
|
|
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
for file in "${conflicting_files[@]}"; do
|
|
info "🗑️ Removing conflicting file: $file"
|
|
if [[ -d "$file" ]]; then
|
|
rm -rf "$file" 2>/dev/null || {
|
|
warning "Failed to remove $file - you may need to remove it manually"
|
|
}
|
|
else
|
|
rm -f "$file" 2>/dev/null || {
|
|
warning "Failed to remove $file - you may need to remove it manually"
|
|
}
|
|
fi
|
|
done
|
|
success "Conflicting files removed"
|
|
else
|
|
warning "Keeping existing files. Installation may fail due to conflicts."
|
|
fi
|
|
fi
|
|
|
|
# Install fresh Docker using manual repository setup
|
|
info "📦 Installing Docker Engine..."
|
|
|
|
# Install required packages
|
|
apt-get update
|
|
apt-get install -y ca-certificates curl gnupg lsb-release
|
|
|
|
# Add Docker GPG key
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
|
|
# Add Docker repository with proper Ubuntu codename detection for Linux Mint
|
|
local ubuntu_codename
|
|
echo "DEBUG: Configuring Docker repository"
|
|
echo "DEBUG: Detected OS: $(lsb_release -is)"
|
|
if [[ "$(lsb_release -is)" == "LinuxMint" ]]; then
|
|
echo "DEBUG: Linux Mint detected, checking for UBUNTU_CODENAME"
|
|
# Linux Mint provides UBUNTU_CODENAME in /etc/os-release
|
|
if [[ -f /etc/os-release ]]; then
|
|
ubuntu_codename=$(grep '^UBUNTU_CODENAME=' /etc/os-release | cut -d= -f2)
|
|
echo "DEBUG: Found UBUNTU_CODENAME=$ubuntu_codename in /etc/os-release"
|
|
fi
|
|
|
|
# Fallback to version mapping if UBUNTU_CODENAME not found
|
|
if [[ -z "$ubuntu_codename" ]]; then
|
|
case "$(lsb_release -rs)" in
|
|
"22"|"22.1"|"22.2"|"22.3")
|
|
ubuntu_codename="noble" # Ubuntu 24.04
|
|
;;
|
|
"21"|"21.1"|"21.2"|"21.3")
|
|
ubuntu_codename="jammy" # Ubuntu 22.04
|
|
;;
|
|
"20"|"20.1"|"20.2"|"20.3")
|
|
ubuntu_codename="focal" # Ubuntu 20.04
|
|
;;
|
|
*)
|
|
ubuntu_codename="noble" # Default to latest LTS
|
|
;;
|
|
esac
|
|
fi
|
|
else
|
|
ubuntu_codename=$(lsb_release -cs)
|
|
fi
|
|
|
|
info "Using Ubuntu codename: $ubuntu_codename for Docker repository"
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $ubuntu_codename stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
|
|
# Update package index with Docker packages
|
|
apt-get update
|
|
|
|
# Install Docker
|
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
|
|
# Add user to docker group if we're running with sudo
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
usermod -aG docker "$SUDO_USER"
|
|
fi
|
|
|
|
# Start and enable Docker service
|
|
start_service docker
|
|
enable_service docker
|
|
|
|
success "Docker installed and configured"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS for Docker installation: $OS_NAME"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Check if Docker is properly installed and running
|
|
check_docker_installation() {
|
|
info "🐳 Checking Docker installation..."
|
|
|
|
# Check if Docker command exists
|
|
if ! command_exists docker; then
|
|
return 1
|
|
fi
|
|
|
|
# Check if Docker daemon is running
|
|
if ! docker info >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
# Check Docker Compose
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
success "Docker is properly installed and running"
|
|
return 0
|
|
} |