Release HOPS v3.1.0 with major security and architecture improvements

🆕 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>
This commit is contained in:
Stephen Klein
2025-07-17 07:01:40 -04:00
parent 06b447ddd5
commit 721f7d7a75
13 changed files with 4749 additions and 23 deletions
+251
View File
@@ -0,0 +1,251 @@
#!/bin/bash
# HOPS - Common Utility Functions
# Shared functions for logging, error handling, and UI
# Version: 3.1.0
# Color codes for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly NC='\033[0m' # No Color
# Global variables (set by setup_logging)
LOG_DIR=""
LOG_FILE=""
# Initialize logging system
setup_logging() {
local log_prefix="$1"
if [[ -z "$log_prefix" ]]; then
echo "ERROR: setup_logging requires a log prefix" >&2
return 1
fi
LOG_DIR="/var/log/hops"
LOG_FILE="$LOG_DIR/${log_prefix}-$(date +%Y%m%d-%H%M%S).log"
if [[ $EUID -eq 0 ]]; then
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
else
echo "WARNING: Not running as root, logging to console only" >&2
fi
}
# Unified logging function
log() {
local message="$1"
local timestamp="$(date '+%Y-%m-%d %T')"
# Write to log file if available
if [[ -n "$LOG_FILE" && -w "$LOG_FILE" ]]; then
echo "$timestamp - $message" >> "$LOG_FILE"
fi
# Always output to console
echo -e "$message"
}
# Error handling with exit
error_exit() {
log "${RED}❌ ERROR: $1${NC}"
if [[ -n "$LOG_FILE" ]]; then
log "${RED}❌ Operation failed. Check logs at: $LOG_FILE${NC}"
fi
exit 1
}
# Warning function
warning() {
log "${YELLOW}⚠️ WARNING: $1${NC}"
}
# Success function
success() {
log "${GREEN}$1${NC}"
}
# Info function
info() {
log "${BLUE}$1${NC}"
}
# Debug function (only shows if DEBUG=1)
debug() {
if [[ "${DEBUG:-0}" == "1" ]]; then
log "${PURPLE}🐛 DEBUG: $1${NC}"
fi
}
# Show HOPS header
show_hops_header() {
local version="$1"
local subtitle="$2"
if [[ -z "$version" ]]; then
version="3.1.0"
fi
clear
cat << "EOF"
_ _ ____ ____ ____
| | | || _ \| _ \/ ___|
| |__| || |_) | |_) \___ \
| __ || __/| __/ ___) |
|_| |_||_| |_| |____/
EOF
echo -e "${CYAN}🚀 Homelab Orchestration Provisioning Script v${version}${NC}"
if [[ -n "$subtitle" ]]; then
echo -e "${WHITE}${subtitle}${NC}"
fi
echo -e "${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
}
# Progress indicator
show_progress() {
local current="$1"
local total="$2"
local message="$3"
local width=50
local percentage=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
printf "\r${BLUE}[${NC}"
printf "%${filled}s" | tr ' ' '='
printf "%${empty}s" | tr ' ' '-'
printf "${BLUE}] %3d%% %s${NC}" "$percentage" "$message"
if [[ $current -eq $total ]]; then
echo
fi
}
# Confirmation prompt
confirm() {
local message="$1"
local default="${2:-n}"
local prompt
case "$default" in
[Yy]|[Yy][Ee][Ss]) prompt="[Y/n]" ;;
[Nn]|[Nn][Oo]) prompt="[y/N]" ;;
*) prompt="[y/n]" ;;
esac
while true; do
read -r -p "${message} ${prompt}: " response
# Use default if empty response
if [[ -z "$response" ]]; then
response="$default"
fi
case "$response" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
[Nn]|[Nn][Oo]) return 1 ;;
*) echo "Please answer yes or no." ;;
esac
done
}
# Spinner for long operations
spinner() {
local pid=$1
local message="$2"
local spin='-\|/'
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r${BLUE}%s %s${NC}" "${spin:i++%${#spin}:1}" "$message"
sleep 0.1
done
printf "\r"
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
error_exit "This script must be run as root or with sudo."
fi
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Wait for user input
pause() {
local message="${1:-Press any key to continue...}"
read -n 1 -s -r -p "$message"
echo
}
# Format bytes to human readable
format_bytes() {
local bytes=$1
local units=("B" "KB" "MB" "GB" "TB")
local unit=0
while [[ $bytes -gt 1024 && $unit -lt 4 ]]; do
bytes=$((bytes / 1024))
((unit++))
done
echo "${bytes}${units[$unit]}"
}
# Check if string is a valid IP address
is_valid_ip() {
local ip=$1
local regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
if [[ $ip =~ $regex ]]; then
local IFS='.'
local -a octets=($ip)
for octet in "${octets[@]}"; do
if [[ $octet -gt 255 ]]; then
return 1
fi
done
return 0
fi
return 1
}
# Check if port is available
is_port_available() {
local port=$1
! ss -tuln | grep -q ":$port "
}
# Get available port starting from given port
get_available_port() {
local start_port=$1
local port=$start_port
while ! is_port_available "$port"; do
((port++))
if [[ $port -gt 65535 ]]; then
error_exit "No available ports found starting from $start_port"
fi
done
echo "$port"
}