#!/bin/bash # autoSMART Node Installation Script # Version: 1.0 # Description: Install autoSMART on target nodes (Linux systems only) # Note: This script is called by deploy.sh and should run on target nodes set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" INSTALL_DIR="/opt/autoSMART" CONFIG_DIR="/etc/autosmart" SERVICE_NAME="autosmart" SYSTEMD_SERVICE="/etc/systemd/system/${SERVICE_NAME}.service" # Default configuration (can be overridden by command line) DB_HOST="${DB_HOST:-192.168.2.102}" DB_USER="${DB_USER:-autosmart}" DB_PASS="${DB_PASS:-autoSMART2025!}" DB_NAME="${DB_NAME:-autosmart}" # Node configuration NODE_ID="${NODE_ID:-$(hostname -s)}" SCAN_INTERVAL="${SCAN_INTERVAL:-300}" FULL_SCAN_INTERVAL="${FULL_SCAN_INTERVAL:-3600}" # Operation modes UNINSTALL=false FORCE_REINSTALL=false CONFIG_ONLY=false # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } show_usage() { echo "autoSMART Node Installation Script v1.0" echo "========================================" echo "" echo "Usage: $0 [COMMAND] [OPTIONS]" echo "" echo "Commands:" echo " install Install autoSMART on current node (default)" echo " uninstall Remove autoSMART completely from current node" echo "" echo "Options:" echo " --help Show this help message" echo " --force-reinstall Clean installation (removes previous version)" echo " --config-only Only create/update configuration files" echo " --db-host HOST Database host (default: 192.168.2.102)" echo " --db-user USER Database user (default: autosmart)" echo " --db-pass PASS Database password (default: autoSMART2025!)" echo " --db-name NAME Database name (default: autosmart)" echo " --node-id ID Node identifier (default: hostname)" echo " --scan-interval SEC Scan interval in seconds (default: 300)" echo "" echo "Note: This script should be called by deploy.sh, not run directly." echo "For deployment from development machine, use: deploy.sh install " echo "" } parse_arguments() { COMMAND="install" # Default command while [[ $# -gt 0 ]]; do case $1 in install|uninstall) COMMAND="$1" shift ;; --help) show_usage exit 0 ;; --force-reinstall) FORCE_REINSTALL=true shift ;; --config-only) CONFIG_ONLY=true shift ;; --db-host) DB_HOST="$2" shift 2 ;; --db-user) DB_USER="$2" shift 2 ;; --db-pass) DB_PASS="$2" shift 2 ;; --db-name) DB_NAME="$2" shift 2 ;; --node-id) NODE_ID="$2" shift 2 ;; --scan-interval) SCAN_INTERVAL="$2" shift 2 ;; *) log_error "Unknown option: $1" show_usage exit 1 ;; esac done } show_header() { log_info "🔧 autoSMART Node Installation v1.0" log_info "===================================" log_info "Installing on target node: $(hostname)" log_info "" log_info "Operation: $COMMAND" log_info "Node ID: $NODE_ID" log_info "Database: $DB_HOST:5432/$DB_NAME" if [[ "$COMMAND" == "install" ]]; then log_info "Install Directory: $INSTALL_DIR" log_info "Config Directory: $CONFIG_DIR" fi log_info "" } check_requirements() { log_info "🔍 Checking system requirements..." # Check if running as root if [[ $EUID -ne 0 ]]; then log_error "This script must be run as root (use sudo)" exit 1 fi # Check if running on Linux if [[ "$(uname)" != "Linux" ]]; then log_error "autoSMART can only be installed on Linux systems" log_error "Current system: $(uname)" exit 1 fi # Check systemd if ! command -v systemctl &> /dev/null; then log_error "systemd is required but not found" exit 1 fi # Check and report dependency status if ! verify_dependencies >/dev/null 2>&1; then log_warning "Some dependencies are missing (will be installed automatically)" fi # Check available space AVAILABLE_SPACE=$(df / | tail -1 | awk '{print $4}') if [[ $AVAILABLE_SPACE -lt 100000 ]]; then log_warning "Less than 100MB available space. Installation may fail." fi log_success "System requirements check passed" } handle_uninstall() { log_info "🗑️ Uninstalling autoSMART..." # Stop and disable service if systemctl is-active --quiet autosmart; then systemctl stop autosmart fi if systemctl is-enabled --quiet autosmart; then systemctl disable autosmart fi # Remove service file if [[ -f "$SYSTEMD_SERVICE" ]]; then rm "$SYSTEMD_SERVICE" systemctl daemon-reload fi # Remove installation directory if [[ -d "$INSTALL_DIR" ]]; then rm -rf "$INSTALL_DIR" fi # Remove configuration directory if [[ -d "$CONFIG_DIR" ]]; then rm -rf "$CONFIG_DIR" fi # Remove log rotation if [[ -f "/etc/logrotate.d/autosmart" ]]; then rm "/etc/logrotate.d/autosmart" fi log_success "✅ autoSMART uninstalled successfully" exit 0 } # Function to check if a package is installed check_package_installed() { local package="$1" local package_manager="$2" case "$package_manager" in "apt-get") dpkg -l | grep -q "^ii $package\( \|:\)" 2>/dev/null ;; "yum"|"dnf") rpm -qa | grep -q "$package" 2>/dev/null ;; "zypper") zypper se -i "$package" | grep -q "^i" 2>/dev/null ;; "pacman") pacman -Q "$package" >/dev/null 2>&1 ;; *) return 1 ;; esac } # Function to verify all dependencies are installed verify_dependencies() { log_info "🔍 Verifying system dependencies..." local missing_packages=() local package_manager="" # Detect package manager if command -v apt-get &> /dev/null; then package_manager="apt-get" elif command -v yum &> /dev/null; then package_manager="yum" elif command -v dnf &> /dev/null; then package_manager="dnf" elif command -v zypper &> /dev/null; then package_manager="zypper" elif command -v pacman &> /dev/null; then package_manager="pacman" else log_warning "Unknown package manager. Dependency verification limited." return 1 fi # Check system packages (including Perl modules from distribution) local system_packages=("perl" "smartmontools" "postgresql-client" "curl" "wget") local perl_packages=() # Add Perl module packages based on package manager case "$package_manager" in "apt-get") perl_packages+=("libdbi-perl" "libdbd-pg-perl" "libjson-perl" "libfile-slurp-perl" "libgetopt-long-descriptive-perl" "libconfig-simple-perl") ;; "yum"|"dnf") perl_packages+=("perl-DBI" "perl-DBD-Pg" "perl-JSON" "perl-File-Slurp" "perl-Getopt-Long" "perl-Config-Simple") ;; "zypper") perl_packages+=("perl-DBI" "perl-DBD-Pg" "perl-JSON" "perl-File-Slurp" "perl-Getopt-Long-Descriptive" "perl-Config-Simple") ;; "pacman") perl_packages+=("perl-dbi" "perl-dbd-pg" "perl-json" "perl-file-slurp") ;; esac # Check system packages for package in "${system_packages[@]}"; do if ! check_package_installed "$package" "$package_manager"; then missing_packages+=("$package") fi done # Check Perl packages from distribution for package in "${perl_packages[@]}"; do if ! check_package_installed "$package" "$package_manager"; then missing_packages+=("$package") fi done # Report results if [[ ${#missing_packages[@]} -eq 0 ]]; then log_success "✅ All dependencies are available" return 0 else log_warning "Missing dependencies detected:" if [[ ${#missing_packages[@]} -gt 0 ]]; then log_warning " Missing packages: ${missing_packages[*]}" fi return 1 fi } # Function to install dependencies install_dependencies() { log_info "📦 Installing system dependencies..." # First check if dependencies are already installed if verify_dependencies >/dev/null 2>&1; then log_success "All dependencies already installed" return 0 fi log_info "Installing missing dependencies..." if command -v apt-get &> /dev/null; then # Debian/Ubuntu log_info "Updating package lists..." apt-get update -qq PACKAGES=( "perl" "libdbi-perl" "libdbd-pg-perl" "libjson-perl" "libfile-slurp-perl" "libgetopt-long-descriptive-perl" "libconfig-simple-perl" "smartmontools" "postgresql-client" "curl" "wget" ) for package in "${PACKAGES[@]}"; do if ! check_package_installed "$package" "apt-get"; then log_info "Installing $package..." if ! apt-get install -y "$package" >/dev/null 2>&1; then log_error "Failed to install $package" exit 1 fi fi done elif command -v dnf &> /dev/null; then # Fedora/RHEL 8+ log_info "Updating package lists..." dnf update -y -q PACKAGES=( "perl" "perl-DBI" "perl-DBD-Pg" "perl-JSON" "perl-File-Slurp" "perl-Getopt-Long" "perl-Config-Simple" "smartmontools" "postgresql" "curl" "wget" ) for package in "${PACKAGES[@]}"; do if ! check_package_installed "$package" "dnf"; then log_info "Installing $package..." if ! dnf install -y "$package" >/dev/null 2>&1; then log_error "Failed to install $package" exit 1 fi fi done elif command -v yum &> /dev/null; then # RHEL/CentOS 7 log_info "Updating package lists..." yum update -y -q PACKAGES=( "perl" "perl-DBI" "perl-DBD-Pg" "perl-JSON" "perl-File-Slurp" "perl-Getopt-Long" "perl-Config-Simple" "smartmontools" "postgresql" "curl" "wget" ) for package in "${PACKAGES[@]}"; do if ! check_package_installed "$package" "yum"; then log_info "Installing $package..." if ! yum install -y "$package" >/dev/null 2>&1; then log_error "Failed to install $package" exit 1 fi fi done elif command -v zypper &> /dev/null; then # openSUSE log_info "Updating package lists..." zypper refresh -q PACKAGES=( "perl" "perl-DBI" "perl-DBD-Pg" "perl-JSON" "perl-File-Slurp" "perl-Getopt-Long-Descriptive" "perl-Config-Simple" "smartmontools" "postgresql" "curl" "wget" ) for package in "${PACKAGES[@]}"; do if ! check_package_installed "$package" "zypper"; then log_info "Installing $package..." if ! zypper install -y "$package" >/dev/null 2>&1; then log_error "Failed to install $package" exit 1 fi fi done elif command -v pacman &> /dev/null; then # Arch Linux log_info "Updating package lists..." pacman -Sy --noconfirm PACKAGES=( "perl" "perl-dbi" "perl-dbd-pg" "perl-json" "perl-file-slurp" "smartmontools" "postgresql" "curl" "wget" ) for package in "${PACKAGES[@]}"; do if ! check_package_installed "$package" "pacman"; then log_info "Installing $package..." if ! pacman -S --noconfirm "$package" >/dev/null 2>&1; then log_error "Failed to install $package" exit 1 fi fi done else log_error "Unsupported package manager. Please install dependencies manually:" log_error " - perl, smartmontools, postgresql-client, curl, wget" log_error " - Perl modules: DBI, DBD::Pg, JSON, File::Slurp, Getopt::Long, Config::Simple" exit 1 fi # Verify installation was successful if verify_dependencies >/dev/null 2>&1; then log_success "✅ All dependencies installed successfully" else log_error "Some dependencies may not have installed correctly" exit 1 fi } create_directories() { log_info "📁 Creating directory structure..." # Create main directories mkdir -p "$INSTALL_DIR"/{scripts,lib,config,docs} mkdir -p "$CONFIG_DIR" # Set permissions chmod 755 "$INSTALL_DIR" chmod 755 "$CONFIG_DIR" log_success "Directories created" } copy_files() { log_info "📋 Copying autoSMART files..." # Copy scripts if [[ -d "$PROJECT_ROOT/scripts" ]]; then cp -r "$PROJECT_ROOT/scripts"/* "$INSTALL_DIR/scripts/" chmod +x "$INSTALL_DIR/scripts"/*.sh 2>/dev/null || true chmod +x "$INSTALL_DIR/scripts"/*.pl 2>/dev/null || true fi # Copy libraries if [[ -d "$PROJECT_ROOT/lib" ]]; then cp -r "$PROJECT_ROOT/lib"/* "$INSTALL_DIR/lib/" fi # Copy default configuration to /etc/default/autosmart if [[ -f "/etc/default/autosmart" ]]; then log_info "📝 Existing configuration found, merging with defaults..." # Backup existing configuration cp "/etc/default/autosmart" "/etc/default/autosmart.backup.$(date +%Y%m%d_%H%M%S)" # Read existing configuration declare -A existing_config while IFS='=' read -r key value; do if [[ $key =~ ^[A-Z_]+$ ]] && [[ -n $value ]]; then # Remove quotes and store value=$(echo "$value" | sed 's/^"//;s/"$//') existing_config["$key"]="$value" fi done < "/etc/default/autosmart" # Start with new configuration template if [[ -f "$PROJECT_ROOT/config/autosmart-defaults.conf" ]]; then cp "$PROJECT_ROOT/config/autosmart-defaults.conf" "/etc/default/autosmart" else cat > "/etc/default/autosmart" << 'EOF' # AutoSMART Configuration AUTOSMART_DEBUG="false" EOF fi # Merge existing values back for key in "${!existing_config[@]}"; do value="${existing_config[$key]}" if grep -q "^${key}=" "/etc/default/autosmart"; then # Update existing key with preserved value sed -i "s|^${key}=.*|${key}=\"${value}\"|" "/etc/default/autosmart" log_info "✓ Preserved existing setting: ${key}=\"${value}\"" else # Add new key echo "${key}=\"${value}\"" >> "/etc/default/autosmart" log_info "✓ Added custom setting: ${key}=\"${value}\"" fi done log_info "✓ Configuration merged successfully" elif [[ -f "$PROJECT_ROOT/config/autosmart-defaults.conf" ]]; then cp "$PROJECT_ROOT/config/autosmart-defaults.conf" /etc/default/autosmart log_info "✓ AutoSMART default configuration installed" else log_warning "Default configuration file not found, creating basic one" cat > /etc/default/autosmart << 'EOF' # AutoSMART Configuration AUTOSMART_DEBUG="false" EOF fi # Copy documentation if [[ -d "$PROJECT_ROOT/docs" ]]; then cp -r "$PROJECT_ROOT/docs"/* "$INSTALL_DIR/docs/" fi # Copy SQL files if [[ -d "$PROJECT_ROOT/sql" ]]; then cp -r "$PROJECT_ROOT/sql" "$INSTALL_DIR/" fi log_success "Files copied" } create_configuration() { log_info "⚙️ Creating configuration files..." # Main configuration file cat > "$CONFIG_DIR/autosmart.conf" << EOF # autoSMART Configuration File # Generated on $(date) [database] host = $DB_HOST port = 5432 user = $DB_USER password = $DB_PASS database = $DB_NAME timeout = 30 [node] id = $NODE_ID scan_interval = $SCAN_INTERVAL full_scan_interval = $FULL_SCAN_INTERVAL store_unchanged = false max_retries = 3 [collection] temperature_threshold = 5 parameter_changes_only = true enable_predictive_analysis = true health_check_interval = 86400 [logging] level = INFO max_size = 10M rotate_count = 5 syslog = true [alerts] enable = true temperature_critical = 60 reallocated_sectors_warning = 1 pending_sectors_critical = 5 EOF # YAML format configuration for Perl daemon cat > "$CONFIG_DIR/cluster-$NODE_ID.conf" << EOF # autoSMART YAML Configuration for $NODE_ID database: host: $DB_HOST port: 5432 user: $DB_USER password: $DB_PASS database: $DB_NAME node: id: $NODE_ID scan_interval: $SCAN_INTERVAL store_unchanged: false collection: temperature_threshold: 5 parameter_changes_only: true full_scan_interval: $FULL_SCAN_INTERVAL EOF # Set secure permissions on config files chmod 600 "$CONFIG_DIR"/*.conf log_success "Configuration created" } create_systemd_service() { log_info "🔧 Creating systemd service..." cat > "$SYSTEMD_SERVICE" << EOF [Unit] Description=autoSMART SMART Data Collector Documentation=file://$INSTALL_DIR/docs/README.md After=network.target postgresql.service Wants=postgresql.service [Service] Type=simple EnvironmentFile=/etc/default/autosmart ExecStart=$INSTALL_DIR/scripts/smart-collector-daemon.pl --config $CONFIG_DIR/cluster-$NODE_ID.conf --foreground ExecReload=/bin/kill -HUP \$MAINPID KillMode=process Restart=always RestartSec=30 User=root Group=root # Security settings NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=$CONFIG_DIR PrivateTmp=true # Resource limits LimitNOFILE=1024 MemoryMax=100M CPUQuota=10% # Logging StandardOutput=journal StandardError=journal SyslogIdentifier=autosmart [Install] WantedBy=multi-user.target EOF # Reload systemd systemctl daemon-reload log_success "Systemd service created" } test_database_connection() { log_info "🔗 Testing database connection..." # Test connection using psql if command -v psql &> /dev/null; then if PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "SELECT version();" >/dev/null 2>&1; then log_success "Database connection successful" else log_warning "Database connection failed. Service may not start correctly." log_info "Please ensure:" log_info " • PostgreSQL server is running on $DB_HOST" log_info " • Database '$DB_NAME' exists" log_info " • User '$DB_USER' has proper permissions" fi else log_warning "psql not found. Cannot test database connection." fi } test_smart_detection() { log_info "🔍 Testing SMART device detection..." DEVICES_FOUND=0 for device in /dev/sd? /dev/nvme?n?; do if [[ -b "$device" ]] && smartctl -i "$device" >/dev/null 2>&1; then MODEL=$(smartctl -i "$device" | grep "Device Model\|Model Number" | head -1 | cut -d: -f2 | xargs) if [[ -n "$MODEL" ]]; then log_info " Found: $device - $MODEL" DEVICES_FOUND=$((DEVICES_FOUND + 1)) fi fi done if [[ $DEVICES_FOUND -gt 0 ]]; then log_success "Detected $DEVICES_FOUND SMART-capable devices" else log_warning "No SMART-capable devices detected" fi } finalize_installation() { log_info "🎯 Finalizing installation..." # Enable service (but don't start yet) systemctl enable "$SERVICE_NAME" # Create log rotation cat > "/etc/logrotate.d/autosmart" << EOF /var/log/autosmart/*.log { daily rotate 7 compress delaycompress missingok notifempty postrotate systemctl reload-or-restart autosmart endscript } EOF log_success "Installation finalized" } show_completion_message() { log_success "✅ autoSMART installation completed successfully!" log_info "" log_info "📋 Installation Summary:" log_info " • Install Directory: $INSTALL_DIR" log_info " • Config Directory: $CONFIG_DIR" log_info " • Service Name: $SERVICE_NAME" log_info " • Node ID: $NODE_ID" log_info "" log_info "🚀 Next Steps:" log_info " 1. Start the service:" log_info " systemctl start $SERVICE_NAME" log_info "" log_info " 2. Check service status:" log_info " systemctl status $SERVICE_NAME" log_info "" log_info " 3. View logs:" log_info " journalctl -u $SERVICE_NAME -f" log_info "" log_info "📖 Documentation: $INSTALL_DIR/docs/README.md" log_info "⚙️ Configuration: $CONFIG_DIR/autosmart.conf" log_info "" log_info "🎉 autoSMART is ready to monitor your storage devices!" } # Main execution main() { parse_arguments "$@" show_header case "$COMMAND" in uninstall) handle_uninstall ;; install) check_requirements # Handle force reinstall if [[ "$FORCE_REINSTALL" == true ]]; then log_info "🗑️ Force reinstall: cleaning previous installation..." handle_uninstall 2>/dev/null || true sleep 2 fi # Handle config-only mode if [[ "$CONFIG_ONLY" == true ]]; then log_info "⚙️ Configuration-only mode" if [[ ! -d "$INSTALL_DIR" ]]; then log_error "autoSMART is not installed. Run full installation first." exit 1 fi create_configuration log_success "✅ Configuration updated successfully!" exit 0 fi # Full installation install_dependencies create_directories copy_files create_configuration create_systemd_service test_database_connection test_smart_detection finalize_installation show_completion_message ;; *) log_error "Unknown command: $COMMAND" show_usage exit 1 ;; esac } # Run main function main "$@"