a0e4e63d01
Reset all Path A script versions from 3.x to 1.0.0 following architectural decision to treat this as a fresh stable baseline after the Path B cleanup. Added TODO.md with prioritized audit findings and replaced the old CHANGELOG with a clean stub. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
844 lines
25 KiB
Bash
Executable File
844 lines
25 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# HOPS - Homelab Orchestration Provisioning Script
|
||
# Primary Management Script
|
||
# Version: 1.0.0
|
||
|
||
# Exit on any error
|
||
set -e
|
||
|
||
# Script version and metadata
|
||
readonly SCRIPT_VERSION="1.0.0"
|
||
readonly SCRIPT_NAME="HOPS"
|
||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# Default script locations
|
||
readonly INSTALLER_SCRIPT="$SCRIPT_DIR/install"
|
||
readonly UNINSTALLER_SCRIPT="$SCRIPT_DIR/uninstall"
|
||
readonly SERVICE_DEFINITIONS="$SCRIPT_DIR/services"
|
||
|
||
# Load system utilities
|
||
source "$SCRIPT_DIR/lib/system.sh"
|
||
|
||
# Color codes are defined in lib/common.sh
|
||
|
||
# Logging setup (will be set by setup_logging)
|
||
LOG_DIR=""
|
||
LOG_FILE=""
|
||
|
||
# Initialize logging
|
||
init_logging() {
|
||
setup_logging "hops-main"
|
||
}
|
||
|
||
# Logging function
|
||
log() {
|
||
local message="$1"
|
||
local timestamp="$(date '+%Y-%m-%d %T')"
|
||
|
||
if [[ -w "$LOG_FILE" ]]; then
|
||
echo "$timestamp - $message" >> "$LOG_FILE"
|
||
fi
|
||
|
||
echo -e "$message"
|
||
}
|
||
|
||
# Error handling
|
||
error_exit() {
|
||
log "${RED}❌ ERROR: $1${NC}"
|
||
exit 1
|
||
}
|
||
|
||
# Warning function
|
||
warning() {
|
||
log "${YELLOW}⚠️ WARNING: $1${NC}"
|
||
}
|
||
|
||
# Success function
|
||
success() {
|
||
log "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
# Info function
|
||
info() {
|
||
log "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
# Clear screen and show header
|
||
show_header() {
|
||
clear
|
||
cat << "EOF"
|
||
|
||
_ _ ____ ____ ____
|
||
| | | || _ \| _ \/ ___|
|
||
| |__| || |_) | |_) \___ \
|
||
| __ || __/| __/ ___) |
|
||
|_| |_||_| |_| |____/
|
||
|
||
EOF
|
||
echo -e "${CYAN}🚀 Homelab Orchestration Provisioning Script v${SCRIPT_VERSION}${NC}"
|
||
echo -e "${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||
}
|
||
|
||
# Check if running as root
|
||
check_root() {
|
||
if [[ $EUID -ne 0 ]]; then
|
||
error_exit "This script must be run as root or with sudo."
|
||
fi
|
||
}
|
||
|
||
# Validate script dependencies
|
||
check_dependencies() {
|
||
local missing_deps=()
|
||
|
||
# Check for required scripts
|
||
if [[ ! -f "$INSTALLER_SCRIPT" ]]; then
|
||
missing_deps+=("Installer script: $INSTALLER_SCRIPT")
|
||
fi
|
||
|
||
if [[ ! -f "$UNINSTALLER_SCRIPT" ]]; then
|
||
missing_deps+=("Uninstaller script: $UNINSTALLER_SCRIPT")
|
||
fi
|
||
|
||
if [[ ! -f "$SERVICE_DEFINITIONS" ]]; then
|
||
missing_deps+=("Service definitions: $SERVICE_DEFINITIONS")
|
||
fi
|
||
|
||
# Check for required commands
|
||
local required_commands=("curl" "wget" "systemctl")
|
||
for cmd in "${required_commands[@]}"; do
|
||
if ! command -v "$cmd" &>/dev/null; then
|
||
missing_deps+=("Command: $cmd")
|
||
fi
|
||
done
|
||
|
||
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
||
error_exit "Missing dependencies:\n$(printf ' • %s\n' "${missing_deps[@]}")"
|
||
fi
|
||
}
|
||
|
||
# Check system requirements
|
||
check_system_requirements() {
|
||
info "Checking system requirements..."
|
||
|
||
# Check OS
|
||
# OS check is handled by lib/system.sh
|
||
detect_os
|
||
if [[ "$OS_NAME_LOWER" != "macos" && ! "$OS_NAME_LOWER" =~ ^(ubuntu|debian|mint)$ ]]; then
|
||
warning "This script is designed for Ubuntu/Debian/Mint systems"
|
||
echo -e "Continue anyway? [y/N]: "
|
||
read -r continue_choice
|
||
[[ ! "$continue_choice" =~ ^[Yy]$ ]] && exit 0
|
||
fi
|
||
|
||
# Check minimum requirements
|
||
local ram_gb=$(free -g | awk '/^Mem:/{print $2}')
|
||
local disk_gb=$(df -BG --output=avail / | tail -n 1 | tr -d 'G')
|
||
|
||
if [[ $ram_gb -lt 2 ]]; then
|
||
warning "Low RAM detected: ${ram_gb}GB (2GB+ recommended)"
|
||
fi
|
||
|
||
if [[ $disk_gb -lt 10 ]]; then
|
||
warning "Low disk space: ${disk_gb}GB (10GB+ recommended)"
|
||
fi
|
||
|
||
success "System requirements check complete"
|
||
}
|
||
|
||
# Get HOPS installation status
|
||
get_installation_status() {
|
||
local status="not_installed"
|
||
local homelab_dirs=(
|
||
"$HOME/hops"
|
||
"/home/*/hops"
|
||
"/opt/hops"
|
||
"/srv/hops"
|
||
)
|
||
|
||
# Check for existing installation
|
||
for dir in "${homelab_dirs[@]}"; do
|
||
if [[ -f "$dir/docker-compose.yml" ]]; then
|
||
status="installed"
|
||
HOMELAB_DIR="$dir"
|
||
break
|
||
fi
|
||
done
|
||
|
||
# Check for running containers
|
||
if command -v docker &>/dev/null && docker ps --format "{{.Names}}" | grep -qE "(sonarr|radarr|jellyfin|plex|portainer)"; then
|
||
if [[ "$status" == "not_installed" ]]; then
|
||
status="partial"
|
||
else
|
||
status="running"
|
||
fi
|
||
fi
|
||
|
||
echo "$status"
|
||
}
|
||
|
||
# Show installation status
|
||
show_status() {
|
||
local status=$(get_installation_status)
|
||
|
||
echo -e "${WHITE}📊 Current Status:${NC}"
|
||
case "$status" in
|
||
"not_installed")
|
||
echo -e " ${RED}● Not Installed${NC}"
|
||
;;
|
||
"installed")
|
||
echo -e " ${YELLOW}● Installed (stopped)${NC}"
|
||
echo -e " ${BLUE}📂 Location: $HOMELAB_DIR${NC}"
|
||
;;
|
||
"running")
|
||
echo -e " ${GREEN}● Running${NC}"
|
||
echo -e " ${BLUE}📂 Location: $HOMELAB_DIR${NC}"
|
||
;;
|
||
"partial")
|
||
echo -e " ${YELLOW}● Partial Installation${NC}"
|
||
;;
|
||
esac
|
||
echo
|
||
}
|
||
|
||
# Run installer
|
||
run_installer() {
|
||
info "Launching HOPS installer..."
|
||
|
||
if [[ ! -f "$INSTALLER_SCRIPT" ]]; then
|
||
error_exit "Installer script not found: $INSTALLER_SCRIPT"
|
||
fi
|
||
|
||
# Source the installer function and run it
|
||
if source "$INSTALLER_SCRIPT" && install_hops; then
|
||
success "Installation completed successfully!"
|
||
else
|
||
error_exit "Installation failed. Check logs for details."
|
||
fi
|
||
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Run uninstaller
|
||
run_uninstaller() {
|
||
info "Launching HOPS uninstaller..."
|
||
|
||
if [[ ! -f "$UNINSTALLER_SCRIPT" ]]; then
|
||
error_exit "Uninstaller script not found: $UNINSTALLER_SCRIPT"
|
||
fi
|
||
|
||
# Source the uninstaller function and run it
|
||
if source "$UNINSTALLER_SCRIPT" && uninstall_hops; then
|
||
success "Uninstallation completed successfully!"
|
||
else
|
||
error_exit "Uninstallation failed. Check logs for details."
|
||
fi
|
||
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Show service status
|
||
show_service_status() {
|
||
show_header
|
||
echo -e "${WHITE}🔍 Service Status Check${NC}\n"
|
||
|
||
if ! command -v docker &>/dev/null; then
|
||
warning "Docker is not installed"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
local status=$(get_installation_status)
|
||
if [[ "$status" == "not_installed" ]]; then
|
||
warning "HOPS is not installed"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
echo -e "${BLUE}📊 Docker Service Status:${NC}"
|
||
if systemctl is-active --quiet docker; then
|
||
echo -e " ${GREEN}● Docker daemon: Running${NC}"
|
||
else
|
||
echo -e " ${RED}● Docker daemon: Stopped${NC}"
|
||
fi
|
||
|
||
echo -e "\n${BLUE}📦 Container Status:${NC}"
|
||
|
||
# Source service definitions to get port info
|
||
if [[ -f "$SERVICE_DEFINITIONS" ]]; then
|
||
source "$SERVICE_DEFINITIONS"
|
||
fi
|
||
|
||
# Known HOPS services with their ports
|
||
local services=(
|
||
"sonarr:8989" "radarr:7878" "lidarr:8686" "readarr:8787"
|
||
"bazarr:6767" "prowlarr:9696" "jellyfin:8096" "plex:32400"
|
||
"overseerr:5055" "jellyseerr:5056" "portainer:9000"
|
||
"traefik:8080" "nginx-proxy-manager:81" "qbittorrent:8082"
|
||
"transmission:9091" "nzbget:6789" "sabnzbd:8080"
|
||
"uptime-kuma:3001" "jellystat:3000"
|
||
)
|
||
|
||
local running_count=0
|
||
local total_count=0
|
||
|
||
for service_info in "${services[@]}"; do
|
||
local service_name="${service_info%:*}"
|
||
local service_port="${service_info#*:}"
|
||
|
||
if docker ps --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
||
((total_count++))
|
||
local status_symbol="${GREEN}●${NC}"
|
||
local status_text="Running"
|
||
|
||
# Check if port is accessible
|
||
if curl -sSf --max-time 2 --connect-timeout 1 "http://localhost:${service_port}" >/dev/null 2>&1; then
|
||
status_text="Running & Accessible"
|
||
((running_count++))
|
||
else
|
||
status_text="Running (starting up)"
|
||
status_symbol="${YELLOW}●${NC}"
|
||
fi
|
||
|
||
printf " %s %-20s %s (:%s)\n" "$status_symbol" "$service_name" "$status_text" "$service_port"
|
||
elif docker ps -a --format "{{.Names}}" | grep -q "^${service_name}$"; then
|
||
((total_count++))
|
||
printf " %s %-20s %s\n" "${RED}●${NC}" "$service_name" "Stopped"
|
||
fi
|
||
done
|
||
|
||
if [[ $total_count -eq 0 ]]; then
|
||
echo -e " ${YELLOW}No HOPS services found${NC}"
|
||
else
|
||
echo -e "\n${WHITE}📈 Summary: ${running_count}/${total_count} services running and accessible${NC}"
|
||
fi
|
||
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Manage services (start/stop/restart)
|
||
manage_services() {
|
||
show_header
|
||
echo -e "${WHITE}🎛️ Service Management${NC}\n"
|
||
|
||
local status=$(get_installation_status)
|
||
if [[ "$status" == "not_installed" ]]; then
|
||
warning "HOPS is not installed"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
if [[ -z "$HOMELAB_DIR" ]]; then
|
||
error_exit "Cannot locate homelab directory with docker-compose.yml"
|
||
fi
|
||
|
||
echo -e "${BLUE}Available actions:${NC}"
|
||
echo -e " 1) Start all services"
|
||
echo -e " 2) Stop all services"
|
||
echo -e " 3) Restart all services"
|
||
echo -e " 4) View logs (recent)"
|
||
echo -e " 5) View logs (follow)"
|
||
echo -e " 6) Update services"
|
||
echo -e " 7) Restart individual service"
|
||
echo -e " 8) Back to main menu"
|
||
|
||
echo -e "\n${WHITE}Select an option [1-8]: ${NC}"
|
||
read -r choice
|
||
|
||
cd "$HOMELAB_DIR"
|
||
|
||
case $choice in
|
||
1)
|
||
info "Starting all services..."
|
||
if docker compose up -d; then
|
||
success "Services started"
|
||
else
|
||
warning "Some services may have failed to start"
|
||
fi
|
||
;;
|
||
2)
|
||
info "Stopping all services..."
|
||
if docker compose down; then
|
||
success "Services stopped"
|
||
else
|
||
warning "Some services may not have stopped cleanly"
|
||
fi
|
||
;;
|
||
3)
|
||
info "Restarting all services..."
|
||
if docker compose restart; then
|
||
success "Services restarted"
|
||
else
|
||
warning "Some services may have failed to restart"
|
||
fi
|
||
;;
|
||
4)
|
||
info "Showing recent logs..."
|
||
docker compose logs --tail=100
|
||
;;
|
||
5)
|
||
info "Following logs (Ctrl+C to exit)..."
|
||
docker compose logs -f --tail=50
|
||
;;
|
||
6)
|
||
info "Updating services..."
|
||
if docker compose pull && docker compose up -d; then
|
||
success "Services updated"
|
||
else
|
||
warning "Update may have failed"
|
||
fi
|
||
;;
|
||
7)
|
||
echo -e "\n${WHITE}Available services:${NC}"
|
||
docker compose ps --services | nl -w2 -s') '
|
||
echo -e "\n${WHITE}Enter service name to restart: ${NC}"
|
||
read -r service_name
|
||
if [[ -n "$service_name" ]]; then
|
||
info "Restarting $service_name..."
|
||
if docker compose restart "$service_name"; then
|
||
success "$service_name restarted"
|
||
else
|
||
warning "Failed to restart $service_name"
|
||
fi
|
||
fi
|
||
;;
|
||
8)
|
||
return
|
||
;;
|
||
*)
|
||
warning "Invalid option"
|
||
;;
|
||
esac
|
||
|
||
echo -e "\n${WHITE}Press Enter to continue...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Show quick access URLs
|
||
show_access_info() {
|
||
show_header
|
||
echo -e "${WHITE}🌐 Service Access Information${NC}\n"
|
||
|
||
local status=$(get_installation_status)
|
||
if [[ "$status" == "not_installed" ]]; then
|
||
warning "HOPS is not installed"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
echo -e "${BLUE}📱 Access your services at:${NC}"
|
||
|
||
# Get local IP
|
||
local local_ip=$(get_primary_ip)
|
||
|
||
# Service URLs with paths
|
||
local services=(
|
||
"Sonarr:8989:/sonarr"
|
||
"Radarr:7878:/radarr"
|
||
"Lidarr:8686:/lidarr"
|
||
"Readarr:8787:/readarr"
|
||
"Bazarr:6767:"
|
||
"Prowlarr:9696:"
|
||
"Jellyfin:8096:"
|
||
"Plex:32400:/web"
|
||
"Overseerr:5055:"
|
||
"Jellyseerr:5056:"
|
||
"Portainer:9000:"
|
||
"Traefik:8080:"
|
||
"NPM:81:"
|
||
"qBittorrent:8082:"
|
||
"Transmission:9091:"
|
||
"NZBGet:6789:"
|
||
"SABnzbd:8080:"
|
||
"Uptime-Kuma:3001:"
|
||
"Jellystat:3000:"
|
||
)
|
||
|
||
local active_services=0
|
||
for service_info in "${services[@]}"; do
|
||
local service_name="${service_info%%:*}"
|
||
local service_port="${service_info#*:}"
|
||
local service_path="${service_port#*:}"
|
||
service_port="${service_port%:*}"
|
||
|
||
if docker ps --format "{{.Names}}" | grep -qi "${service_name,,}"; then
|
||
local url="http://${local_ip}:${service_port}${service_path}"
|
||
printf " ${GREEN}●${NC} %-15s %s\n" "$service_name" "$url"
|
||
((active_services++))
|
||
fi
|
||
done
|
||
|
||
if [[ $active_services -eq 0 ]]; then
|
||
echo -e " ${YELLOW}No services currently running${NC}"
|
||
fi
|
||
|
||
echo -e "\n${YELLOW}💡 Tips:${NC}"
|
||
echo -e " • Bookmark these URLs for easy access"
|
||
echo -e " • Default credentials are in the .env file"
|
||
echo -e " • Change default passwords after first login"
|
||
echo -e " • Some services may take a few minutes to fully start"
|
||
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Show logs
|
||
show_logs() {
|
||
show_header
|
||
echo -e "${WHITE}📋 HOPS Logs${NC}\n"
|
||
|
||
if [[ ! -d "$LOG_DIR" ]]; then
|
||
warning "No log directory found"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
echo -e "${BLUE}Available log files:${NC}"
|
||
local log_files=($(find "$LOG_DIR" -name "*.log" -type f | sort -r))
|
||
|
||
if [[ ${#log_files[@]} -eq 0 ]]; then
|
||
warning "No log files found"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
local count=1
|
||
for log_file in "${log_files[@]}"; do
|
||
local basename_log=$(basename "$log_file")
|
||
local size=$(du -h "$log_file" | cut -f1)
|
||
local date=$(stat -c %y "$log_file" | cut -d' ' -f1)
|
||
|
||
printf " %d) %-40s (%s, %s)\n" "$count" "$basename_log" "$size" "$date"
|
||
((count++))
|
||
done
|
||
|
||
echo -e "\n${WHITE}Select a log file to view [1-${#log_files[@]}] or 0 to go back: ${NC}"
|
||
read -r choice
|
||
|
||
if [[ "$choice" -eq 0 ]]; then
|
||
return
|
||
elif [[ "$choice" -gt 0 && "$choice" -le ${#log_files[@]} ]]; then
|
||
local selected_log="${log_files[$((choice-1))]}"
|
||
echo -e "\n${BLUE}Showing last 50 lines of $(basename "$selected_log"):${NC}\n"
|
||
tail -50 "$selected_log"
|
||
else
|
||
warning "Invalid selection"
|
||
fi
|
||
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Check for updates
|
||
check_for_updates() {
|
||
info "Checking for updates..."
|
||
|
||
# Check if we're in a git repository
|
||
if ! git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||
warning "Not in a git repository. Cannot check for updates."
|
||
return 1
|
||
fi
|
||
|
||
# Fetch latest changes
|
||
if ! git -C "$SCRIPT_DIR" fetch origin main >/dev/null 2>&1; then
|
||
warning "Failed to fetch updates from remote repository."
|
||
return 1
|
||
fi
|
||
|
||
# Check if we're behind
|
||
local local_commit=$(git -C "$SCRIPT_DIR" rev-parse HEAD)
|
||
local remote_commit=$(git -C "$SCRIPT_DIR" rev-parse origin/main)
|
||
|
||
if [[ "$local_commit" == "$remote_commit" ]]; then
|
||
success "HOPS is up to date (v$SCRIPT_VERSION)"
|
||
return 0
|
||
else
|
||
local commits_behind=$(git -C "$SCRIPT_DIR" rev-list --count HEAD..origin/main)
|
||
warning "HOPS is $commits_behind commits behind. Update available!"
|
||
|
||
# Show what's new
|
||
echo -e "\n${BLUE}📋 Recent changes:${NC}"
|
||
git -C "$SCRIPT_DIR" log --oneline --max-count=5 HEAD..origin/main | sed 's/^/ • /'
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# Update HOPS
|
||
update_hops() {
|
||
show_header
|
||
echo -e "${WHITE}🔄 HOPS Update${NC}\n"
|
||
|
||
# Check if we're in a git repository
|
||
if ! git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||
error_exit "Not in a git repository. Cannot update automatically."
|
||
fi
|
||
|
||
# Check for local changes
|
||
if ! git -C "$SCRIPT_DIR" diff-index --quiet HEAD --; then
|
||
warning "Local changes detected. These will be backed up before updating."
|
||
|
||
# Create backup
|
||
local backup_dir="$SCRIPT_DIR/.backup-$(date +%Y%m%d-%H%M%S)"
|
||
info "Creating backup at: $backup_dir"
|
||
|
||
if ! cp -r "$SCRIPT_DIR" "$backup_dir"; then
|
||
error_exit "Failed to create backup"
|
||
fi
|
||
|
||
success "Backup created successfully"
|
||
fi
|
||
|
||
# Fetch and show what will be updated
|
||
info "Fetching latest changes..."
|
||
if ! git -C "$SCRIPT_DIR" fetch origin main; then
|
||
error_exit "Failed to fetch updates from remote repository"
|
||
fi
|
||
|
||
# Check if update is needed
|
||
if ! check_for_updates; then
|
||
echo -e "\n${WHITE}Continue with update? [y/N]: ${NC}"
|
||
read -r update_choice
|
||
if [[ ! "$update_choice" =~ ^[Yy]$ ]]; then
|
||
info "Update cancelled"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
else
|
||
info "Already up to date"
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
return
|
||
fi
|
||
|
||
# Perform the update
|
||
info "Updating HOPS..."
|
||
if git -C "$SCRIPT_DIR" pull origin main; then
|
||
success "HOPS updated successfully!"
|
||
|
||
# Source the updated script to get new version
|
||
if [[ -f "$SCRIPT_DIR/hops" ]]; then
|
||
local new_version=$(grep '^readonly SCRIPT_VERSION=' "$SCRIPT_DIR/hops" | cut -d'"' -f2)
|
||
success "Updated to version $new_version"
|
||
fi
|
||
|
||
echo -e "\n${YELLOW}💡 Note: Please restart HOPS to use the updated version${NC}"
|
||
else
|
||
error_exit "Update failed. Your installation may be in an inconsistent state."
|
||
fi
|
||
|
||
echo -e "\n${WHITE}Press Enter to exit (restart HOPS to use new version)...${NC}"
|
||
read -r
|
||
exit 0
|
||
}
|
||
|
||
# Show help information
|
||
show_help() {
|
||
show_header
|
||
echo -e "${WHITE}📚 HOPS Help & Documentation${NC}\n"
|
||
|
||
echo -e "${BLUE}🎯 What is HOPS?${NC}"
|
||
echo -e "HOPS (Homelab Orchestration Provisioning Script) is an automated installer"
|
||
echo -e "for popular homelab applications including media servers, download clients,"
|
||
echo -e "and monitoring tools.\n"
|
||
|
||
echo -e "${BLUE}🚀 Quick Start:${NC}"
|
||
echo -e " 1. Run this script as root/sudo"
|
||
echo -e " 2. Choose 'Install HOPS' from the menu"
|
||
echo -e " 3. Configure directories and timezone"
|
||
echo -e " 4. Select your desired services"
|
||
echo -e " 5. Wait for installation to complete"
|
||
echo -e " 6. Access services via the provided URLs\n"
|
||
|
||
echo -e "${BLUE}📱 Supported Services:${NC}"
|
||
echo -e " • Media Management: Sonarr, Radarr, Lidarr, Readarr, Bazarr, Prowlarr"
|
||
echo -e " • Download Clients: qBittorrent, Transmission, NZBGet, SABnzbd"
|
||
echo -e " • Media Servers: Jellyfin, Plex, Emby, Jellystat"
|
||
echo -e " • Request Management: Overseerr, Jellyseerr, Ombi"
|
||
echo -e " • Reverse Proxy: Traefik, Nginx Proxy Manager"
|
||
echo -e " • Monitoring: Portainer, Uptime Kuma, Watchtower\n"
|
||
|
||
echo -e "${BLUE}🔧 Requirements:${NC}"
|
||
echo -e " • Ubuntu/Debian/Mint Linux"
|
||
echo -e " • 2GB+ RAM (4GB+ recommended)"
|
||
echo -e " • 10GB+ free disk space"
|
||
echo -e " • Root/sudo access"
|
||
echo -e " • Internet connection\n"
|
||
|
||
echo -e "${BLUE}📁 Default Locations:${NC}"
|
||
local default_homelab_path=$(get_default_homelab_path)
|
||
local default_config_path=$(get_default_config_path)
|
||
local default_media_path=$(get_default_media_path)
|
||
|
||
echo -e " • Homelab directory: $default_homelab_path/"
|
||
echo -e " • App configurations: $default_config_path/"
|
||
echo -e " • Media storage: $default_media_path/"
|
||
echo -e " • Logs: $LOG_DIR/\n"
|
||
|
||
echo -e "${BLUE}🆘 Troubleshooting:${NC}"
|
||
echo -e " • Check logs in the 'View Logs' menu"
|
||
echo -e " • Verify Docker is running: docker info"
|
||
echo -e " • Check container status: docker ps"
|
||
echo -e " • View service logs: docker logs [service-name]"
|
||
echo -e " • Restart services: docker compose restart [service-name]\n"
|
||
|
||
echo -e "${BLUE}🔐 Security Notes:${NC}"
|
||
echo -e " • Change default passwords in .env file after installation"
|
||
echo -e " • Configure firewall rules as needed"
|
||
echo -e " • Regularly update services using the management menu\n"
|
||
|
||
echo -e "${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
}
|
||
|
||
# Main menu
|
||
show_main_menu() {
|
||
local status=$(get_installation_status)
|
||
|
||
echo -e "${WHITE}🎛️ Main Menu:${NC}"
|
||
echo -e " 1) Install HOPS"
|
||
|
||
if [[ "$status" != "not_installed" ]]; then
|
||
echo -e " 2) Uninstall HOPS"
|
||
echo -e " 3) Manage Services"
|
||
echo -e " 4) Service Status"
|
||
echo -e " 5) Access Information"
|
||
else
|
||
echo -e " 2) Uninstall HOPS ${YELLOW}(not installed)${NC}"
|
||
echo -e " 3) Manage Services ${YELLOW}(not installed)${NC}"
|
||
echo -e " 4) Service Status ${YELLOW}(not installed)${NC}"
|
||
echo -e " 5) Access Information ${YELLOW}(not installed)${NC}"
|
||
fi
|
||
|
||
echo -e " 6) Check for Updates"
|
||
echo -e " 7) View Logs"
|
||
echo -e " 8) Help & Documentation"
|
||
echo -e " 9) Exit"
|
||
|
||
echo -e "\n${WHITE}Select an option [1-9]: ${NC}"
|
||
}
|
||
|
||
# Main program loop
|
||
main() {
|
||
init_logging
|
||
check_root
|
||
check_dependencies
|
||
|
||
while true; do
|
||
show_header
|
||
show_status
|
||
show_main_menu
|
||
|
||
read -r choice
|
||
|
||
case $choice in
|
||
1)
|
||
check_system_requirements
|
||
run_installer
|
||
;;
|
||
2)
|
||
run_uninstaller
|
||
;;
|
||
3)
|
||
manage_services
|
||
;;
|
||
4)
|
||
show_service_status
|
||
;;
|
||
5)
|
||
show_access_info
|
||
;;
|
||
6)
|
||
# Check for updates and optionally update
|
||
if check_for_updates; then
|
||
echo -e "\n${WHITE}Press Enter to return to main menu...${NC}"
|
||
read -r
|
||
else
|
||
echo -e "\n${WHITE}Would you like to update now? [y/N]: ${NC}"
|
||
read -r update_choice
|
||
if [[ "$update_choice" =~ ^[Yy]$ ]]; then
|
||
update_hops
|
||
fi
|
||
fi
|
||
;;
|
||
7)
|
||
show_logs
|
||
;;
|
||
8)
|
||
show_help
|
||
;;
|
||
9)
|
||
info "Thank you for using HOPS!"
|
||
exit 0
|
||
;;
|
||
*)
|
||
warning "Invalid option. Please select 1-9."
|
||
sleep 2
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
# Handle command line arguments
|
||
parse_args() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--update)
|
||
init_logging
|
||
check_root
|
||
update_hops
|
||
exit $?
|
||
;;
|
||
--check-updates)
|
||
init_logging
|
||
if check_for_updates; then
|
||
exit 0
|
||
else
|
||
exit 1
|
||
fi
|
||
;;
|
||
--version)
|
||
echo "HOPS v$SCRIPT_VERSION"
|
||
exit 0
|
||
;;
|
||
--help|-h)
|
||
echo "HOPS - Homelab Orchestration Provisioning Script v$SCRIPT_VERSION"
|
||
echo ""
|
||
echo "Usage: $0 [options]"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " --update Update HOPS to the latest version"
|
||
echo " --check-updates Check if updates are available (exit 1 if updates available)"
|
||
echo " --version Show version information"
|
||
echo " --help, -h Show this help message"
|
||
echo ""
|
||
echo "When run without options, HOPS starts in interactive mode."
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "Unknown option: $1"
|
||
echo "Use --help for usage information."
|
||
exit 1
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
}
|
||
|
||
# Script entry point
|
||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||
# Parse command line arguments first
|
||
parse_args "$@"
|
||
|
||
# If no arguments provided, run main interactive mode
|
||
main
|
||
fi |