Rename scripts for clarity and add Huntarr service
- Renamed all scripts to descriptive names without prefixes: • hops.sh → hops (main script) • hops_installer_enhanced.sh → install • hops_uninstaller_fixed.sh → uninstall • hops_service_definitions.sh → services • hops_install.sh → setup • hops_privileged_setup.sh → privileged-setup • hops_user_operations.sh → user-operations • hops_service_definitions_improved.sh → services-improved - Added Huntarr service support: • Docker image: ghcr.io/plexguide/huntarr:latest • Port: 9705 with /health endpoint • Missing media discovery and automation • Integrates with *arr stack services • Added to installer menu as option 8 - Updated all script references and documentation - Updated service categories in README and CLAUDE.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+22
-4
@@ -4,6 +4,12 @@
|
||||
# Shared functions for logging, error handling, and UI
|
||||
# Version: 3.1.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'
|
||||
@@ -27,7 +33,13 @@ setup_logging() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
LOG_DIR="/var/log/hops"
|
||||
# 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
|
||||
@@ -214,10 +226,16 @@ is_valid_ip() {
|
||||
local regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
|
||||
|
||||
if [[ $ip =~ $regex ]]; then
|
||||
local IFS='.'
|
||||
local -a octets=($ip)
|
||||
# Split IP into octets using parameter expansion
|
||||
local octet1="${ip%%.*}"
|
||||
local temp="${ip#*.}"
|
||||
local octet2="${temp%%.*}"
|
||||
temp="${temp#*.}"
|
||||
local octet3="${temp%%.*}"
|
||||
local octet4="${temp#*.}"
|
||||
|
||||
for octet in "${octets[@]}"; do
|
||||
# Check each octet
|
||||
for octet in "$octet1" "$octet2" "$octet3" "$octet4"; do
|
||||
if [[ $octet -gt 255 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
+9
-9
@@ -449,7 +449,7 @@ EOF
|
||||
|
||||
# Generate service definitions
|
||||
for service in "${services[@]}"; do
|
||||
if "$SCRIPT_DIR/hops_service_definitions_improved.sh" generate "$service" >> "$compose_file"; then
|
||||
if "$SCRIPT_DIR/services-improved" generate "$service" >> "$compose_file"; then
|
||||
success "Added service: $service"
|
||||
else
|
||||
error_exit "Failed to generate service definition for: $service"
|
||||
@@ -616,7 +616,7 @@ fi
|
||||
|
||||
# Phase 1: Privileged setup
|
||||
info "📋 Phase 1: Privileged setup (requires root)"
|
||||
if "$SCRIPT_DIR/hops_privileged_setup.sh"; then
|
||||
if "$SCRIPT_DIR/privileged-setup"; then
|
||||
success "Privileged setup completed"
|
||||
else
|
||||
error_exit "Privileged setup failed"
|
||||
@@ -651,7 +651,7 @@ case "$choice" in
|
||||
;;
|
||||
4)
|
||||
echo "Available services:"
|
||||
"$SCRIPT_DIR/hops_service_definitions_improved.sh" list
|
||||
"$SCRIPT_DIR/services-improved" list
|
||||
read -p "Enter service names (space-separated): " -a services
|
||||
;;
|
||||
*)
|
||||
@@ -661,10 +661,10 @@ case "$choice" in
|
||||
esac
|
||||
|
||||
# Generate and deploy
|
||||
if "$SCRIPT_DIR/hops_user_operations.sh" generate "${services[@]}"; then
|
||||
if "$SCRIPT_DIR/user-operations" generate "${services[@]}"; then
|
||||
echo "Configuration generated successfully"
|
||||
|
||||
if "$SCRIPT_DIR/hops_user_operations.sh" deploy; then
|
||||
if "$SCRIPT_DIR/user-operations" deploy; then
|
||||
echo "Services deployed successfully"
|
||||
else
|
||||
echo "Deployment failed"
|
||||
@@ -677,7 +677,7 @@ fi
|
||||
USERSCRIPT
|
||||
|
||||
success "Installation completed successfully"
|
||||
success "Services are now running. Check status with: ./hops_user_operations.sh status"
|
||||
success "Services are now running. Check status with: ./user-operations status"
|
||||
EOF
|
||||
|
||||
chmod +x "$wrapper_script"
|
||||
@@ -703,9 +703,9 @@ main() {
|
||||
;;
|
||||
|
||||
"create-all")
|
||||
create_privileged_setup "hops_privileged_setup.sh"
|
||||
create_user_script "hops_user_operations.sh"
|
||||
create_installation_wrapper "hops_install.sh"
|
||||
create_privileged_setup "privileged-setup"
|
||||
create_user_script "user-operations"
|
||||
create_installation_wrapper "setup"
|
||||
;;
|
||||
|
||||
"run")
|
||||
|
||||
+686
-13
@@ -5,8 +5,8 @@
|
||||
# Version: 3.1.0
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$LIB_DIR/common.sh"
|
||||
|
||||
# Global variables for system info
|
||||
OS_NAME=""
|
||||
@@ -17,6 +17,16 @@ OS_NAME_LOWER=""
|
||||
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)
|
||||
@@ -35,7 +45,7 @@ detect_os() {
|
||||
success "Detected supported OS: $OS_NAME $OS_VERSION"
|
||||
;;
|
||||
*)
|
||||
error_exit "Unsupported OS: $OS_NAME $OS_VERSION. Only Ubuntu/Debian/Linux Mint are supported."
|
||||
error_exit "Unsupported OS: $OS_NAME $OS_VERSION. Only Ubuntu/Debian/Linux Mint/macOS are supported."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -50,13 +60,24 @@ check_system_requirements() {
|
||||
|
||||
# Check architecture
|
||||
local arch=$(uname -m)
|
||||
if [[ "$arch" != "x86_64" ]]; then
|
||||
error_exit "Unsupported architecture: $arch. Only x86_64 is supported."
|
||||
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 command_exists free; then
|
||||
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)
|
||||
@@ -68,7 +89,10 @@ check_system_requirements() {
|
||||
|
||||
# Check disk space
|
||||
local disk_avail_gb
|
||||
if command_exists df; then
|
||||
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"
|
||||
@@ -79,7 +103,13 @@ check_system_requirements() {
|
||||
fi
|
||||
|
||||
# Check CPU cores
|
||||
local cpu_cores=$(nproc)
|
||||
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
|
||||
@@ -189,7 +219,12 @@ check_sudo() {
|
||||
|
||||
# Get system timezone
|
||||
get_system_timezone() {
|
||||
if [[ -f /etc/timezone ]]; then
|
||||
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/||'
|
||||
@@ -206,8 +241,16 @@ validate_timezone() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "/usr/share/zoneinfo/$timezone" ]]; then
|
||||
return 0
|
||||
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
|
||||
@@ -221,7 +264,12 @@ check_storage_space() {
|
||||
# Create directory if it doesn't exist
|
||||
mkdir -p "$path" 2>/dev/null || true
|
||||
|
||||
local available_gb=$(df -BG --output=avail "$path" | tail -n 1 | tr -d 'G')
|
||||
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"
|
||||
@@ -273,6 +321,13 @@ get_user_info() {
|
||||
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}')
|
||||
|
||||
@@ -309,10 +364,628 @@ run_system_checks() {
|
||||
check_system_requirements "$min_ram_gb" "$min_disk_gb" "$target_dir"
|
||||
check_internet
|
||||
check_docker_requirements
|
||||
check_firewall
|
||||
|
||||
# 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
|
||||
echo "/Users/$USER/homelab/media"
|
||||
else
|
||||
echo "/mnt/media"
|
||||
fi
|
||||
}
|
||||
|
||||
get_default_config_path() {
|
||||
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||
echo "/Users/$USER/homelab/config"
|
||||
else
|
||||
echo "/opt/appdata"
|
||||
fi
|
||||
}
|
||||
|
||||
get_default_homelab_path() {
|
||||
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||
echo "/Users/$USER/homelab"
|
||||
else
|
||||
echo "/home/$USER/homelab"
|
||||
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() {
|
||||
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 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
|
||||
|
||||
# Remove Docker repository
|
||||
if [[ -f "/etc/apt/sources.list.d/docker.list" ]]; then
|
||||
info "🗑️ Removing Docker repository..."
|
||||
rm -f "/etc/apt/sources.list.d/docker.list"
|
||||
fi
|
||||
|
||||
# Remove Docker GPG key
|
||||
if [[ -f "/etc/apt/keyrings/docker.gpg" ]]; then
|
||||
rm -f "/etc/apt/keyrings/docker.gpg"
|
||||
fi
|
||||
|
||||
# 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() {
|
||||
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
|
||||
|
||||
# Remove conflicting compose-bridge binary if it exists
|
||||
if [[ -f "/usr/local/bin/compose-bridge" ]]; then
|
||||
info "🗑️ Removing conflicting compose-bridge binary..."
|
||||
rm -f "/usr/local/bin/compose-bridge" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
sudo -u "$actual_user" brew install --cask docker
|
||||
|
||||
# Start Docker Desktop
|
||||
info "🚀 Starting Docker Desktop..."
|
||||
open -a Docker
|
||||
|
||||
# Wait for Docker to start
|
||||
info "⏳ Waiting for Docker Desktop to start (this may take a few minutes)..."
|
||||
local max_wait=120
|
||||
local wait_time=0
|
||||
|
||||
while ! docker info >/dev/null 2>&1; do
|
||||
if [[ $wait_time -ge $max_wait ]]; then
|
||||
error_exit "Docker Desktop failed to start within $max_wait seconds. Please start it manually and try again."
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
((wait_time += 5))
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
echo
|
||||
success "Docker Desktop installed and started successfully"
|
||||
;;
|
||||
"ubuntu"|"debian"|"linuxmint"|"mint")
|
||||
# Check for existing Docker installation
|
||||
if command_exists docker; then
|
||||
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"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install fresh Docker using the official script
|
||||
info "📦 Installing Docker Engine..."
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# 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
|
||||
}
|
||||
Reference in New Issue
Block a user