721f7d7a75
🆕 New Features: - Encrypted secret management with AES-256 encryption - Privilege separation (root vs user operations) - Comprehensive input validation and sanitization - Pinned container versions for security - Modular architecture with shared libraries 🔒 Security Enhancements: - Encrypted .env file storage with master key management - Input validation preventing injection attacks - Secure password generation with complexity requirements - Enhanced file permissions and ownership handling - Security auditing capabilities 🏗️ Architecture Improvements: - Shared library structure (lib/) for common functions - Enhanced error handling with detailed context - Improved service definitions with validation - Standardized logging and UI components - Better code organization and maintainability 📝 New Components: - hops_install.sh: New secure installation wrapper - hops_privileged_setup.sh: Root-only operations - hops_user_operations.sh: User operations without sudo - hops_service_definitions_improved.sh: Enhanced service generation - lib/: Shared libraries for common functionality - CLAUDE.md: Complete development documentation 🔧 User Experience: - Multiple installation methods (new secure, manual, legacy) - Better error messages and troubleshooting guidance - Improved service management commands - Enhanced documentation and help system This release maintains backward compatibility while adding enterprise-grade security features. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
318 lines
8.5 KiB
Bash
318 lines
8.5 KiB
Bash
#!/bin/bash
|
|
|
|
# HOPS - System Validation Functions
|
|
# Functions for system checks, OS detection, and requirements validation
|
|
# Version: 3.1.0
|
|
|
|
# Source common functions
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/common.sh"
|
|
|
|
# Global variables for system info
|
|
OS_NAME=""
|
|
OS_VERSION=""
|
|
OS_NAME_LOWER=""
|
|
|
|
# Detect operating system
|
|
detect_os() {
|
|
info "🔍 Detecting operating system..."
|
|
|
|
if command_exists lsb_release; then
|
|
OS_NAME=$(lsb_release -is)
|
|
OS_VERSION=$(lsb_release -rs)
|
|
elif [[ -f /etc/os-release ]]; then
|
|
OS_NAME=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
OS_VERSION=$(grep '^VERSION_ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
|
|
else
|
|
error_exit "Unable to detect operating system"
|
|
fi
|
|
|
|
OS_NAME_LOWER=$(echo "$OS_NAME" | tr '[:upper:]' '[:lower:]')
|
|
|
|
# Validate supported OS
|
|
case "$OS_NAME_LOWER" in
|
|
ubuntu|debian|linuxmint|mint)
|
|
success "Detected supported OS: $OS_NAME $OS_VERSION"
|
|
;;
|
|
*)
|
|
error_exit "Unsupported OS: $OS_NAME $OS_VERSION. Only Ubuntu/Debian/Linux Mint are supported."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Check system requirements
|
|
check_system_requirements() {
|
|
local min_ram_gb=${1:-2}
|
|
local min_disk_gb=${2:-10}
|
|
local target_dir="${3:-/}"
|
|
|
|
info "🔍 Checking system requirements..."
|
|
|
|
# Check architecture
|
|
local arch=$(uname -m)
|
|
if [[ "$arch" != "x86_64" ]]; then
|
|
error_exit "Unsupported architecture: $arch. Only x86_64 is supported."
|
|
fi
|
|
|
|
# Check RAM
|
|
local ram_gb
|
|
if command_exists free; then
|
|
ram_gb=$(free -g | awk '/^Mem:/{print $2}')
|
|
else
|
|
ram_gb=$(awk '/MemTotal/ {print int($2/1024/1024)}' /proc/meminfo)
|
|
fi
|
|
|
|
if [[ $ram_gb -lt $min_ram_gb ]]; then
|
|
error_exit "Insufficient RAM: ${ram_gb}GB detected, ${min_ram_gb}GB required"
|
|
fi
|
|
|
|
# Check disk space
|
|
local disk_avail_gb
|
|
if command_exists df; then
|
|
disk_avail_gb=$(df -BG --output=avail "$target_dir" | tail -n 1 | tr -d 'G')
|
|
else
|
|
error_exit "Unable to check disk space - 'df' command not available"
|
|
fi
|
|
|
|
if [[ $disk_avail_gb -lt $min_disk_gb ]]; then
|
|
error_exit "Insufficient disk space: ${disk_avail_gb}GB available in $target_dir, ${min_disk_gb}GB required"
|
|
fi
|
|
|
|
# Check CPU cores
|
|
local cpu_cores=$(nproc)
|
|
if [[ $cpu_cores -lt 2 ]]; then
|
|
warning "Only ${cpu_cores} CPU core(s) detected. 2+ cores recommended for optimal performance."
|
|
fi
|
|
|
|
success "System requirements met: ${ram_gb}GB RAM, ${disk_avail_gb}GB disk space, ${cpu_cores} CPU cores"
|
|
}
|
|
|
|
# Check if running in a container
|
|
check_container_environment() {
|
|
if [[ -f /.dockerenv ]] || grep -q 'container=docker' /proc/1/environ 2>/dev/null; then
|
|
warning "Running inside a container. Some features may not work correctly."
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Check internet connectivity
|
|
check_internet() {
|
|
local test_urls=(
|
|
"google.com"
|
|
"github.com"
|
|
"docker.com"
|
|
)
|
|
|
|
info "🌐 Checking internet connectivity..."
|
|
|
|
for url in "${test_urls[@]}"; do
|
|
if ping -c 1 -W 5 "$url" >/dev/null 2>&1; then
|
|
success "Internet connectivity verified"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
error_exit "No internet connectivity detected. Please check your network connection."
|
|
}
|
|
|
|
# Check Docker requirements
|
|
check_docker_requirements() {
|
|
info "🐳 Checking Docker requirements..."
|
|
|
|
# Check if Docker is installed
|
|
if ! command_exists docker; then
|
|
warning "Docker not installed. Will be installed automatically."
|
|
return 1
|
|
fi
|
|
|
|
# Check if Docker daemon is running
|
|
if ! docker info >/dev/null 2>&1; then
|
|
warning "Docker daemon not running. Will be started automatically."
|
|
return 1
|
|
fi
|
|
|
|
# Check Docker version
|
|
local docker_version=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
|
|
local min_version="20.10.0"
|
|
|
|
if ! version_compare "$docker_version" "$min_version"; then
|
|
error_exit "Docker version $docker_version is too old. Minimum required: $min_version"
|
|
fi
|
|
|
|
# Check Docker Compose
|
|
if ! docker compose version >/dev/null 2>&1; then
|
|
error_exit "Docker Compose not available. Please install Docker Compose v2+"
|
|
fi
|
|
|
|
success "Docker requirements met"
|
|
return 0
|
|
}
|
|
|
|
# Compare version strings (returns 0 if version1 >= version2)
|
|
version_compare() {
|
|
local version1="$1"
|
|
local version2="$2"
|
|
|
|
# Convert versions to arrays
|
|
local IFS='.'
|
|
local -a ver1=($version1)
|
|
local -a ver2=($version2)
|
|
|
|
# Compare each component
|
|
for i in {0..2}; do
|
|
local v1=${ver1[$i]:-0}
|
|
local v2=${ver2[$i]:-0}
|
|
|
|
if [[ $v1 -gt $v2 ]]; then
|
|
return 0
|
|
elif [[ $v1 -lt $v2 ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Check if user has sudo privileges
|
|
check_sudo() {
|
|
if [[ $EUID -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! sudo -n true 2>/dev/null; then
|
|
error_exit "This script requires sudo privileges. Please run with sudo or as root."
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Get system timezone
|
|
get_system_timezone() {
|
|
if [[ -f /etc/timezone ]]; then
|
|
cat /etc/timezone
|
|
elif [[ -L /etc/localtime ]]; then
|
|
readlink /etc/localtime | sed 's|/usr/share/zoneinfo/||'
|
|
else
|
|
timedatectl show --property=Timezone --value 2>/dev/null || echo "UTC"
|
|
fi
|
|
}
|
|
|
|
# Validate timezone
|
|
validate_timezone() {
|
|
local timezone="$1"
|
|
|
|
if [[ -z "$timezone" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -f "/usr/share/zoneinfo/$timezone" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Check available storage space for specific path
|
|
check_storage_space() {
|
|
local path="$1"
|
|
local required_gb="$2"
|
|
|
|
# Create directory if it doesn't exist
|
|
mkdir -p "$path" 2>/dev/null || true
|
|
|
|
local available_gb=$(df -BG --output=avail "$path" | tail -n 1 | tr -d 'G')
|
|
|
|
if [[ $available_gb -lt $required_gb ]]; then
|
|
error_exit "Insufficient storage space in $path: ${available_gb}GB available, ${required_gb}GB required"
|
|
fi
|
|
|
|
success "Storage space check passed: ${available_gb}GB available in $path"
|
|
}
|
|
|
|
# Check if directory is writable
|
|
check_directory_writable() {
|
|
local dir="$1"
|
|
|
|
# Try to create directory if it doesn't exist
|
|
if ! mkdir -p "$dir" 2>/dev/null; then
|
|
error_exit "Cannot create directory: $dir"
|
|
fi
|
|
|
|
# Check if writable
|
|
if ! [[ -w "$dir" ]]; then
|
|
error_exit "Directory not writable: $dir"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Get current user info (handles sudo correctly)
|
|
get_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")
|
|
else
|
|
user_info["username"]="$USER"
|
|
user_info["uid"]=$(id -u)
|
|
user_info["gid"]=$(id -g)
|
|
user_info["home"]="$HOME"
|
|
fi
|
|
|
|
# Return as key=value pairs
|
|
for key in "${!user_info[@]}"; do
|
|
echo "${key}=${user_info[$key]}"
|
|
done
|
|
}
|
|
|
|
# Check if firewall is available and configured
|
|
check_firewall() {
|
|
info "🔥 Checking firewall status..."
|
|
|
|
if command_exists ufw; then
|
|
local ufw_status=$(ufw status | head -n1 | awk '{print $2}')
|
|
|
|
case "$ufw_status" in
|
|
"active")
|
|
success "UFW firewall is active"
|
|
return 0
|
|
;;
|
|
"inactive")
|
|
warning "UFW firewall is inactive. Will be configured automatically."
|
|
return 1
|
|
;;
|
|
*)
|
|
warning "UFW firewall status unknown: $ufw_status"
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
warning "UFW not installed. Will be installed automatically."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Comprehensive system check
|
|
run_system_checks() {
|
|
local min_ram_gb=${1:-2}
|
|
local min_disk_gb=${2:-10}
|
|
local target_dir="${3:-/}"
|
|
|
|
info "🔍 Running comprehensive system checks..."
|
|
|
|
check_root
|
|
detect_os
|
|
check_system_requirements "$min_ram_gb" "$min_disk_gb" "$target_dir"
|
|
check_internet
|
|
check_docker_requirements
|
|
check_firewall
|
|
|
|
# Check for container environment (warning only)
|
|
check_container_environment
|
|
|
|
success "All system checks passed"
|
|
} |