Remove Path B pipeline and dev artifacts
Deletes the incomplete, never-wired second install pipeline: setup, privileged-setup, user-operations, services-improved, lib/privileges.sh. Also removes committed dev artifacts (summary7-19.txt, discord-header.md). Path A (hops -> install -> services) is now the sole canonical pipeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
# HOPS Discord Channel Header
|
|
||||||
|
|
||||||
**HOPS - Homelab Orchestration Provisioning Script** 🏠
|
|
||||||
Cross-platform automation tool for deploying homelab infrastructure using Docker Compose. Menu-driven installation and management of media servers, download clients, monitoring tools, and more. Supports Linux, macOS, and Windows (WSL2).
|
|
||||||
🔗 **GitHub**: https://github.com/skiercm/hops
|
|
||||||
@@ -1,775 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS - Privilege Management System
|
|
||||||
# Split operations into privileged and non-privileged components
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/common.sh"
|
|
||||||
|
|
||||||
# Operations that require root privileges
|
|
||||||
PRIVILEGED_OPERATIONS=(
|
|
||||||
"install_docker"
|
|
||||||
"configure_firewall"
|
|
||||||
"create_system_directories"
|
|
||||||
"install_packages"
|
|
||||||
"configure_systemd"
|
|
||||||
"setup_secrets_directory"
|
|
||||||
"modify_system_files"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Operations that can run as regular user
|
|
||||||
NON_PRIVILEGED_OPERATIONS=(
|
|
||||||
"generate_docker_compose"
|
|
||||||
"pull_docker_images"
|
|
||||||
"start_containers"
|
|
||||||
"stop_containers"
|
|
||||||
"view_logs"
|
|
||||||
"check_service_status"
|
|
||||||
"validate_configuration"
|
|
||||||
"backup_user_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if operation requires privileges
|
|
||||||
requires_privileges() {
|
|
||||||
local operation="$1"
|
|
||||||
|
|
||||||
for priv_op in "${PRIVILEGED_OPERATIONS[@]}"; do
|
|
||||||
if [[ "$operation" == "$priv_op" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if operation can run as regular user
|
|
||||||
can_run_as_user() {
|
|
||||||
local operation="$1"
|
|
||||||
|
|
||||||
for user_op in "${NON_PRIVILEGED_OPERATIONS[@]}"; do
|
|
||||||
if [[ "$operation" == "$user_op" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get current user information
|
|
||||||
get_current_user_info() {
|
|
||||||
local -A user_info
|
|
||||||
|
|
||||||
if [[ -n "$SUDO_USER" ]]; then
|
|
||||||
user_info["username"]="$SUDO_USER"
|
|
||||||
user_info["uid"]=$(id -u "$SUDO_USER")
|
|
||||||
user_info["gid"]=$(id -g "$SUDO_USER")
|
|
||||||
user_info["home"]=$(eval echo "~$SUDO_USER")
|
|
||||||
user_info["is_sudo"]="true"
|
|
||||||
else
|
|
||||||
user_info["username"]="$USER"
|
|
||||||
user_info["uid"]=$(id -u)
|
|
||||||
user_info["gid"]=$(id -g)
|
|
||||||
user_info["home"]="$HOME"
|
|
||||||
user_info["is_sudo"]="false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Return as key=value pairs
|
|
||||||
for key in "${!user_info[@]}"; do
|
|
||||||
echo "${key}=${user_info[$key]}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Drop privileges to regular user
|
|
||||||
drop_privileges() {
|
|
||||||
local command="$1"
|
|
||||||
shift
|
|
||||||
local args=("$@")
|
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
|
||||||
debug "Already running as non-root user"
|
|
||||||
exec "$command" "${args[@]}"
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$SUDO_USER" ]]; then
|
|
||||||
error_exit "Cannot drop privileges: SUDO_USER not set"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local user_info
|
|
||||||
user_info=$(get_current_user_info)
|
|
||||||
|
|
||||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
|
||||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
|
||||||
local home=$(echo "$user_info" | grep "home=" | cut -d= -f2)
|
|
||||||
|
|
||||||
debug "Dropping privileges to user: $SUDO_USER (uid=$uid, gid=$gid)"
|
|
||||||
|
|
||||||
# Set environment variables for the user
|
|
||||||
local env_vars=(
|
|
||||||
"HOME=$home"
|
|
||||||
"USER=$SUDO_USER"
|
|
||||||
"LOGNAME=$SUDO_USER"
|
|
||||||
"PATH=/usr/local/bin:/usr/bin:/bin"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Execute command as user
|
|
||||||
sudo -u "$SUDO_USER" env "${env_vars[@]}" "$command" "${args[@]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run operation with appropriate privileges
|
|
||||||
run_with_privileges() {
|
|
||||||
local operation="$1"
|
|
||||||
local command="$2"
|
|
||||||
shift 2
|
|
||||||
local args=("$@")
|
|
||||||
|
|
||||||
if requires_privileges "$operation"; then
|
|
||||||
debug "Operation '$operation' requires root privileges"
|
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
|
||||||
error_exit "Operation '$operation' requires root privileges. Please run with sudo."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run as root
|
|
||||||
exec "$command" "${args[@]}"
|
|
||||||
elif can_run_as_user "$operation"; then
|
|
||||||
debug "Operation '$operation' can run as regular user"
|
|
||||||
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
|
||||||
# Drop privileges
|
|
||||||
drop_privileges "$command" "${args[@]}"
|
|
||||||
else
|
|
||||||
# Run as current user
|
|
||||||
exec "$command" "${args[@]}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
error_exit "Unknown operation: $operation"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create privileged setup script
|
|
||||||
create_privileged_setup() {
|
|
||||||
local setup_script="$1"
|
|
||||||
|
|
||||||
cat > "$setup_script" << 'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS Privileged Setup Script
|
|
||||||
# This script handles operations that require root privileges
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/system.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/security.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "privileged-setup"
|
|
||||||
|
|
||||||
# Check root privileges
|
|
||||||
check_root
|
|
||||||
|
|
||||||
# Install Docker if not present
|
|
||||||
install_docker() {
|
|
||||||
info "🐳 Installing Docker..."
|
|
||||||
|
|
||||||
if command_exists docker; then
|
|
||||||
success "Docker already installed"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update package index
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
# Install prerequisites
|
|
||||||
apt-get install -y \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
gnupg \
|
|
||||||
lsb-release
|
|
||||||
|
|
||||||
# Add Docker GPG key
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
|
|
||||||
# Add Docker repository with proper Ubuntu codename detection for Linux Mint
|
|
||||||
local ubuntu_codename
|
|
||||||
if [[ "$(lsb_release -is)" == "Linuxmint" ]]; then
|
|
||||||
# Linux Mint provides UBUNTU_CODENAME in /etc/os-release
|
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
ubuntu_codename=$(grep '^UBUNTU_CODENAME=' /etc/os-release | cut -d= -f2)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fallback to version mapping if UBUNTU_CODENAME not found
|
|
||||||
if [[ -z "$ubuntu_codename" ]]; then
|
|
||||||
case "$(lsb_release -rs)" in
|
|
||||||
"22"|"22.1"|"22.2"|"22.3")
|
|
||||||
ubuntu_codename="noble" # Ubuntu 24.04
|
|
||||||
;;
|
|
||||||
"21"|"21.1"|"21.2"|"21.3")
|
|
||||||
ubuntu_codename="jammy" # Ubuntu 22.04
|
|
||||||
;;
|
|
||||||
"20"|"20.1"|"20.2"|"20.3")
|
|
||||||
ubuntu_codename="focal" # Ubuntu 20.04
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
ubuntu_codename="noble" # Default to latest LTS
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ubuntu_codename=$(lsb_release -cs)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $ubuntu_codename stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
|
|
||||||
# Update package index with Docker packages
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
||||||
|
|
||||||
# Start and enable Docker service
|
|
||||||
systemctl start docker
|
|
||||||
systemctl enable docker
|
|
||||||
|
|
||||||
success "Docker installed successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure firewall
|
|
||||||
configure_firewall() {
|
|
||||||
info "🔥 Configuring firewall..."
|
|
||||||
|
|
||||||
# Install UFW if not present
|
|
||||||
if ! command_exists ufw; then
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y ufw
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Reset firewall to defaults
|
|
||||||
ufw --force reset
|
|
||||||
|
|
||||||
# Set default policies
|
|
||||||
ufw default deny incoming
|
|
||||||
ufw default allow outgoing
|
|
||||||
|
|
||||||
# Allow SSH (prevent lockout)
|
|
||||||
ufw allow ssh
|
|
||||||
|
|
||||||
# Allow HTTP and HTTPS
|
|
||||||
ufw allow 80/tcp
|
|
||||||
ufw allow 443/tcp
|
|
||||||
|
|
||||||
# Enable firewall
|
|
||||||
ufw --force enable
|
|
||||||
|
|
||||||
success "Firewall configured successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create system directories
|
|
||||||
create_system_directories() {
|
|
||||||
info "📁 Creating system directories..."
|
|
||||||
|
|
||||||
local directories=(
|
|
||||||
"/opt/appdata"
|
|
||||||
"/mnt/media"
|
|
||||||
"/mnt/media/movies"
|
|
||||||
"/mnt/media/tv"
|
|
||||||
"/mnt/media/music"
|
|
||||||
"/mnt/media/downloads"
|
|
||||||
"/var/log/hops"
|
|
||||||
)
|
|
||||||
|
|
||||||
for dir in "${directories[@]}"; do
|
|
||||||
if mkdir -p "$dir"; then
|
|
||||||
success "Created directory: $dir"
|
|
||||||
else
|
|
||||||
error_exit "Failed to create directory: $dir"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set ownership to the user who ran sudo
|
|
||||||
if [[ -n "$SUDO_USER" ]]; then
|
|
||||||
local user_info
|
|
||||||
user_info=$(get_user_info)
|
|
||||||
|
|
||||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
|
||||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
|
||||||
|
|
||||||
chown -R "$uid:$gid" /opt/appdata /mnt/media
|
|
||||||
success "Set ownership of directories to $SUDO_USER"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add user to docker group
|
|
||||||
add_user_to_docker_group() {
|
|
||||||
if [[ -z "$SUDO_USER" ]]; then
|
|
||||||
warning "No SUDO_USER set, skipping docker group addition"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "👥 Adding user to docker group..."
|
|
||||||
|
|
||||||
if usermod -aG docker "$SUDO_USER"; then
|
|
||||||
success "User $SUDO_USER added to docker group"
|
|
||||||
warning "User must log out and back in for group changes to take effect"
|
|
||||||
else
|
|
||||||
error_exit "Failed to add user to docker group"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install required packages
|
|
||||||
install_packages() {
|
|
||||||
info "📦 Installing required packages..."
|
|
||||||
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
local packages=(
|
|
||||||
"curl"
|
|
||||||
"wget"
|
|
||||||
"git"
|
|
||||||
"jq"
|
|
||||||
"htop"
|
|
||||||
"tree"
|
|
||||||
"unzip"
|
|
||||||
"gnupg"
|
|
||||||
"software-properties-common"
|
|
||||||
"apt-transport-https"
|
|
||||||
"ca-certificates"
|
|
||||||
"lsb-release"
|
|
||||||
)
|
|
||||||
|
|
||||||
for package in "${packages[@]}"; do
|
|
||||||
if apt-get install -y "$package"; then
|
|
||||||
success "Installed package: $package"
|
|
||||||
else
|
|
||||||
warning "Failed to install package: $package"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Setup secrets directory
|
|
||||||
setup_secrets_directory() {
|
|
||||||
info "🔐 Setting up secrets directory..."
|
|
||||||
|
|
||||||
local secrets_dir="/etc/hops/secrets"
|
|
||||||
|
|
||||||
if mkdir -p "$secrets_dir"; then
|
|
||||||
chmod 700 "$secrets_dir"
|
|
||||||
success "Secrets directory created: $secrets_dir"
|
|
||||||
else
|
|
||||||
error_exit "Failed to create secrets directory"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure system settings
|
|
||||||
configure_system() {
|
|
||||||
info "⚙️ Configuring system settings..."
|
|
||||||
|
|
||||||
# Set timezone if not already set
|
|
||||||
if [[ -n "$TZ" ]]; then
|
|
||||||
timedatectl set-timezone "$TZ" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Enable IP forwarding for Docker
|
|
||||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
|
||||||
sysctl -p /etc/sysctl.conf
|
|
||||||
|
|
||||||
success "System configuration completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main privileged setup
|
|
||||||
main() {
|
|
||||||
info "🚀 Starting privileged setup..."
|
|
||||||
|
|
||||||
# System checks
|
|
||||||
detect_os
|
|
||||||
check_system_requirements
|
|
||||||
|
|
||||||
# Install packages
|
|
||||||
install_packages
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
install_docker
|
|
||||||
|
|
||||||
# Configure firewall
|
|
||||||
configure_firewall
|
|
||||||
|
|
||||||
# Create directories
|
|
||||||
create_system_directories
|
|
||||||
|
|
||||||
# Add user to docker group
|
|
||||||
add_user_to_docker_group
|
|
||||||
|
|
||||||
# Setup secrets
|
|
||||||
setup_secrets_directory
|
|
||||||
|
|
||||||
# Configure system
|
|
||||||
configure_system
|
|
||||||
|
|
||||||
success "Privileged setup completed successfully"
|
|
||||||
success "Please log out and back in for group changes to take effect"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run if executed directly
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "$setup_script"
|
|
||||||
success "Privileged setup script created: $setup_script"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create non-privileged user script
|
|
||||||
create_user_script() {
|
|
||||||
local user_script="$1"
|
|
||||||
|
|
||||||
cat > "$user_script" << 'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS User Script
|
|
||||||
# This script handles operations that can run as regular user
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/docker.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/validation.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "user-operations"
|
|
||||||
|
|
||||||
# Check if user is in docker group
|
|
||||||
check_docker_access() {
|
|
||||||
if ! groups "\$USER" | grep -q docker; then
|
|
||||||
error_exit "User not in docker group. Please run the privileged setup first and log out/in."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker info >/dev/null 2>&1; then
|
|
||||||
error_exit "Cannot access Docker daemon. Please ensure Docker is running."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate Docker Compose configuration
|
|
||||||
generate_docker_compose() {
|
|
||||||
local services=("$@")
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
info "📝 Generating Docker Compose configuration..."
|
|
||||||
|
|
||||||
# Create homelab directory
|
|
||||||
mkdir -p "$HOME/hops"
|
|
||||||
|
|
||||||
# Generate compose file header
|
|
||||||
cat > "$compose_file" << EOF
|
|
||||||
services:
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Generate service definitions
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
if "$SCRIPT_DIR/services-improved" generate "$service" >> "$compose_file"; then
|
|
||||||
success "Added service: $service"
|
|
||||||
else
|
|
||||||
error_exit "Failed to generate service definition for: $service"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Add networks section
|
|
||||||
cat >> "$compose_file" << EOF
|
|
||||||
|
|
||||||
networks:
|
|
||||||
homelab:
|
|
||||||
driver: bridge
|
|
||||||
traefik:
|
|
||||||
driver: bridge
|
|
||||||
database:
|
|
||||||
driver: bridge
|
|
||||||
EOF
|
|
||||||
|
|
||||||
success "Docker Compose configuration generated: $compose_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deploy services
|
|
||||||
deploy_services() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "🚀 Deploying services..."
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
|
|
||||||
# Pull images
|
|
||||||
if docker compose pull; then
|
|
||||||
success "Docker images pulled successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to pull Docker images"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start services
|
|
||||||
if docker compose up -d; then
|
|
||||||
success "Services deployed successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to deploy services"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stop services
|
|
||||||
stop_services() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "🛑 Stopping services..."
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
|
|
||||||
if docker compose down; then
|
|
||||||
success "Services stopped successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to stop services"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show service status
|
|
||||||
show_service_status() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "📊 Service status:"
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
docker compose ps
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main user operations
|
|
||||||
main() {
|
|
||||||
local action="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
# Check Docker access
|
|
||||||
check_docker_access
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
"generate")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 generate <service1> [service2] ..."
|
|
||||||
fi
|
|
||||||
generate_docker_compose "$@"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"deploy")
|
|
||||||
deploy_services
|
|
||||||
;;
|
|
||||||
|
|
||||||
"stop")
|
|
||||||
stop_services
|
|
||||||
;;
|
|
||||||
|
|
||||||
"status")
|
|
||||||
show_service_status
|
|
||||||
;;
|
|
||||||
|
|
||||||
"logs")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 logs <service_name>"
|
|
||||||
fi
|
|
||||||
cd "$HOME/hops"
|
|
||||||
docker compose logs -f "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error_exit "Unknown action: $action"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run if executed directly
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "$user_script"
|
|
||||||
success "User script created: $user_script"
|
|
||||||
|
|
||||||
# Create installation wrapper
|
|
||||||
create_installation_wrapper() {
|
|
||||||
local wrapper_script="$1"
|
|
||||||
|
|
||||||
cat > "$wrapper_script" << 'EOF'
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS Installation Wrapper
|
|
||||||
# Orchestrates privileged and non-privileged installation steps
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "installation-wrapper"
|
|
||||||
|
|
||||||
# Show header
|
|
||||||
show_hops_header "3.1.0" "Installation Wrapper"
|
|
||||||
|
|
||||||
# Check if we're running as root
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
|
||||||
if [[ -z "$SUDO_USER" ]]; then
|
|
||||||
error_exit "Please run with sudo, not as root directly"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
error_exit "This script must be run with sudo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Phase 1: Privileged setup
|
|
||||||
info "📋 Phase 1: Privileged setup (requires root)"
|
|
||||||
if "$SCRIPT_DIR/privileged-setup"; then
|
|
||||||
success "Privileged setup completed"
|
|
||||||
else
|
|
||||||
error_exit "Privileged setup failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Phase 2: User setup
|
|
||||||
info "📋 Phase 2: User setup (running as $SUDO_USER)"
|
|
||||||
|
|
||||||
# Drop privileges and run user setup
|
|
||||||
sudo -u "$SUDO_USER" bash << 'USERSCRIPT'
|
|
||||||
cd "$HOME"
|
|
||||||
echo "Running as user: $(whoami)"
|
|
||||||
|
|
||||||
# Interactive service selection
|
|
||||||
echo "Select services to install:"
|
|
||||||
echo "1) Media Server Stack (Jellyfin, Sonarr, Radarr, Prowlarr)"
|
|
||||||
echo "2) Download Client Stack (qBittorrent, Transmission)"
|
|
||||||
echo "3) Monitoring Stack (Portainer, Uptime Kuma)"
|
|
||||||
echo "4) Custom selection"
|
|
||||||
|
|
||||||
read -p "Enter your choice (1-4): " choice
|
|
||||||
|
|
||||||
case "$choice" in
|
|
||||||
1)
|
|
||||||
services=("jellyfin" "sonarr" "radarr" "prowlarr")
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
services=("qbittorrent" "transmission")
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
services=("portainer" "uptime-kuma")
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
echo "Available services:"
|
|
||||||
"$SCRIPT_DIR/services-improved" list
|
|
||||||
read -p "Enter service names (space-separated): " -a services
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid choice"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Generate and deploy
|
|
||||||
if "$SCRIPT_DIR/user-operations" generate "${services[@]}"; then
|
|
||||||
echo "Configuration generated successfully"
|
|
||||||
|
|
||||||
if "$SCRIPT_DIR/user-operations" deploy; then
|
|
||||||
echo "Services deployed successfully"
|
|
||||||
else
|
|
||||||
echo "Deployment failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Configuration generation failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
USERSCRIPT
|
|
||||||
|
|
||||||
success "Installation completed successfully"
|
|
||||||
success "Services are now running. Check status with: ./user-operations status"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "$wrapper_script"
|
|
||||||
success "Installation wrapper created: $wrapper_script"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
main() {
|
|
||||||
local action="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
"create-setup")
|
|
||||||
create_privileged_setup "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"create-user")
|
|
||||||
create_user_script "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"create-wrapper")
|
|
||||||
create_installation_wrapper "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"create-all")
|
|
||||||
create_privileged_setup "privileged-setup"
|
|
||||||
create_user_script "user-operations"
|
|
||||||
create_installation_wrapper "setup"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"run")
|
|
||||||
local operation="$1"
|
|
||||||
local command="$2"
|
|
||||||
shift 2
|
|
||||||
run_with_privileges "$operation" "$command" "$@"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"help"|"--help"|"-h")
|
|
||||||
cat <<EOF
|
|
||||||
HOPS Privilege Management System
|
|
||||||
|
|
||||||
Usage: $0 <action> [options]
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
create-setup <file> Create privileged setup script
|
|
||||||
create-user <file> Create non-privileged user script
|
|
||||||
create-wrapper <file> Create installation wrapper
|
|
||||||
create-all Create all scripts
|
|
||||||
run <op> <cmd> [args] Run operation with appropriate privileges
|
|
||||||
help Show this help message
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
$0 create-all
|
|
||||||
$0 run install_docker /usr/bin/apt-get install docker-ce
|
|
||||||
$0 run generate_docker_compose ./compose-gen.sh
|
|
||||||
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error_exit "Unknown action: $action. Use 'help' for usage information."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# If script is run directly (not sourced)
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
setup_logging "privileges"
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS Privileged Setup Script
|
|
||||||
# This script handles operations that require root privileges
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/system.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/security.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "privileged-setup"
|
|
||||||
|
|
||||||
# Check root privileges
|
|
||||||
check_root
|
|
||||||
|
|
||||||
# Install Docker if not present
|
|
||||||
install_docker() {
|
|
||||||
info "🐳 Installing Docker..."
|
|
||||||
|
|
||||||
if command_exists docker; then
|
|
||||||
success "Docker already installed"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update package index
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
# Install prerequisites
|
|
||||||
apt-get install -y \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
gnupg \
|
|
||||||
lsb-release
|
|
||||||
|
|
||||||
# Add Docker GPG key
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
||||||
|
|
||||||
# Add Docker repository with proper Ubuntu codename detection for Linux Mint
|
|
||||||
local ubuntu_codename
|
|
||||||
if [[ "$(lsb_release -is)" == "Linuxmint" ]]; then
|
|
||||||
# Linux Mint provides UBUNTU_CODENAME in /etc/os-release
|
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
ubuntu_codename=$(grep '^UBUNTU_CODENAME=' /etc/os-release | cut -d= -f2)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fallback to version mapping if UBUNTU_CODENAME not found
|
|
||||||
if [[ -z "$ubuntu_codename" ]]; then
|
|
||||||
case "$(lsb_release -rs)" in
|
|
||||||
"22"|"22.1"|"22.2"|"22.3")
|
|
||||||
ubuntu_codename="noble" # Ubuntu 24.04
|
|
||||||
;;
|
|
||||||
"21"|"21.1"|"21.2"|"21.3")
|
|
||||||
ubuntu_codename="jammy" # Ubuntu 22.04
|
|
||||||
;;
|
|
||||||
"20"|"20.1"|"20.2"|"20.3")
|
|
||||||
ubuntu_codename="focal" # Ubuntu 20.04
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
ubuntu_codename="noble" # Default to latest LTS
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ubuntu_codename=$(lsb_release -cs)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "DEBUG: Detected OS: $(lsb_release -is), Ubuntu codename: $ubuntu_codename"
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $ubuntu_codename stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
|
|
||||||
# Update package index with Docker packages
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
||||||
|
|
||||||
# Start and enable Docker service
|
|
||||||
systemctl start docker
|
|
||||||
systemctl enable docker
|
|
||||||
|
|
||||||
success "Docker installed successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure firewall
|
|
||||||
configure_firewall() {
|
|
||||||
info "🔥 Configuring firewall..."
|
|
||||||
|
|
||||||
# Install UFW if not present
|
|
||||||
if ! command_exists ufw; then
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y ufw
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Reset firewall to defaults
|
|
||||||
ufw --force reset
|
|
||||||
|
|
||||||
# Set default policies
|
|
||||||
ufw default deny incoming
|
|
||||||
ufw default allow outgoing
|
|
||||||
|
|
||||||
# Allow SSH (prevent lockout)
|
|
||||||
ufw allow ssh
|
|
||||||
|
|
||||||
# Allow HTTP and HTTPS
|
|
||||||
ufw allow 80/tcp
|
|
||||||
ufw allow 443/tcp
|
|
||||||
|
|
||||||
# Enable firewall
|
|
||||||
ufw --force enable
|
|
||||||
|
|
||||||
success "Firewall configured successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create system directories
|
|
||||||
create_system_directories() {
|
|
||||||
info "📁 Creating system directories..."
|
|
||||||
|
|
||||||
local directories=(
|
|
||||||
"/opt/appdata"
|
|
||||||
"/mnt/media"
|
|
||||||
"/mnt/media/movies"
|
|
||||||
"/mnt/media/tv"
|
|
||||||
"/mnt/media/music"
|
|
||||||
"/mnt/media/downloads"
|
|
||||||
"/var/log/hops"
|
|
||||||
)
|
|
||||||
|
|
||||||
for dir in "${directories[@]}"; do
|
|
||||||
if mkdir -p "$dir"; then
|
|
||||||
success "Created directory: $dir"
|
|
||||||
else
|
|
||||||
error_exit "Failed to create directory: $dir"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set ownership to the user who ran sudo
|
|
||||||
if [[ -n "$SUDO_USER" ]]; then
|
|
||||||
local user_info
|
|
||||||
user_info=$(get_user_info)
|
|
||||||
|
|
||||||
local uid=$(echo "$user_info" | grep "uid=" | cut -d= -f2)
|
|
||||||
local gid=$(echo "$user_info" | grep "gid=" | cut -d= -f2)
|
|
||||||
|
|
||||||
chown -R "$uid:$gid" /opt/appdata /mnt/media
|
|
||||||
success "Set ownership of directories to $SUDO_USER"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add user to docker group
|
|
||||||
add_user_to_docker_group() {
|
|
||||||
if [[ -z "$SUDO_USER" ]]; then
|
|
||||||
warning "No SUDO_USER set, skipping docker group addition"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "👥 Adding user to docker group..."
|
|
||||||
|
|
||||||
if usermod -aG docker "$SUDO_USER"; then
|
|
||||||
success "User $SUDO_USER added to docker group"
|
|
||||||
warning "User must log out and back in for group changes to take effect"
|
|
||||||
else
|
|
||||||
error_exit "Failed to add user to docker group"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install required packages
|
|
||||||
install_packages() {
|
|
||||||
info "📦 Installing required packages..."
|
|
||||||
|
|
||||||
apt-get update
|
|
||||||
|
|
||||||
local packages=(
|
|
||||||
"curl"
|
|
||||||
"wget"
|
|
||||||
"git"
|
|
||||||
"jq"
|
|
||||||
"htop"
|
|
||||||
"tree"
|
|
||||||
"unzip"
|
|
||||||
"gnupg"
|
|
||||||
"software-properties-common"
|
|
||||||
"apt-transport-https"
|
|
||||||
"ca-certificates"
|
|
||||||
"lsb-release"
|
|
||||||
)
|
|
||||||
|
|
||||||
for package in "${packages[@]}"; do
|
|
||||||
if apt-get install -y "$package"; then
|
|
||||||
success "Installed package: $package"
|
|
||||||
else
|
|
||||||
warning "Failed to install package: $package"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Setup secrets directory
|
|
||||||
setup_secrets_directory() {
|
|
||||||
info "🔐 Setting up secrets directory..."
|
|
||||||
|
|
||||||
local secrets_dir="/etc/hops/secrets"
|
|
||||||
|
|
||||||
if mkdir -p "$secrets_dir"; then
|
|
||||||
chmod 700 "$secrets_dir"
|
|
||||||
success "Secrets directory created: $secrets_dir"
|
|
||||||
else
|
|
||||||
error_exit "Failed to create secrets directory"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configure system settings
|
|
||||||
configure_system() {
|
|
||||||
info "⚙️ Configuring system settings..."
|
|
||||||
|
|
||||||
# Set timezone if not already set
|
|
||||||
if [[ -n "$TZ" ]]; then
|
|
||||||
timedatectl set-timezone "$TZ" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Enable IP forwarding for Docker
|
|
||||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
|
||||||
sysctl -p /etc/sysctl.conf
|
|
||||||
|
|
||||||
success "System configuration completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main privileged setup
|
|
||||||
main() {
|
|
||||||
info "🚀 Starting privileged setup..."
|
|
||||||
|
|
||||||
# System checks
|
|
||||||
detect_os
|
|
||||||
check_system_requirements
|
|
||||||
|
|
||||||
# Install packages
|
|
||||||
install_packages
|
|
||||||
|
|
||||||
# Install Docker
|
|
||||||
install_docker
|
|
||||||
|
|
||||||
# Configure firewall
|
|
||||||
configure_firewall
|
|
||||||
|
|
||||||
# Create directories
|
|
||||||
create_system_directories
|
|
||||||
|
|
||||||
# Add user to docker group
|
|
||||||
add_user_to_docker_group
|
|
||||||
|
|
||||||
# Setup secrets
|
|
||||||
setup_secrets_directory
|
|
||||||
|
|
||||||
# Configure system
|
|
||||||
configure_system
|
|
||||||
|
|
||||||
success "Privileged setup completed successfully"
|
|
||||||
success "Please log out and back in for group changes to take effect"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run if executed directly
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
@@ -1,572 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS Service Definitions - Improved Version
|
|
||||||
# Contains all Docker Compose service configurations with error handling and pinned versions
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
# Exit on any error
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
else
|
|
||||||
echo "ERROR: Common library not found. Please ensure lib/common.sh exists." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "$SCRIPT_DIR/lib/security.sh" ]]; then
|
|
||||||
source "$SCRIPT_DIR/lib/security.sh"
|
|
||||||
else
|
|
||||||
echo "ERROR: Security library not found. Please ensure lib/security.sh exists." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Service definitions with pinned versions (from docker.sh)
|
|
||||||
declare -A SERVICE_IMAGES=(
|
|
||||||
# Media Management (*arr Stack)
|
|
||||||
["sonarr"]="lscr.io/linuxserver/sonarr:4.0.10"
|
|
||||||
["radarr"]="lscr.io/linuxserver/radarr:5.8.3"
|
|
||||||
["lidarr"]="lscr.io/linuxserver/lidarr:2.5.3"
|
|
||||||
["readarr"]="lscr.io/linuxserver/readarr:0.3.32-develop"
|
|
||||||
["bazarr"]="lscr.io/linuxserver/bazarr:1.4.3"
|
|
||||||
["prowlarr"]="lscr.io/linuxserver/prowlarr:1.24.3"
|
|
||||||
["tdarr"]="ghcr.io/haveagitgat/tdarr:2.26.01"
|
|
||||||
|
|
||||||
# Download Clients
|
|
||||||
["qbittorrent"]="lscr.io/linuxserver/qbittorrent:4.6.7"
|
|
||||||
["transmission"]="lscr.io/linuxserver/transmission:4.0.6"
|
|
||||||
["nzbget"]="lscr.io/linuxserver/nzbget:24.3"
|
|
||||||
["sabnzbd"]="lscr.io/linuxserver/sabnzbd:4.3.3"
|
|
||||||
|
|
||||||
# Media Servers
|
|
||||||
["jellyfin"]="lscr.io/linuxserver/jellyfin:10.9.11"
|
|
||||||
["plex"]="lscr.io/linuxserver/plex:1.40.5"
|
|
||||||
["emby"]="lscr.io/linuxserver/emby:4.8.8"
|
|
||||||
["jellystat"]="cyfershepard/jellystat:1.1.0"
|
|
||||||
|
|
||||||
# Request Management
|
|
||||||
["overseerr"]="lscr.io/linuxserver/overseerr:1.33.2"
|
|
||||||
["jellyseerr"]="fallenbagel/jellyseerr:1.9.2"
|
|
||||||
["ombi"]="lscr.io/linuxserver/ombi:4.43.5"
|
|
||||||
|
|
||||||
# Reverse Proxy & Security
|
|
||||||
["traefik"]="traefik:v3.1.6"
|
|
||||||
["nginx-proxy-manager"]="jc21/nginx-proxy-manager:2.11.3"
|
|
||||||
["authelia"]="authelia/authelia:4.38.16"
|
|
||||||
|
|
||||||
# Monitoring & Management
|
|
||||||
["portainer"]="portainer/portainer-ce:2.21.4"
|
|
||||||
["uptime-kuma"]="louislam/uptime-kuma:1.23.15"
|
|
||||||
["watchtower"]="containrrr/watchtower:1.7.1"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Service port mapping
|
|
||||||
declare -A SERVICE_PORTS=(
|
|
||||||
["sonarr"]="8989"
|
|
||||||
["radarr"]="7878"
|
|
||||||
["lidarr"]="8686"
|
|
||||||
["readarr"]="8787"
|
|
||||||
["bazarr"]="6767"
|
|
||||||
["prowlarr"]="9696"
|
|
||||||
["tdarr"]="8265"
|
|
||||||
["qbittorrent"]="8082"
|
|
||||||
["transmission"]="9091"
|
|
||||||
["nzbget"]="6789"
|
|
||||||
["sabnzbd"]="8080"
|
|
||||||
["jellyfin"]="8096"
|
|
||||||
["plex"]="32400"
|
|
||||||
["emby"]="8096"
|
|
||||||
["jellystat"]="3000"
|
|
||||||
["overseerr"]="5055"
|
|
||||||
["jellyseerr"]="5056"
|
|
||||||
["ombi"]="3579"
|
|
||||||
["traefik"]="8080"
|
|
||||||
["nginx-proxy-manager"]="81"
|
|
||||||
["authelia"]="9091"
|
|
||||||
["portainer"]="9000"
|
|
||||||
["uptime-kuma"]="3001"
|
|
||||||
["watchtower"]="8080"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "service-definitions"
|
|
||||||
|
|
||||||
# Validate service name
|
|
||||||
validate_service_name_internal() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
if [[ -z "$service_name" ]]; then
|
|
||||||
error_exit "Service name is required"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! validate_service_name "$service_name"; then
|
|
||||||
error_exit "Invalid service name: $service_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${SERVICE_IMAGES[$service_name]}" ]]; then
|
|
||||||
error_exit "Unknown service: $service_name"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get service image with validation
|
|
||||||
get_service_image() {
|
|
||||||
local service_name="$1"
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
echo "${SERVICE_IMAGES[$service_name]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get service port with validation
|
|
||||||
get_service_port() {
|
|
||||||
local service_name="$1"
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
echo "${SERVICE_PORTS[$service_name]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common environment variables for LinuxServer containers
|
|
||||||
get_linuxserver_env() {
|
|
||||||
cat <<EOF
|
|
||||||
- PUID=\${PUID:-1000}
|
|
||||||
- PGID=\${PGID:-1000}
|
|
||||||
- TZ=\${TZ:-UTC}
|
|
||||||
- UMASK=002
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common restart policy
|
|
||||||
get_restart_policy() {
|
|
||||||
echo " restart: unless-stopped"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common network configuration
|
|
||||||
get_homelab_network() {
|
|
||||||
cat <<EOF
|
|
||||||
networks:
|
|
||||||
- homelab
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common healthcheck for web services
|
|
||||||
get_web_healthcheck() {
|
|
||||||
local service_name="$1"
|
|
||||||
local path="${2:-/}"
|
|
||||||
|
|
||||||
if [[ -z "$service_name" ]]; then
|
|
||||||
error_exit "Service name required for healthcheck"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:$port$path || exit 1"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 60s
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common volume configuration
|
|
||||||
get_common_volumes() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
if [[ -z "$service_name" ]]; then
|
|
||||||
error_exit "Service name required for volumes"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
volumes:
|
|
||||||
- \${CONFIG_ROOT:-/opt/appdata}/$service_name:/config
|
|
||||||
- \${DATA_ROOT:-/mnt/media}:/data
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common Traefik labels
|
|
||||||
get_traefik_labels() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
if [[ -z "$service_name" ]]; then
|
|
||||||
error_exit "Service name required for Traefik labels"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.$service_name.rule=Host(\`$service_name.\${DOMAIN:-localhost}\`)"
|
|
||||||
- "traefik.http.routers.$service_name.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.$service_name.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.$service_name.loadbalancer.server.port=$port"
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------
|
|
||||||
# SERVICE GENERATORS
|
|
||||||
# --------------------------------------------
|
|
||||||
|
|
||||||
# Generate *arr service (template for Sonarr, Radarr, etc.)
|
|
||||||
generate_arr_service() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
$service_name:
|
|
||||||
image: $image
|
|
||||||
container_name: $service_name
|
|
||||||
$(get_restart_policy)
|
|
||||||
ports:
|
|
||||||
- "$port:$port"
|
|
||||||
environment:
|
|
||||||
$(get_linuxserver_env)
|
|
||||||
$(get_common_volumes "$service_name")
|
|
||||||
$(get_web_healthcheck "$service_name")
|
|
||||||
$(get_homelab_network)
|
|
||||||
$(get_traefik_labels "$service_name")
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate media server service
|
|
||||||
generate_media_server() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
# Special handling for Plex
|
|
||||||
local additional_config=""
|
|
||||||
if [[ "$service_name" == "plex" ]]; then
|
|
||||||
additional_config=" - PLEX_CLAIM=\${PLEX_CLAIM:-}
|
|
||||||
- ADVERTISE_IP=\${ADVERTISE_IP:-}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
$service_name:
|
|
||||||
image: $image
|
|
||||||
container_name: $service_name
|
|
||||||
$(get_restart_policy)
|
|
||||||
ports:
|
|
||||||
- "$port:$port"
|
|
||||||
environment:
|
|
||||||
$(get_linuxserver_env)
|
|
||||||
$additional_config
|
|
||||||
$(get_common_volumes "$service_name")
|
|
||||||
$(get_web_healthcheck "$service_name")
|
|
||||||
$(get_homelab_network)
|
|
||||||
$(get_traefik_labels "$service_name")
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate download client service
|
|
||||||
generate_download_client() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
# Special handling for qBittorrent
|
|
||||||
local additional_config=""
|
|
||||||
if [[ "$service_name" == "qbittorrent" ]]; then
|
|
||||||
additional_config=" - WEBUI_PORT=$port"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
$service_name:
|
|
||||||
image: $image
|
|
||||||
container_name: $service_name
|
|
||||||
$(get_restart_policy)
|
|
||||||
ports:
|
|
||||||
- "$port:$port"
|
|
||||||
environment:
|
|
||||||
$(get_linuxserver_env)
|
|
||||||
$additional_config
|
|
||||||
$(get_common_volumes "$service_name")
|
|
||||||
$(get_web_healthcheck "$service_name")
|
|
||||||
$(get_homelab_network)
|
|
||||||
$(get_traefik_labels "$service_name")
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate monitoring service
|
|
||||||
generate_monitoring_service() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
# Special handling for Portainer
|
|
||||||
local additional_config=""
|
|
||||||
local volume_config=""
|
|
||||||
|
|
||||||
if [[ "$service_name" == "portainer" ]]; then
|
|
||||||
volume_config=" volumes:
|
|
||||||
- \${CONFIG_ROOT:-/opt/appdata}/$service_name:/data
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- /etc/localtime:/etc/localtime:ro"
|
|
||||||
elif [[ "$service_name" == "watchtower" ]]; then
|
|
||||||
volume_config=" volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- /etc/localtime:/etc/localtime:ro"
|
|
||||||
additional_config=" - WATCHTOWER_CLEANUP=true
|
|
||||||
- WATCHTOWER_SCHEDULE=0 0 4 * * *"
|
|
||||||
else
|
|
||||||
volume_config=$(get_common_volumes "$service_name")
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
$service_name:
|
|
||||||
image: $image
|
|
||||||
container_name: $service_name
|
|
||||||
$(get_restart_policy)
|
|
||||||
ports:
|
|
||||||
- "$port:$port"
|
|
||||||
environment:
|
|
||||||
$(get_linuxserver_env)
|
|
||||||
$additional_config
|
|
||||||
$volume_config
|
|
||||||
$(get_web_healthcheck "$service_name")
|
|
||||||
$(get_homelab_network)
|
|
||||||
$(get_traefik_labels "$service_name")
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate Traefik service
|
|
||||||
generate_traefik() {
|
|
||||||
local service_name="traefik"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
traefik:
|
|
||||||
image: $image
|
|
||||||
container_name: traefik
|
|
||||||
$(get_restart_policy)
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
- "$port:$port"
|
|
||||||
environment:
|
|
||||||
- CF_API_EMAIL=\${CF_API_EMAIL:-}
|
|
||||||
- CF_API_KEY=\${CF_API_KEY:-}
|
|
||||||
command:
|
|
||||||
- "--api.dashboard=true"
|
|
||||||
- "--api.insecure=true"
|
|
||||||
- "--entrypoints.web.address=:80"
|
|
||||||
- "--entrypoints.websecure.address=:443"
|
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--providers.docker.exposedbydefault=false"
|
|
||||||
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
|
|
||||||
- "--certificatesresolvers.letsencrypt.acme.email=\${ACME_EMAIL:-admin@localhost}"
|
|
||||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
|
||||||
volumes:
|
|
||||||
- \${CONFIG_ROOT:-/opt/appdata}/traefik:/letsencrypt
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
networks:
|
|
||||||
- homelab
|
|
||||||
- traefik
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.traefik.rule=Host(\`traefik.\${DOMAIN:-localhost}\`)"
|
|
||||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.traefik.loadbalancer.server.port=$port"
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main service generator function
|
|
||||||
generate_service_definition() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
if [[ -z "$service_name" ]]; then
|
|
||||||
error_exit "Service name is required"
|
|
||||||
fi
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
debug "Generating service definition for: $service_name"
|
|
||||||
|
|
||||||
case "$service_name" in
|
|
||||||
# Media Management (*arr Stack)
|
|
||||||
"sonarr"|"radarr"|"lidarr"|"readarr"|"bazarr"|"prowlarr")
|
|
||||||
generate_arr_service "$service_name"
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Download Clients
|
|
||||||
"qbittorrent"|"transmission"|"nzbget"|"sabnzbd")
|
|
||||||
generate_download_client "$service_name"
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Media Servers
|
|
||||||
"jellyfin"|"plex"|"emby"|"jellystat")
|
|
||||||
generate_media_server "$service_name"
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Request Management
|
|
||||||
"overseerr"|"jellyseerr"|"ombi")
|
|
||||||
generate_arr_service "$service_name" # They use similar patterns
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Reverse Proxy & Security
|
|
||||||
"traefik")
|
|
||||||
generate_traefik
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Monitoring & Management
|
|
||||||
"portainer"|"uptime-kuma"|"watchtower")
|
|
||||||
generate_monitoring_service "$service_name"
|
|
||||||
;;
|
|
||||||
|
|
||||||
# Other services
|
|
||||||
"nginx-proxy-manager"|"authelia"|"tdarr")
|
|
||||||
generate_arr_service "$service_name" # Use generic template
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error_exit "Unknown service: $service_name"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate multiple services
|
|
||||||
generate_multiple_services() {
|
|
||||||
local services=("$@")
|
|
||||||
|
|
||||||
if [[ ${#services[@]} -eq 0 ]]; then
|
|
||||||
error_exit "No services specified"
|
|
||||||
fi
|
|
||||||
|
|
||||||
debug "Generating definitions for ${#services[@]} services"
|
|
||||||
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
generate_service_definition "$service"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# List all available services
|
|
||||||
list_available_services() {
|
|
||||||
echo "Available services:"
|
|
||||||
echo
|
|
||||||
|
|
||||||
local categories=(
|
|
||||||
"Media Management:sonarr,radarr,lidarr,readarr,bazarr,prowlarr,tdarr"
|
|
||||||
"Download Clients:qbittorrent,transmission,nzbget,sabnzbd"
|
|
||||||
"Media Servers:jellyfin,plex,emby,jellystat"
|
|
||||||
"Request Management:overseerr,jellyseerr,ombi"
|
|
||||||
"Reverse Proxy & Security:traefik,nginx-proxy-manager,authelia"
|
|
||||||
"Monitoring & Management:portainer,uptime-kuma,watchtower"
|
|
||||||
)
|
|
||||||
|
|
||||||
for category in "${categories[@]}"; do
|
|
||||||
local category_name="${category%%:*}"
|
|
||||||
local services="${category#*:}"
|
|
||||||
|
|
||||||
echo -e "${CYAN}${category_name}:${NC}"
|
|
||||||
IFS=',' read -ra service_list <<< "$services"
|
|
||||||
|
|
||||||
for service in "${service_list[@]}"; do
|
|
||||||
local port=$(get_service_port "$service")
|
|
||||||
local image=$(get_service_image "$service")
|
|
||||||
printf " %-20s Port: %-6s Image: %s\n" "$service" "$port" "$image"
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate service configuration
|
|
||||||
validate_service_config() {
|
|
||||||
local service_name="$1"
|
|
||||||
|
|
||||||
validate_service_name_internal "$service_name"
|
|
||||||
|
|
||||||
local image=$(get_service_image "$service_name")
|
|
||||||
local port=$(get_service_port "$service_name")
|
|
||||||
|
|
||||||
# Check if image exists (basic validation)
|
|
||||||
if [[ -z "$image" ]]; then
|
|
||||||
error_exit "No image defined for service: $service_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if port is valid
|
|
||||||
if [[ ! "$port" =~ ^[0-9]+$ ]] || [[ "$port" -lt 1 ]] || [[ "$port" -gt 65535 ]]; then
|
|
||||||
error_exit "Invalid port for service $service_name: $port"
|
|
||||||
fi
|
|
||||||
|
|
||||||
success "Service configuration valid: $service_name"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main function for command line usage
|
|
||||||
main() {
|
|
||||||
local action="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
"generate")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 generate <service_name> [service_name...]"
|
|
||||||
fi
|
|
||||||
generate_multiple_services "$@"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"list")
|
|
||||||
list_available_services
|
|
||||||
;;
|
|
||||||
|
|
||||||
"validate")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 validate <service_name>"
|
|
||||||
fi
|
|
||||||
validate_service_config "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"help"|"--help"|"-h")
|
|
||||||
cat <<EOF
|
|
||||||
HOPS Service Definitions - Improved Version
|
|
||||||
|
|
||||||
Usage: $0 <action> [options]
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
generate <service>... Generate service definitions
|
|
||||||
list List all available services
|
|
||||||
validate <service> Validate service configuration
|
|
||||||
help Show this help message
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
$0 generate sonarr radarr jellyfin
|
|
||||||
$0 list
|
|
||||||
$0 validate traefik
|
|
||||||
|
|
||||||
EOF
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error_exit "Unknown action: $action. Use 'help' for usage information."
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# If script is run directly (not sourced)
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS Installation Wrapper
|
|
||||||
# Orchestrates privileged and non-privileged installation steps
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "installation-wrapper"
|
|
||||||
|
|
||||||
# Show header
|
|
||||||
show_hops_header "3.1.0-beta" "Installation Wrapper"
|
|
||||||
|
|
||||||
# Check if we're running as root
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
|
||||||
if [[ -z "$SUDO_USER" ]]; then
|
|
||||||
error_exit "Please run with sudo, not as root directly"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
error_exit "This script must be run with sudo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Phase 1: Privileged setup
|
|
||||||
info "📋 Phase 1: Privileged setup (requires root)"
|
|
||||||
if "$SCRIPT_DIR/privileged-setup"; then
|
|
||||||
success "Privileged setup completed"
|
|
||||||
else
|
|
||||||
error_exit "Privileged setup failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Phase 2: User setup
|
|
||||||
info "📋 Phase 2: User setup (running as $SUDO_USER)"
|
|
||||||
|
|
||||||
# Drop privileges and run user setup
|
|
||||||
sudo -u "$SUDO_USER" bash << 'USERSCRIPT'
|
|
||||||
cd "$HOME"
|
|
||||||
echo "Running as user: $(whoami)"
|
|
||||||
|
|
||||||
# Interactive service selection
|
|
||||||
echo "Select services to install:"
|
|
||||||
echo "1) Media Server Stack (Jellyfin, Sonarr, Radarr, Prowlarr)"
|
|
||||||
echo "2) Download Client Stack (qBittorrent, Transmission)"
|
|
||||||
echo "3) Monitoring Stack (Portainer, Uptime Kuma)"
|
|
||||||
echo "4) Custom selection"
|
|
||||||
|
|
||||||
read -p "Enter your choice (1-4): " choice
|
|
||||||
|
|
||||||
case "$choice" in
|
|
||||||
1)
|
|
||||||
services=("jellyfin" "sonarr" "radarr" "prowlarr")
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
services=("qbittorrent" "transmission")
|
|
||||||
;;
|
|
||||||
3)
|
|
||||||
services=("portainer" "uptime-kuma")
|
|
||||||
;;
|
|
||||||
4)
|
|
||||||
echo "Available services:"
|
|
||||||
"$SCRIPT_DIR/services-improved" list
|
|
||||||
read -p "Enter service names (space-separated): " -a services
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid choice"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Generate and deploy
|
|
||||||
if "$SCRIPT_DIR/user-operations" generate "${services[@]}"; then
|
|
||||||
echo "Configuration generated successfully"
|
|
||||||
|
|
||||||
if "$SCRIPT_DIR/user-operations" deploy; then
|
|
||||||
echo "Services deployed successfully"
|
|
||||||
else
|
|
||||||
echo "Deployment failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Configuration generation failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
USERSCRIPT
|
|
||||||
|
|
||||||
success "Installation completed successfully"
|
|
||||||
success "Services are now running. Check status with: ./user-operations status"
|
|
||||||
-170
@@ -1,170 +0,0 @@
|
|||||||
# HOPS Linux Mint Docker Repository Troubleshooting Summary
|
|
||||||
## Date: July 19, 2025
|
|
||||||
|
|
||||||
### Problem Description
|
|
||||||
HOPS installation failing on Linux Mint 22.1 with Docker repository error:
|
|
||||||
```
|
|
||||||
E: The repository 'https://download.docker.com/linux/ubuntu xia Release' does not have a Release file.
|
|
||||||
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Root Cause Analysis
|
|
||||||
- Linux Mint uses its own codenames (e.g., "xia" for version 22.1)
|
|
||||||
- Docker repositories are structured around Ubuntu codenames (e.g., "noble", "jammy", "focal")
|
|
||||||
- HOPS was using `lsb_release -cs` which returns "xia" instead of the Ubuntu base codename
|
|
||||||
- Docker doesn't have a repository for Linux Mint's "xia" codename
|
|
||||||
|
|
||||||
### System Information (Linux Mint 22.1)
|
|
||||||
```
|
|
||||||
Distributor ID: LinuxMint
|
|
||||||
Description: Linux Mint 22.1
|
|
||||||
Release: 22.1
|
|
||||||
Codename: xia
|
|
||||||
UBUNTU_CODENAME=noble (from /etc/os-release)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Troubleshooting Steps Attempted
|
|
||||||
|
|
||||||
#### 1. Initial Fix Attempt (Commit af57a77)
|
|
||||||
**Files Modified:** `privileged-setup`, `lib/privileges.sh`
|
|
||||||
**Approach:** Added Linux Mint version to Ubuntu codename mapping
|
|
||||||
- Mint 22.x → Ubuntu 24.04 (noble)
|
|
||||||
- Mint 21.x → Ubuntu 22.04 (jammy)
|
|
||||||
- Mint 20.x → Ubuntu 20.04 (focal)
|
|
||||||
|
|
||||||
**Result:** Still failed with same error
|
|
||||||
|
|
||||||
#### 2. System Cleanup
|
|
||||||
**Commands Executed:**
|
|
||||||
```bash
|
|
||||||
sudo rm -f /etc/apt/sources.list.d/docker*
|
|
||||||
sudo rm -f /etc/apt/sources.list.d/*docker*
|
|
||||||
sudo rm -f /usr/share/keyrings/docker*
|
|
||||||
sudo rm -f /etc/apt/keyrings/docker*
|
|
||||||
sudo grep -i docker /etc/apt/sources.list
|
|
||||||
sudo apt clean
|
|
||||||
sudo apt autoclean
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:** Confirmed clean state, but error persisted
|
|
||||||
|
|
||||||
#### 3. Improved Fix (Commit 4fd78ec)
|
|
||||||
**Files Modified:** `privileged-setup`, `lib/privileges.sh`
|
|
||||||
**Approach:** Use `UBUNTU_CODENAME` from `/etc/os-release` with fallback to version mapping
|
|
||||||
```bash
|
|
||||||
# Primary method: Read UBUNTU_CODENAME from /etc/os-release
|
|
||||||
ubuntu_codename=$(grep '^UBUNTU_CODENAME=' /etc/os-release | cut -d= -f2)
|
|
||||||
|
|
||||||
# Fallback: Version mapping if UBUNTU_CODENAME not found
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:** Still experiencing same error after system cleanup
|
|
||||||
|
|
||||||
#### 4. Discovery of UBUNTU_CODENAME
|
|
||||||
**Key Finding:** Linux Mint 22.1 provides `UBUNTU_CODENAME=noble` in `/etc/os-release`
|
|
||||||
- This is the correct Ubuntu codename that Docker repositories support
|
|
||||||
- Should eliminate need for manual version mapping
|
|
||||||
|
|
||||||
#### 5. Root Cause Discovery - Wrong Installation Path (Session 2)
|
|
||||||
**Date:** July 19, 2025 (Evening)
|
|
||||||
**Discovery:** User was running `./setup` script, but fixes were applied to different code paths
|
|
||||||
**Investigation Steps:**
|
|
||||||
- Confirmed user had latest code with fixes (commit 4fd78ec)
|
|
||||||
- System was completely clean of Docker repositories
|
|
||||||
- `UBUNTU_CODENAME=noble` correctly detected
|
|
||||||
- Still getting "xia" error despite fixes
|
|
||||||
|
|
||||||
**Key Finding:** The `setup` script uses `lib/system.sh::install_docker()` which was calling Docker's convenience script `curl -fsSL https://get.docker.com | sh`, NOT the fixed installation functions in `privileged-setup` or `lib/privileges.sh`.
|
|
||||||
|
|
||||||
#### 6. Fix Applied to Correct Installation Path (Commit ce0f7f2)
|
|
||||||
**Files Modified:** `lib/system.sh` (lines 1122-1168)
|
|
||||||
**Approach:**
|
|
||||||
- Replaced Docker convenience script with manual repository setup
|
|
||||||
- Added Linux Mint Ubuntu codename detection logic to `lib/system.sh`
|
|
||||||
- Included same UBUNTU_CODENAME detection and fallback mapping
|
|
||||||
- Added debug output: "Using Ubuntu codename: X for Docker repository"
|
|
||||||
|
|
||||||
**Result:** User tested after pulling latest code - still experiencing same "xia" error
|
|
||||||
|
|
||||||
### Current Status (End of Session 2)
|
|
||||||
**Problem State:** Persistent "xia" repository error despite comprehensive fixes
|
|
||||||
**Fixes Applied:**
|
|
||||||
- Three different installation paths updated with Linux Mint detection
|
|
||||||
- Complete Docker repository cleanup performed multiple times
|
|
||||||
- Debug output added to track codename detection
|
|
||||||
- Manual testing confirmed UBUNTU_CODENAME=noble is available
|
|
||||||
|
|
||||||
**Unresolved Questions:**
|
|
||||||
1. Why debug output from fixed code is not appearing in installation logs
|
|
||||||
2. Whether there's a fourth Docker installation path not yet discovered
|
|
||||||
3. Possible system-level caching or existing Docker installation interfering
|
|
||||||
4. Whether the correct script path is actually being executed
|
|
||||||
|
|
||||||
### Next Steps (For Tomorrow)
|
|
||||||
1. **Execution Path Verification:** Add debug traces to determine which exact functions are being called during `./setup`
|
|
||||||
2. **Docker Installation Check:** Verify if Docker is already installed and causing early function returns
|
|
||||||
3. **Complete Docker Removal:** If Docker exists, completely remove it before testing fixes
|
|
||||||
4. **Alternative Installation Methods:** Test other entry points (`./hops`, `./install`, `./privileged-setup` directly)
|
|
||||||
5. **System State Analysis:** Check for any persistent apt configurations or cached repository information
|
|
||||||
|
|
||||||
### Technical Notes
|
|
||||||
- Linux Mint consistently provides `UBUNTU_CODENAME` in modern versions
|
|
||||||
- Using this field is more reliable than version-based mapping
|
|
||||||
- Docker installation uses Ubuntu repositories for Debian-based distributions
|
|
||||||
- Issue affects all Linux Mint installations using HOPS
|
|
||||||
- The Docker convenience script `get.docker.com` has its own broken Linux Mint detection
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `privileged-setup` (lines 43-70, 72) - ✅ Fixed
|
|
||||||
- `lib/privileges.sh` (lines 199-226, 228) - ✅ Fixed
|
|
||||||
- `lib/system.sh` (lines 1122-1168) - ✅ Fixed
|
|
||||||
|
|
||||||
#### 7. Final Resolution (Session 3 - July 20, 2025)
|
|
||||||
**Root Cause Identified:** Critical case sensitivity bug in Linux Mint detection
|
|
||||||
- `lsb_release -is` returns `"Linuxmint"` (lowercase 'm')
|
|
||||||
- All code was checking for `"LinuxMint"` (uppercase 'M')
|
|
||||||
- This caused Linux Mint detection to fail completely, falling back to "xia" codename
|
|
||||||
|
|
||||||
**Final Fixes Applied:**
|
|
||||||
1. **Case Sensitivity Fix (Commit 736ed1b):**
|
|
||||||
- Fixed `lib/system.sh:1151`: `"LinuxMint"` → `"Linuxmint"`
|
|
||||||
- Fixed `privileged-setup:45`: `"LinuxMint"` → `"Linuxmint"`
|
|
||||||
- Fixed `lib/privileges.sh:201`: `"LinuxMint"` → `"Linuxmint"`
|
|
||||||
|
|
||||||
2. **Debug Tracing Added (Commit d2e9a69):**
|
|
||||||
- Added comprehensive debug output to trace execution paths
|
|
||||||
- Fixed Docker repository cleanup order in `remove_docker_linux()`
|
|
||||||
- Added specific cleanup for Linux Mint codenames (xia, vera, vanessa)
|
|
||||||
|
|
||||||
3. **Docker Service Issues (Manual Fix):**
|
|
||||||
- Created missing `docker` group: `sudo groupadd docker`
|
|
||||||
- Added user to docker group: `sudo usermod -aG docker skier`
|
|
||||||
- Started Docker services: `sudo systemctl start docker.socket docker`
|
|
||||||
|
|
||||||
4. **Directory Detection Fix (Commit a28a6e5):**
|
|
||||||
- Fixed sudo home directory resolution in `install` script
|
|
||||||
- Changed `$HOME/hops` to use `$SUDO_USER` home directory
|
|
||||||
- Resolved `/root/hops` vs `/home/skier/hops` issue
|
|
||||||
|
|
||||||
**Final Result:** ✅ **COMPLETE SUCCESS**
|
|
||||||
- Docker repositories now use correct Ubuntu codename "noble"
|
|
||||||
- Sonarr container deployed and running successfully
|
|
||||||
- Web UI accessible at localhost:8989
|
|
||||||
- All Linux Mint Docker repository issues resolved
|
|
||||||
|
|
||||||
### Current Status (RESOLVED)
|
|
||||||
**Problem State:** ✅ **COMPLETELY RESOLVED**
|
|
||||||
**Final Working State:**
|
|
||||||
- Linux Mint detection working: `DEBUG: Linux Mint detected, checking for UBUNTU_CODENAME`
|
|
||||||
- Ubuntu codename detection: `DEBUG: Found UBUNTU_CODENAME=noble in /etc/os-release`
|
|
||||||
- Repository configuration: `ℹ️ Using Ubuntu codename: noble for Docker repository`
|
|
||||||
- Docker installation: Downloads from `https://download.docker.com/linux/ubuntu noble`
|
|
||||||
- Service deployment: Sonarr running and accessible
|
|
||||||
|
|
||||||
### Git Commits
|
|
||||||
- `af57a77`: Initial Linux Mint version mapping fix
|
|
||||||
- `4fd78ec`: Improved fix using UBUNTU_CODENAME detection
|
|
||||||
- `ce0f7f2`: Fix lib/system.sh Docker installation path with manual repository setup
|
|
||||||
- `d2e9a69`: Fix Docker repository issues with debug tracing and cleanup order
|
|
||||||
- `736ed1b`: Fix critical Linux Mint case sensitivity bug in repository detection
|
|
||||||
- `a28a6e5`: Fix homelab directory detection when running with sudo
|
|
||||||
-173
@@ -1,173 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HOPS User Script
|
|
||||||
# This script handles operations that can run as regular user
|
|
||||||
# Version: 3.1.0-beta
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Source common functions
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/lib/common.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/docker.sh"
|
|
||||||
source "$SCRIPT_DIR/lib/validation.sh"
|
|
||||||
|
|
||||||
# Initialize logging
|
|
||||||
setup_logging "user-operations"
|
|
||||||
|
|
||||||
# Check if user is in docker group
|
|
||||||
check_docker_access() {
|
|
||||||
if ! groups "$USER" | grep -q docker; then
|
|
||||||
error_exit "User not in docker group. Please run the privileged setup first and log out/in."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker info >/dev/null 2>&1; then
|
|
||||||
error_exit "Cannot access Docker daemon. Please ensure Docker is running."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Generate Docker Compose configuration
|
|
||||||
generate_docker_compose() {
|
|
||||||
local services=("$@")
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
info "📝 Generating Docker Compose configuration..."
|
|
||||||
|
|
||||||
# Create homelab directory
|
|
||||||
mkdir -p "$HOME/hops"
|
|
||||||
|
|
||||||
# Generate compose file header
|
|
||||||
cat > "$compose_file" << EOF
|
|
||||||
services:
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Generate service definitions
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
if "$SCRIPT_DIR/services-improved" generate "$service" >> "$compose_file"; then
|
|
||||||
success "Added service: $service"
|
|
||||||
else
|
|
||||||
error_exit "Failed to generate service definition for: $service"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Add networks section
|
|
||||||
cat >> "$compose_file" << EOF
|
|
||||||
|
|
||||||
networks:
|
|
||||||
homelab:
|
|
||||||
driver: bridge
|
|
||||||
traefik:
|
|
||||||
driver: bridge
|
|
||||||
database:
|
|
||||||
driver: bridge
|
|
||||||
EOF
|
|
||||||
|
|
||||||
success "Docker Compose configuration generated: $compose_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deploy services
|
|
||||||
deploy_services() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "🚀 Deploying services..."
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
|
|
||||||
# Pull images
|
|
||||||
if docker compose pull; then
|
|
||||||
success "Docker images pulled successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to pull Docker images"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start services
|
|
||||||
if docker compose up -d; then
|
|
||||||
success "Services deployed successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to deploy services"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Stop services
|
|
||||||
stop_services() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "🛑 Stopping services..."
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
|
|
||||||
if docker compose down; then
|
|
||||||
success "Services stopped successfully"
|
|
||||||
else
|
|
||||||
error_exit "Failed to stop services"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show service status
|
|
||||||
show_service_status() {
|
|
||||||
local compose_file="$HOME/hops/docker-compose.yml"
|
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
|
||||||
error_exit "Docker Compose file not found: $compose_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "📊 Service status:"
|
|
||||||
|
|
||||||
cd "$HOME/hops"
|
|
||||||
docker compose ps
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main user operations
|
|
||||||
main() {
|
|
||||||
local action="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
# Check Docker access
|
|
||||||
check_docker_access
|
|
||||||
|
|
||||||
case "$action" in
|
|
||||||
"generate")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 generate <service1> [service2] ..."
|
|
||||||
fi
|
|
||||||
generate_docker_compose "$@"
|
|
||||||
;;
|
|
||||||
|
|
||||||
"deploy")
|
|
||||||
deploy_services
|
|
||||||
;;
|
|
||||||
|
|
||||||
"stop")
|
|
||||||
stop_services
|
|
||||||
;;
|
|
||||||
|
|
||||||
"status")
|
|
||||||
show_service_status
|
|
||||||
;;
|
|
||||||
|
|
||||||
"logs")
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
error_exit "Usage: $0 logs <service_name>"
|
|
||||||
fi
|
|
||||||
cd "$HOME/hops"
|
|
||||||
docker compose logs -f "$1"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
error_exit "Unknown action: $action"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run if executed directly
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user