Rename scripts for clarity and add Huntarr service

- Renamed all scripts to descriptive names without prefixes:
  • hops.sh → hops (main script)
  • hops_installer_enhanced.sh → install
  • hops_uninstaller_fixed.sh → uninstall
  • hops_service_definitions.sh → services
  • hops_install.sh → setup
  • hops_privileged_setup.sh → privileged-setup
  • hops_user_operations.sh → user-operations
  • hops_service_definitions_improved.sh → services-improved

- Added Huntarr service support:
  • Docker image: ghcr.io/plexguide/huntarr:latest
  • Port: 9705 with /health endpoint
  • Missing media discovery and automation
  • Integrates with *arr stack services
  • Added to installer menu as option 8

- Updated all script references and documentation
- Updated service categories in README and CLAUDE.md

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Stephen Klein
2025-07-17 21:52:22 -04:00
parent 721f7d7a75
commit 5affcd2e26
17 changed files with 1077 additions and 3678 deletions
Executable
+560
View File
@@ -0,0 +1,560 @@
#!/bin/bash
uninstall_hops() {
# Clear terminal at startup
clear
# Exit on any error (but allow some failures during cleanup)
set +e
# Script version for consistency
local SCRIPT_VERSION="3.1.0"
# --------------------------------------------
# LOGGING SETUP
# --------------------------------------------
local LOG_DIR="/var/log/hops"
local LOG_FILE="$LOG_DIR/homelab-uninstall-$(date +%Y%m%d-%H%M%S).log"
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
log() {
echo -e "$(date '+%Y-%m-%d %T') - $1" | tee -a "$LOG_FILE"
}
error_exit() {
log "❌ ERROR: $1"
log "❌ Uninstallation failed. Check logs at: $LOG_FILE"
exit 1
}
warning() {
log "⚠️ WARNING: $1"
}
# --------------------------------------------
# HEADER
# --------------------------------------------
cat << "EOF"
_ _ ____ ____ ____
| | | || _ \| _ \/ ___|
| |__| || |_) | |_) \___ \
| __ || __/| __/ ___) |
|_| |_||_| |_| |____/
EOF
echo -e "🗑️ Homelab Orchestration Provisioning Script - UNINSTALLER v${SCRIPT_VERSION}\n"
log "🗑️ Starting HOPS Uninstallation v${SCRIPT_VERSION}"
# --------------------------------------------
# ROOT CHECK
# --------------------------------------------
if [[ $EUID -ne 0 ]]; then
error_exit "This script must be run as root or with sudo."
fi
# --------------------------------------------
# CONFIRMATION PROMPT
# --------------------------------------------
show_uninstall_warning() {
echo -e "⚠️ WARNING: This will completely remove your HOPS installation!"
echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "This uninstaller will:"
echo -e " • Stop and remove all Docker containers"
echo -e " • Remove Docker images (optional)"
echo -e " • Remove Docker Compose configuration"
echo -e " • Clean up application data (optional)"
echo -e " • Remove firewall rules"
echo -e " • Uninstall Docker (optional)"
echo -e ""
echo -e "⚠️ YOUR MEDIA FILES WILL NOT BE DELETED"
echo -e "⚠️ APPLICATION DATA REMOVAL IS OPTIONAL"
echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}
get_uninstall_options() {
echo -e "\n🔧 Uninstall Options:"
# Container and compose removal (always done)
REMOVE_CONTAINERS=true
REMOVE_COMPOSE=true
# Optional removals
echo -e "\n❓ Remove Docker images? (saves disk space but requires re-download) [y/N]: "
read -r remove_images
REMOVE_IMAGES=false
[[ "$remove_images" =~ ^[Yy]$ ]] && REMOVE_IMAGES=true
echo -e "\n❓ Remove application data? (⚠️ DELETES ALL CONFIGURATIONS!) [y/N]: "
read -r remove_appdata
REMOVE_APPDATA=false
[[ "$remove_appdata" =~ ^[Yy]$ ]] && REMOVE_APPDATA=true
echo -e "\n❓ Uninstall Docker completely? [y/N]: "
read -r remove_docker
REMOVE_DOCKER=false
[[ "$remove_docker" =~ ^[Yy]$ ]] && REMOVE_DOCKER=true
echo -e "\n❓ Remove firewall rules? [Y/n]: "
read -r remove_firewall
REMOVE_FIREWALL=true
[[ "$remove_firewall" =~ ^[Nn]$ ]] && REMOVE_FIREWALL=false
# Final confirmation
echo -e "\n⚠️ FINAL CONFIRMATION"
echo -e "━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "Actions to perform:"
echo -e " • Remove containers: ✅"
echo -e " • Remove compose files: ✅"
[[ $REMOVE_IMAGES == true ]] && echo -e " • Remove Docker images: ✅" || echo -e " • Remove Docker images: ❌"
[[ $REMOVE_APPDATA == true ]] && echo -e " • Remove app data: ✅" || echo -e " • Remove app data: ❌"
[[ $REMOVE_DOCKER == true ]] && echo -e " • Uninstall Docker: ✅" || echo -e " • Uninstall Docker: ❌"
[[ $REMOVE_FIREWALL == true ]] && echo -e " • Remove firewall rules: ✅" || echo -e " • Remove firewall rules: ❌"
echo -e "\n❓ Proceed with uninstallation? [y/N]: "
read -r final_confirm
if [[ ! "$final_confirm" =~ ^[Yy]$ ]]; then
log "🚫 Uninstallation cancelled by user"
exit 0
fi
}
# --------------------------------------------
# HOMELAB DIRECTORY DETECTION
# --------------------------------------------
find_homelab_directory() {
local POSSIBLE_DIRS=(
"$HOME/homelab"
"/home/*/homelab"
"/opt/homelab"
"/srv/homelab"
)
# Try to find from running user's home first
if [[ -n "$SUDO_USER" ]]; then
local user_home=$(eval echo "~$SUDO_USER")
POSSIBLE_DIRS=("$user_home/homelab" "${POSSIBLE_DIRS[@]}")
fi
HOMELAB_DIR=""
for dir in "${POSSIBLE_DIRS[@]}"; do
if [[ -f "$dir/docker-compose.yml" ]]; then
HOMELAB_DIR="$dir"
log "✅ Found homelab directory: $HOMELAB_DIR"
break
fi
done
if [[ -z "$HOMELAB_DIR" ]]; then
echo -e "\n📂 Could not auto-detect homelab directory."
echo -e "Please enter the path to your homelab directory (contains docker-compose.yml):"
read -r user_dir
if [[ -f "$user_dir/docker-compose.yml" ]]; then
HOMELAB_DIR="$user_dir"
log "✅ Using homelab directory: $HOMELAB_DIR"
else
warning "No docker-compose.yml found in $user_dir"
log "📝 Will proceed with container cleanup by name instead"
fi
fi
# Set APPDATA_DIR from env file if available
if [[ -f "$HOMELAB_DIR/.env" ]]; then
APPDATA_DIR=$(grep "^CONFIG_ROOT=" "$HOMELAB_DIR/.env" | cut -d= -f2)
log "📁 Found appdata directory: $APPDATA_DIR"
fi
}
# --------------------------------------------
# SERVICE DETECTION
# --------------------------------------------
detect_running_services() {
log "🔍 Detecting running HOPS services..."
# Known HOPS service names
local KNOWN_SERVICES=(
"sonarr" "radarr" "lidarr" "readarr" "bazarr" "prowlarr" "tdarr"
"nzbget" "sabnzbd" "transmission" "qbittorrent"
"plex" "jellyfin" "emby" "jellystat" "jellystat-db"
"overseerr" "jellyseerr" "ombi"
"traefik" "nginx-proxy-manager" "authelia"
"portainer" "watchtower" "uptime-kuma"
"postgres" "redis"
)
DETECTED_SERVICES=()
for service in "${KNOWN_SERVICES[@]}"; do
if docker ps -a --format "{{.Names}}" | grep -q "^${service}$"; then
DETECTED_SERVICES+=("$service")
fi
done
if [[ ${#DETECTED_SERVICES[@]} -gt 0 ]]; then
log "✅ Detected services: ${DETECTED_SERVICES[*]}"
else
log "⚠️ No HOPS services detected"
fi
}
# --------------------------------------------
# CONTAINER CLEANUP
# --------------------------------------------
stop_and_remove_containers() {
log "🛑 Stopping and removing containers..."
if [[ -n "$HOMELAB_DIR" && -f "$HOMELAB_DIR/docker-compose.yml" ]]; then
cd "$HOMELAB_DIR"
# Stop services gracefully
log "🔄 Stopping services with docker compose..."
if docker compose ps -q | grep -q .; then
if ! docker compose down --timeout 30 2>&1 | tee -a "$LOG_FILE"; then
warning "Docker compose down failed, attempting force removal"
docker compose down --timeout 10 --remove-orphans --volumes 2>&1 | tee -a "$LOG_FILE" || true
fi
else
log "️ No running compose services found"
fi
fi
# Fallback: Remove containers by name
if [[ ${#DETECTED_SERVICES[@]} -gt 0 ]]; then
log "🧹 Cleaning up remaining containers..."
for service in "${DETECTED_SERVICES[@]}"; do
if docker ps -a --format "{{.Names}}" | grep -q "^${service}$"; then
log "🗑️ Removing container: $service"
docker stop "$service" 2>/dev/null || true
docker rm -f "$service" 2>/dev/null || true
fi
done
fi
log "✅ Container cleanup complete"
}
# --------------------------------------------
# IMAGE CLEANUP
# --------------------------------------------
remove_docker_images() {
if [[ $REMOVE_IMAGES == true ]]; then
log "🗑️ Removing Docker images..."
# Common HOPS images
local HOPS_IMAGES=(
"lscr.io/linuxserver/sonarr"
"lscr.io/linuxserver/radarr"
"lscr.io/linuxserver/lidarr"
"lscr.io/linuxserver/readarr"
"lscr.io/linuxserver/bazarr"
"lscr.io/linuxserver/prowlarr"
"ghcr.io/haveagitgat/tdarr"
"lscr.io/linuxserver/nzbget"
"lscr.io/linuxserver/sabnzbd"
"lscr.io/linuxserver/transmission"
"lscr.io/linuxserver/qbittorrent"
"plexinc/pms-docker"
"jellyfin/jellyfin"
"emby/embyserver"
"sctx/overseerr"
"fallenbagel/jellyseerr"
"lscr.io/linuxserver/ombi"
"traefik"
"jc21/nginx-proxy-manager"
"authelia/authelia"
"portainer/portainer-ce"
"containrrr/watchtower"
"louislam/uptime-kuma"
"postgres"
"redis"
"cyfershepard/jellystat"
)
for image in "${HOPS_IMAGES[@]}"; do
if docker images --format "{{.Repository}}" | grep -q "^${image}$"; then
log "🗑️ Removing image: $image"
docker rmi -f "$image" 2>/dev/null || true
fi
done
# Clean up dangling images
log "🧹 Cleaning up dangling images..."
docker image prune -f 2>/dev/null || true
log "✅ Image cleanup complete"
else
log "⏭️ Skipping image removal"
fi
}
# --------------------------------------------
# NETWORK CLEANUP
# --------------------------------------------
cleanup_networks() {
log "🌐 Cleaning up Docker networks..."
local HOPS_NETWORKS=("homelab" "traefik" "database")
for network in "${HOPS_NETWORKS[@]}"; do
if docker network ls --format "{{.Name}}" | grep -q "^${network}$"; then
log "🗑️ Removing network: $network"
docker network rm "$network" 2>/dev/null || warning "Could not remove network: $network"
fi
done
log "✅ Network cleanup complete"
}
# --------------------------------------------
# VOLUME CLEANUP
# --------------------------------------------
cleanup_volumes() {
log "💾 Cleaning up Docker volumes..."
local HOPS_VOLUMES=("postgres_data" "redis_data")
for volume in "${HOPS_VOLUMES[@]}"; do
if docker volume ls --format "{{.Name}}" | grep -q "^${volume}$"; then
log "🗑️ Removing volume: $volume"
docker volume rm "$volume" 2>/dev/null || warning "Could not remove volume: $volume"
fi
done
# Clean up orphaned volumes
log "🧹 Cleaning up orphaned volumes..."
docker volume prune -f 2>/dev/null || true
log "✅ Volume cleanup complete"
}
# --------------------------------------------
# FILE CLEANUP
# --------------------------------------------
cleanup_compose_files() {
if [[ $REMOVE_COMPOSE == true && -n "$HOMELAB_DIR" ]]; then
log "📝 Removing Docker Compose files..."
cd "$HOMELAB_DIR"
# Backup before removal
if [[ -f docker-compose.yml ]]; then
local BACKUP_FILE="docker-compose.yml.removed.$(date +%Y%m%d%H%M%S)"
log "📦 Backing up compose file to: $BACKUP_FILE"
cp docker-compose.yml "$BACKUP_FILE" 2>/dev/null || warning "Could not backup compose file"
rm -f docker-compose.yml
fi
# Remove other compose-related files
rm -f docker-compose.override.yml .env 2>/dev/null || true
# Remove empty homelab directory if it's empty
cd ..
if [[ -d "$HOMELAB_DIR" ]]; then
rmdir "$HOMELAB_DIR" 2>/dev/null && log "📁 Removed empty homelab directory" || log "📁 Homelab directory not empty, keeping it"
fi
log "✅ Compose file cleanup complete"
else
log "⏭️ Skipping compose file removal"
fi
}
cleanup_appdata() {
if [[ $REMOVE_APPDATA == true && -n "$APPDATA_DIR" ]]; then
log "🗂️ Removing application data..."
echo -e "\n⚠️ FINAL WARNING: This will delete ALL application configurations!"
echo -e "Application data directory: $APPDATA_DIR"
echo -e "❓ Are you absolutely sure? Type 'DELETE' to confirm: "
read -r delete_confirm
if [[ "$delete_confirm" == "DELETE" ]]; then
# Create a backup first
local BACKUP_DIR="/tmp/hops-appdata-backup-$(date +%Y%m%d%H%M%S)"
log "📦 Creating backup at: $BACKUP_DIR"
if cp -r "$APPDATA_DIR" "$BACKUP_DIR" 2>/dev/null; then
log "✅ Backup created successfully"
# Remove the original
if rm -rf "$APPDATA_DIR" 2>/dev/null; then
log "✅ Application data removed"
log "📦 Backup available at: $BACKUP_DIR"
else
warning "Failed to remove application data directory"
fi
else
warning "Could not create backup, skipping appdata removal"
fi
else
log "🚫 Application data removal cancelled"
fi
else
log "⏭️ Skipping application data removal"
fi
}
# --------------------------------------------
# FIREWALL CLEANUP
# --------------------------------------------
cleanup_firewall() {
if [[ $REMOVE_FIREWALL == true ]]; then
log "🔥 Removing firewall rules..."
if command -v ufw &>/dev/null; then
# Remove HOPS-specific rules by searching for comments
local rules_to_remove=()
# Get numbered rules that contain HOPS service names
mapfile -t rules_to_remove < <(ufw status numbered | grep -E "(sonarr|radarr|lidarr|readarr|bazarr|prowlarr|jellyfin|plex|portainer|traefik|npm)" | awk '{print $1}' | tr -d '[]')
# Remove rules in reverse order to maintain numbering
if [[ ${#rules_to_remove[@]} -gt 0 ]]; then
for ((i=${#rules_to_remove[@]}-1; i>=0; i--)); do
local rule_num="${rules_to_remove[i]}"
log "🗑️ Removing firewall rule #$rule_num"
echo "y" | ufw delete "$rule_num" 2>/dev/null || true
done
fi
log "✅ Firewall cleanup complete"
else
log "️ UFW not installed, skipping firewall cleanup"
fi
else
log "⏭️ Skipping firewall cleanup"
fi
}
# --------------------------------------------
# DOCKER UNINSTALLATION
# --------------------------------------------
uninstall_docker() {
if [[ $REMOVE_DOCKER == true ]]; then
log "🐳 Uninstalling Docker..."
# Stop Docker service
systemctl stop docker 2>/dev/null || true
systemctl disable docker 2>/dev/null || true
# Remove Docker packages
local DOCKER_PACKAGES=(
"docker-ce" "docker-ce-cli" "containerd.io" "docker-buildx-plugin"
"docker-compose-plugin" "docker.io" "docker-doc" "docker-compose"
"podman-docker" "containerd" "runc"
)
for package in "${DOCKER_PACKAGES[@]}"; do
if dpkg -l | grep -q "^ii.*$package"; then
log "🗑️ Removing package: $package"
apt-get remove -y "$package" 2>/dev/null || true
fi
done
# Clean up Docker directories
log "🗑️ Removing Docker directories..."
rm -rf /var/lib/docker /etc/docker /var/run/docker.sock 2>/dev/null || true
rm -rf ~/.docker 2>/dev/null || true
# Remove from user directories too
if [[ -n "$SUDO_USER" ]]; then
local user_home=$(eval echo "~$SUDO_USER")
rm -rf "$user_home/.docker" 2>/dev/null || true
fi
# Remove Docker group
if getent group docker &>/dev/null; then
groupdel docker 2>/dev/null || warning "Could not remove docker group"
fi
# Clean up package cache
apt-get autoremove -y 2>/dev/null || true
apt-get autoclean 2>/dev/null || true
log "✅ Docker uninstallation complete"
else
log "⏭️ Skipping Docker uninstallation"
fi
}
# --------------------------------------------
# CLEANUP LOG FILES
# --------------------------------------------
cleanup_logs() {
log "📋 Cleaning up HOPS log files..."
# Keep current log file, remove others
find "$LOG_DIR" -name "homelab-*.log" -not -name "$(basename "$LOG_FILE")" -delete 2>/dev/null || true
# Remove log directory if empty (except current log)
local remaining_logs=$(find "$LOG_DIR" -name "*.log" | wc -l)
if [[ $remaining_logs -le 1 ]]; then
log "📁 Log directory will be cleaned after this session"
fi
log "✅ Log cleanup complete"
}
# --------------------------------------------
# MAIN UNINSTALLATION FLOW
# --------------------------------------------
show_uninstall_warning
get_uninstall_options
log "🚀 Starting HOPS uninstallation process..."
find_homelab_directory
detect_running_services
# Core cleanup steps
stop_and_remove_containers
remove_docker_images
cleanup_networks
cleanup_volumes
cleanup_compose_files
cleanup_appdata
cleanup_firewall
uninstall_docker
cleanup_logs
# --------------------------------------------
# FINAL SUMMARY
# --------------------------------------------
echo -e "\n✅ HOPS Uninstallation Complete!"
echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "📋 Uninstallation Summary:"
echo -e "\n🗑️ Removed Components:"
echo " • Docker containers: ✅"
echo " • Docker Compose files: ✅"
[[ $REMOVE_IMAGES == true ]] && echo " • Docker images: ✅" || echo " • Docker images: ❌ (kept)"
[[ $REMOVE_APPDATA == true ]] && echo " • Application data: ✅" || echo " • Application data: ❌ (kept)"
[[ $REMOVE_DOCKER == true ]] && echo " • Docker installation: ✅" || echo " • Docker installation: ❌ (kept)"
[[ $REMOVE_FIREWALL == true ]] && echo " • Firewall rules: ✅" || echo " • Firewall rules: ❌ (kept)"
echo -e "\n📂 Preserved:"
echo " • Media files: ✅ (never touched)"
[[ $REMOVE_APPDATA != true ]] && echo " • Application configurations: ✅"
if [[ $REMOVE_APPDATA == true ]]; then
echo -e "\n📦 Backup Location:"
echo " • Application data backup: /tmp/hops-appdata-backup-*"
echo " • Consider moving this backup to a permanent location"
fi
echo -e "\n📋 Complete log: $LOG_FILE"
echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "✅ HOPS uninstallation completed successfully!"
if [[ -n "$SUDO_USER" ]]; then
echo -e "\n💡 Note: You may want to restart your session to ensure all group changes take effect."
fi
if [[ $REMOVE_DOCKER == true ]]; then
echo -e "\n🔄 Recommendation: Reboot your system to complete Docker removal."
fi
return 0
}