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>
This commit is contained in:
@@ -1,14 +1,27 @@
|
|||||||
# HOPS - Homelab Orchestration Provisioning Script
|
# HOPS - Homelab Orchestration Provisioning Script
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
|
|
||||||
**HOPS** is a comprehensive, automated deployment solution for popular homelab applications. It simplifies the process of setting up and managing Docker-based services including media servers, download clients, monitoring tools, and more.
|
**HOPS** is a comprehensive, automated deployment solution for popular homelab applications. It simplifies the process of setting up and managing Docker-based services including media servers, download clients, monitoring tools, and more.
|
||||||
|
|
||||||
## 🆕 What's New in v3.1.0-beta
|
## 🆕 What's New in v3.2.0
|
||||||
|
|
||||||
### Major Security Enhancements
|
### Major macOS Compatibility Improvements
|
||||||
|
- **🍎 Enhanced macOS Support**: Comprehensive fixes for macOS installation and operation
|
||||||
|
- **🔐 Keychain Integration**: Proper Docker authentication with macOS keychain
|
||||||
|
- **👤 User Directory Fixes**: All directories now use actual user home instead of root
|
||||||
|
- **🚀 Docker Desktop Integration**: Improved Docker Desktop startup and management
|
||||||
|
- **⚡ Better Error Handling**: Enhanced error messages and troubleshooting for macOS
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **🔧 Fixed password generation**: Resolved `shuf` command and encoding issues on macOS
|
||||||
|
- **🐳 Fixed container creation**: Resolved Docker Compose working directory issues
|
||||||
|
- **🏥 Fixed healthchecks**: Improved Jellyseerr and other service health monitoring
|
||||||
|
- **📁 Fixed file permissions**: Proper ownership of config and media directories
|
||||||
|
|
||||||
|
### Previous in v3.1.0-beta
|
||||||
- **🔐 Encrypted Secret Management**: All passwords and sensitive data now encrypted with AES-256
|
- **🔐 Encrypted Secret Management**: All passwords and sensitive data now encrypted with AES-256
|
||||||
- **🛡️ Input Validation**: Comprehensive validation preventing injection attacks
|
- **🛡️ Input Validation**: Comprehensive validation preventing injection attacks
|
||||||
- **⚡ Privilege Separation**: Root operations separated from user operations
|
- **⚡ Privilege Separation**: Root operations separated from user operations
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
# HOPS - Homelab Orchestration Provisioning Script
|
# HOPS - Homelab Orchestration Provisioning Script
|
||||||
# Primary Management Script
|
# Primary Management Script
|
||||||
# Version: 3.1.0
|
# Version: 3.2.0
|
||||||
|
|
||||||
# Exit on any error
|
# Exit on any error
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Script version and metadata
|
# Script version and metadata
|
||||||
readonly SCRIPT_VERSION="3.1.0-beta"
|
readonly SCRIPT_VERSION="3.2.0"
|
||||||
readonly SCRIPT_NAME="HOPS"
|
readonly SCRIPT_NAME="HOPS"
|
||||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
|||||||
@@ -197,8 +197,9 @@ EOF
|
|||||||
mkdir -p "$MEDIA_DIR"/{movies,tv,music,books,downloads}
|
mkdir -p "$MEDIA_DIR"/{movies,tv,music,books,downloads}
|
||||||
mkdir -p "$APPDATA_DIR"
|
mkdir -p "$APPDATA_DIR"
|
||||||
|
|
||||||
# Set ownership if not root
|
# Set ownership to actual user (not root)
|
||||||
if [[ "$RUNNING_USER" != "root" ]]; then
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
log "📁 Setting ownership of directories to $SUDO_USER ($PUID:$PGID)"
|
||||||
chown -R "$PUID:$PGID" "$MEDIA_DIR" "$APPDATA_DIR" 2>/dev/null || true
|
chown -R "$PUID:$PGID" "$MEDIA_DIR" "$APPDATA_DIR" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -336,17 +337,17 @@ EOF
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Fallback: construct a guaranteed compliant password
|
# Fallback: construct a guaranteed compliant password
|
||||||
local upper=$(tr -dc 'A-Z' < /dev/urandom | head -c2)
|
local upper=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2)
|
||||||
local lower=$(tr -dc 'a-z' < /dev/urandom | head -c4)
|
local lower=$(generate_chars 'abcdefghijklmnopqrstuvwxyz' 4)
|
||||||
local digits=$(tr -dc '0-9' < /dev/urandom | head -c2)
|
local digits=$(generate_chars '0123456789' 2)
|
||||||
local symbols=$(tr -dc '!@#$%^&*' < /dev/urandom | head -c2)
|
local symbols=$(generate_chars '!@#$%^&*' 2)
|
||||||
local remaining_length=$((length - 10))
|
local remaining_length=$((length - 10))
|
||||||
|
|
||||||
if [[ $remaining_length -gt 0 ]]; then
|
if [[ $remaining_length -gt 0 ]]; then
|
||||||
local remaining=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c$remaining_length)
|
local remaining=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $remaining_length)
|
||||||
echo "${upper}${lower}${digits}${symbols}${remaining}" | fold -w1 | shuf | tr -d '\n'
|
shuffle_string "${upper}${lower}${digits}${symbols}${remaining}"
|
||||||
else
|
else
|
||||||
echo "${upper}${lower}${digits}${symbols}"
|
shuffle_string "${upper}${lower}${digits}${symbols}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,8 +592,22 @@ EOF
|
|||||||
# DOCKER COMPOSE FILE GENERATION
|
# DOCKER COMPOSE FILE GENERATION
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
generate_docker_compose() {
|
generate_docker_compose() {
|
||||||
local HOMELAB_DIR="$HOME/hops"
|
# Use actual user's home directory, not root's
|
||||||
|
local actual_user_home
|
||||||
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
actual_user_home=$(eval echo "~$SUDO_USER")
|
||||||
|
else
|
||||||
|
actual_user_home="$HOME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local HOMELAB_DIR="$actual_user_home/hops"
|
||||||
mkdir -p "$HOMELAB_DIR"
|
mkdir -p "$HOMELAB_DIR"
|
||||||
|
|
||||||
|
# Set ownership to actual user
|
||||||
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
chown -R "$PUID:$PGID" "$HOMELAB_DIR" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
cd "$HOMELAB_DIR"
|
cd "$HOMELAB_DIR"
|
||||||
|
|
||||||
if [[ -f docker-compose.yml ]]; then
|
if [[ -f docker-compose.yml ]]; then
|
||||||
@@ -649,6 +664,15 @@ EOF
|
|||||||
deploy_services() {
|
deploy_services() {
|
||||||
log "🚀 Starting deployment..."
|
log "🚀 Starting deployment..."
|
||||||
|
|
||||||
|
# Ensure we're in the correct directory
|
||||||
|
local HOMELAB_DIR="$HOME/hops"
|
||||||
|
if [[ ! -d "$HOMELAB_DIR" ]]; then
|
||||||
|
error_exit_with_rollback "Homelab directory not found: $HOMELAB_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$HOMELAB_DIR"
|
||||||
|
log "📁 Working in directory: $(pwd)"
|
||||||
|
|
||||||
# Set up error trap
|
# Set up error trap
|
||||||
trap 'error_exit_with_rollback "Deployment failed at step: ${BASH_COMMAND}"' ERR
|
trap 'error_exit_with_rollback "Deployment failed at step: ${BASH_COMMAND}"' ERR
|
||||||
|
|
||||||
@@ -670,11 +694,40 @@ EOF
|
|||||||
done
|
done
|
||||||
track_step "directories_created"
|
track_step "directories_created"
|
||||||
|
|
||||||
|
# Handle macOS keychain access for Docker authentication
|
||||||
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||||
|
local actual_user
|
||||||
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
actual_user="$SUDO_USER"
|
||||||
|
else
|
||||||
|
actual_user="$(whoami)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "🔐 Preparing Docker authentication for macOS..."
|
||||||
|
|
||||||
|
# Try to unlock keychain if needed
|
||||||
|
if ! sudo -u "$actual_user" security -v unlock-keychain ~/Library/Keychains/login.keychain-db 2>/dev/null; then
|
||||||
|
log "⚠️ Could not unlock keychain automatically"
|
||||||
|
log "💡 If you have private Docker images, you may need to manually unlock keychain"
|
||||||
|
log "💡 Run: security -v unlock-keychain ~/Library/Keychains/login.keychain-db"
|
||||||
|
else
|
||||||
|
log "✅ Keychain unlocked successfully"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Pull images with retry logic
|
# Pull images with retry logic
|
||||||
log "📥 Pulling container images..."
|
log "📥 Pulling container images..."
|
||||||
local PULL_RETRIES=3
|
local PULL_RETRIES=3
|
||||||
for attempt in $(seq 1 $PULL_RETRIES); do
|
for attempt in $(seq 1 $PULL_RETRIES); do
|
||||||
if docker compose pull 2>&1 | tee -a "$LOG_FILE"; then
|
local pull_cmd
|
||||||
|
if [[ "$OS_NAME_LOWER" == "macos" && -n "$SUDO_USER" ]]; then
|
||||||
|
# Run as the actual user to access keychain
|
||||||
|
pull_cmd="sudo -u $SUDO_USER docker compose pull"
|
||||||
|
else
|
||||||
|
pull_cmd="docker compose pull"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $pull_cmd 2>&1 | tee -a "$LOG_FILE"; then
|
||||||
track_step "images_pulled"
|
track_step "images_pulled"
|
||||||
break
|
break
|
||||||
elif [[ $attempt -eq $PULL_RETRIES ]]; then
|
elif [[ $attempt -eq $PULL_RETRIES ]]; then
|
||||||
@@ -687,11 +740,62 @@ EOF
|
|||||||
|
|
||||||
# Start containers
|
# Start containers
|
||||||
log "🔄 Starting containers..."
|
log "🔄 Starting containers..."
|
||||||
if docker compose up -d 2>&1 | tee -a "$LOG_FILE"; then
|
log "📄 Using docker-compose.yml in directory: $(pwd)"
|
||||||
track_step "containers_started"
|
log "🔧 Docker Compose configuration preview:"
|
||||||
|
docker compose config --quiet 2>/dev/null || log "⚠️ Could not preview configuration"
|
||||||
|
|
||||||
|
# Run docker compose up as the actual user on macOS to access keychain
|
||||||
|
local up_cmd
|
||||||
|
if [[ "$OS_NAME_LOWER" == "macos" && -n "$SUDO_USER" ]]; then
|
||||||
|
up_cmd="sudo -u $SUDO_USER docker compose up -d"
|
||||||
else
|
else
|
||||||
log "❌ Some containers failed to start. Checking status..."
|
up_cmd="docker compose up -d"
|
||||||
docker compose ps
|
fi
|
||||||
|
|
||||||
|
if $up_cmd 2>&1 | tee -a "$LOG_FILE"; then
|
||||||
|
track_step "containers_started"
|
||||||
|
log "✅ Container startup command completed successfully"
|
||||||
|
|
||||||
|
# Wait a moment for containers to initialize
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
log "🔍 Checking container status..."
|
||||||
|
|
||||||
|
# Use consistent command execution
|
||||||
|
local status_cmd logs_cmd
|
||||||
|
if [[ "$OS_NAME_LOWER" == "macos" && -n "$SUDO_USER" ]]; then
|
||||||
|
status_cmd="sudo -u $SUDO_USER docker compose ps"
|
||||||
|
logs_cmd="sudo -u $SUDO_USER docker compose logs"
|
||||||
|
else
|
||||||
|
status_cmd="docker compose ps"
|
||||||
|
logs_cmd="docker compose logs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$status_cmd --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Count running containers
|
||||||
|
local running_containers=$($status_cmd --filter "status=running" --format "{{.Names}}" | wc -l)
|
||||||
|
local total_containers=$($status_cmd --format "{{.Names}}" | wc -l)
|
||||||
|
|
||||||
|
log "📊 Container Status: $running_containers/$total_containers containers running"
|
||||||
|
|
||||||
|
if [[ $running_containers -eq 0 ]]; then
|
||||||
|
log "⚠️ No containers are running. Checking for errors..."
|
||||||
|
$logs_cmd --tail=20 | tee -a "$LOG_FILE"
|
||||||
|
warning "Containers were started but none are currently running. Check logs above."
|
||||||
|
elif [[ $running_containers -lt $total_containers ]]; then
|
||||||
|
log "⚠️ Some containers are not running. Checking logs..."
|
||||||
|
$logs_cmd --tail=20 | tee -a "$LOG_FILE"
|
||||||
|
warning "Not all containers are running. Check logs above."
|
||||||
|
else
|
||||||
|
log "✅ All containers are running successfully"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "❌ Container startup failed. Checking status..."
|
||||||
|
$status_cmd --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" | tee -a "$LOG_FILE"
|
||||||
|
log "📋 Recent container logs:"
|
||||||
|
$logs_cmd --tail=50 | tee -a "$LOG_FILE"
|
||||||
error_exit_with_rollback "Container startup failed"
|
error_exit_with_rollback "Container startup failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+40
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# HOPS - Common Utility Functions
|
# HOPS - Common Utility Functions
|
||||||
# Shared functions for logging, error handling, and UI
|
# Shared functions for logging, error handling, and UI
|
||||||
# Version: 3.1.0-beta
|
# Version: 3.2.0
|
||||||
|
|
||||||
# Prevent multiple sourcing
|
# Prevent multiple sourcing
|
||||||
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
if [[ -n "${HOPS_COMMON_LOADED:-}" ]]; then
|
||||||
@@ -101,7 +101,7 @@ show_hops_header() {
|
|||||||
local subtitle="$2"
|
local subtitle="$2"
|
||||||
|
|
||||||
if [[ -z "$version" ]]; then
|
if [[ -z "$version" ]]; then
|
||||||
version="3.1.0"
|
version="3.2.0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
clear
|
clear
|
||||||
@@ -267,3 +267,41 @@ get_available_port() {
|
|||||||
|
|
||||||
echo "$port"
|
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"
|
||||||
|
}
|
||||||
@@ -442,8 +442,6 @@ generate_docker_compose() {
|
|||||||
|
|
||||||
# Generate compose file header
|
# Generate compose file header
|
||||||
cat > "$compose_file" << EOF
|
cat > "$compose_file" << EOF
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -75,23 +75,23 @@ generate_secure_password() {
|
|||||||
# Fallback: construct guaranteed compliant password
|
# Fallback: construct guaranteed compliant password
|
||||||
debug "Using fallback password generation method"
|
debug "Using fallback password generation method"
|
||||||
|
|
||||||
local upper=$(tr -dc 'A-Z' < /dev/urandom | head -c2)
|
local upper=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2)
|
||||||
local lower=$(tr -dc 'a-z' < /dev/urandom | head -c4)
|
local lower=$(generate_chars 'abcdefghijklmnopqrstuvwxyz' 4)
|
||||||
local digits=$(tr -dc '0-9' < /dev/urandom | head -c2)
|
local digits=$(generate_chars '0123456789' 2)
|
||||||
local symbols=$(tr -dc '!@#$%^&*' < /dev/urandom | head -c2)
|
local symbols=$(generate_chars '!@#$%^&*' 2)
|
||||||
local remaining_length=$((length - 10))
|
local remaining_length=$((length - 10))
|
||||||
|
|
||||||
local password=""
|
local password=""
|
||||||
|
|
||||||
if [[ $remaining_length -gt 0 ]]; then
|
if [[ $remaining_length -gt 0 ]]; then
|
||||||
local remaining=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c$remaining_length)
|
local remaining=$(generate_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $remaining_length)
|
||||||
password="${upper}${lower}${digits}${symbols}${remaining}"
|
password="${upper}${lower}${digits}${symbols}${remaining}"
|
||||||
else
|
else
|
||||||
password="${upper}${lower}${digits}${symbols}"
|
password="${upper}${lower}${digits}${symbols}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Shuffle the password
|
# Shuffle the password
|
||||||
password=$(echo "$password" | fold -w1 | shuf | tr -d '\n')
|
password=$(shuffle_string "$password")
|
||||||
|
|
||||||
echo "$password"
|
echo "$password"
|
||||||
}
|
}
|
||||||
|
|||||||
+193
-20
@@ -379,15 +379,36 @@ run_system_checks() {
|
|||||||
# Get platform-specific default paths
|
# Get platform-specific default paths
|
||||||
get_default_media_path() {
|
get_default_media_path() {
|
||||||
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||||
echo "/Users/$USER/hops/media"
|
# Use actual user, not root when running via sudo
|
||||||
|
local actual_user
|
||||||
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
actual_user="$SUDO_USER"
|
||||||
else
|
else
|
||||||
echo "/mnt/media"
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_default_config_path() {
|
get_default_config_path() {
|
||||||
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||||
echo "/Users/$USER/hops/config"
|
# 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
|
else
|
||||||
echo "/opt/appdata"
|
echo "/opt/appdata"
|
||||||
fi
|
fi
|
||||||
@@ -395,9 +416,23 @@ get_default_config_path() {
|
|||||||
|
|
||||||
get_default_homelab_path() {
|
get_default_homelab_path() {
|
||||||
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
if [[ "$OS_NAME_LOWER" == "macos" ]]; then
|
||||||
echo "/Users/$USER/hops"
|
# Use actual user, not root when running via sudo
|
||||||
|
local actual_user
|
||||||
|
if [[ -n "$SUDO_USER" ]]; then
|
||||||
|
actual_user="$SUDO_USER"
|
||||||
else
|
else
|
||||||
echo "/home/$USER/hops"
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,35 +907,127 @@ install_docker() {
|
|||||||
actual_user="$(whoami)"
|
actual_user="$(whoami)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove conflicting compose-bridge binary if it exists
|
# Check for and handle conflicting files
|
||||||
if [[ -f "/usr/local/bin/compose-bridge" ]]; then
|
local conflicting_files=()
|
||||||
info "🗑️ Removing conflicting compose-bridge binary..."
|
local potential_conflicts=(
|
||||||
rm -f "/usr/local/bin/compose-bridge" 2>/dev/null || true
|
"/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
|
fi
|
||||||
|
|
||||||
sudo -u "$actual_user" brew install --cask docker
|
# 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
|
# Start Docker Desktop with user interaction guidance
|
||||||
info "🚀 Starting Docker Desktop..."
|
info "🚀 Starting Docker Desktop..."
|
||||||
open -a Docker
|
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
|
# Wait for Docker to start
|
||||||
info "⏳ Waiting for Docker Desktop to start (this may take a few minutes)..."
|
info "⏳ Verifying Docker Desktop is running..."
|
||||||
local max_wait=120
|
local max_wait=60
|
||||||
local wait_time=0
|
local wait_time=0
|
||||||
|
local docker_started=false
|
||||||
|
|
||||||
while ! docker info >/dev/null 2>&1; do
|
while [[ $wait_time -lt $max_wait ]]; do
|
||||||
if [[ $wait_time -ge $max_wait ]]; then
|
# Try to connect to Docker daemon
|
||||||
error_exit "Docker Desktop failed to start within $max_wait seconds. Please start it manually and try again."
|
if docker info >/dev/null 2>&1; then
|
||||||
|
docker_started=true
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep 5
|
sleep 2
|
||||||
((wait_time += 5))
|
((wait_time += 2))
|
||||||
echo -n "."
|
echo -n "."
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
success "Docker Desktop installed and started successfully"
|
|
||||||
|
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")
|
"ubuntu"|"debian"|"linuxmint"|"mint")
|
||||||
# Check for existing Docker installation
|
# Check for existing Docker installation
|
||||||
@@ -946,6 +1073,52 @@ install_docker() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 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 the official script
|
# Install fresh Docker using the official script
|
||||||
info "📦 Installing Docker Engine..."
|
info "📦 Installing Docker Engine..."
|
||||||
curl -fsSL https://get.docker.com | sh
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ get_web_healthcheck() {
|
|||||||
test: ["CMD-SHELL", "curl -f http://localhost:$port$path || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:$port$path || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 5
|
||||||
start_period: 60s
|
start_period: 90s
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,7 +612,12 @@ $(get_restart_policy)
|
|||||||
volumes:
|
volumes:
|
||||||
- \${CONFIG_ROOT}/jellyseerr:/app/config
|
- \${CONFIG_ROOT}/jellyseerr:/app/config
|
||||||
$(get_timezone_mount)
|
$(get_timezone_mount)
|
||||||
$(get_web_healthcheck 5055)
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:5055/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 120s
|
||||||
$(get_homelab_network)
|
$(get_homelab_network)
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
@@ -944,8 +949,6 @@ generate_complete_compose() {
|
|||||||
|
|
||||||
# Start with the base compose structure
|
# Start with the base compose structure
|
||||||
cat > "$compose_file" <<EOF
|
cat > "$compose_file" <<EOF
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
homelab:
|
homelab:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ generate_docker_compose() {
|
|||||||
|
|
||||||
# Generate compose file header
|
# Generate compose file header
|
||||||
cat > "$compose_file" << EOF
|
cat > "$compose_file" << EOF
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user