Files
hops/fixed_primary_script.sh
T
Stephen Klein 772fb7da52 Initial release of HOPS v3.1.0
- Complete homelab orchestration and provisioning system
- Support for 20+ popular homelab services
- Interactive installation with dependency resolution
- Security hardening and firewall configuration
- Service health monitoring and management interface
- Comprehensive error handling with rollback capabilities
- Complete uninstaller with data preservation options

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 06:42:48 -04:00

679 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# HOPS - Homelab Orchestration Provisioning Script
# Primary Management Script
# Version: 3.1.0
# Exit on any error
set -e
# Script version and metadata
readonly SCRIPT_VERSION="3.1.0"
readonly SCRIPT_NAME="HOPS"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default script locations
readonly INSTALLER_SCRIPT="$SCRIPT_DIR/hops_installer_enhanced.sh"
readonly UNINSTALLER_SCRIPT="$SCRIPT_DIR/hops_uninstaller_fixed.sh"
readonly SERVICE_DEFINITIONS="$SCRIPT_DIR/hops_service_definitions.sh"
# Color codes for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly NC='\033[0m' # No Color
# Logging setup
readonly LOG_DIR="/var/log/hops"
readonly LOG_FILE="$LOG_DIR/hops-main-$(date +%Y%m%d-%H%M%S).log"
# Initialize logging
init_logging() {
if [[ $EUID -eq 0 ]]; then
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
fi
}
# 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
if ! grep -qE '^ID=(ubuntu|debian|mint)' /etc/os-release; 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/homelab"
"/home/*/homelab"
"/opt/homelab"
"/srv/homelab"
)
# 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=$(hostname -I | awk '{print $1}')
# 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
}
# 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}"
echo -e " • Homelab directory: ~/homelab/"
echo -e " • App configurations: /opt/appdata/"
echo -e " • Media storage: /mnt/media/"
echo -e " • Logs: /var/log/hops/\n"
echo -e "${BLUE}🆘 Troubleshooting:${NC}"
echo -e " • Check logs in the 'View Logs' menu"
echo -e " • Verify Docker is running: systemctl status docker"
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) View Logs"
echo -e " 7) Help & Documentation"
echo -e " 8) Exit"
echo -e "\n${WHITE}Select an option [1-8]: ${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)
show_logs
;;
7)
show_help
;;
8)
info "Thank you for using HOPS!"
exit 0
;;
*)
warning "Invalid option. Please select 1-8."
sleep 2
;;
esac
done
}
# Script entry point
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi