3cba0998a7
- Remove duplicate log/error_exit/warning/success/info from hops and uninstall; remove validate_password, generate_secure_password, create_docker_networks, validate_timezone from install. Single canonical copies now live in lib/common.sh, lib/security.sh, lib/validation.sh, and lib/docker.sh (A5, Q1) - Fix lib/docker.sh, lib/validation.sh, lib/security.sh to use LIB_DIR instead of SCRIPT_DIR so sourcing them inside a function does not clobber the caller's SCRIPT_DIR - Move SCRIPT_VERSION to lib/common.sh as the single source of truth; remove local declarations from hops, install, and uninstall - uninstall now sources lib/common.sh directly for standalone safety - validate_timezone updated to warn-and-default instead of error_exit - validate_password updated to handle empty input (return 3) - Update CHANGELOG and TODO to reflect resolved items (B1-B6, A1, A3, A5, Q1) and bump version to 1.0.1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
810 lines
25 KiB
Bash
Executable File
810 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
|
|
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
echo "ERROR: HOPS requires Linux (Ubuntu 20.04+, Debian 11+, or Linux Mint 20+)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Script metadata (SCRIPT_VERSION defined in lib/common.sh)
|
|
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 shared utilities
|
|
source "$SCRIPT_DIR/lib/common.sh"
|
|
source "$SCRIPT_DIR/lib/system.sh"
|
|
|
|
# Initialize logging
|
|
init_logging() {
|
|
setup_logging "hops-main"
|
|
}
|
|
|
|
# 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"
|
|
"/opt/hops"
|
|
"/srv/hops"
|
|
)
|
|
while IFS= read -r d; do homelab_dirs+=("$d"); done < <(compgen -G "/home/*/hops" 2>/dev/null)
|
|
|
|
# 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=$((total_count + 1))
|
|
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=$((running_count + 1))
|
|
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=$((total_count + 1))
|
|
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=$((active_services + 1))
|
|
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=$((count + 1))
|
|
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/lib/common.sh" | 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 |