f16725e 3 months ago History
1 contributor
755 lines | 21.281kb
#!/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 "$(dirnameverify_dependencies() {
    log_info "๐Ÿ” Verifying system dependencies..."
    
    local missing_packages=()
    local package_manager=""SH_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 <IP>"
    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 missing_perl_modules=()
    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 from local packages (offline)
install_dependencies_offline() {
    log_info "๏ฟฝ๐Ÿ“ฆ Installing dependencies from local packages..."
    
    local packages_dir="$PROJECT_ROOT/packages"
    
    if [[ ! -d "$packages_dir" ]]; then
        log_warning "Local packages directory not found: $packages_dir"
        log_info "Falling back to online installation..."
        return 1
    fi
    
    local package_manager=""
    if command -v apt-get &> /dev/null; then
        package_manager="apt-get"
        local deb_files=("$packages_dir"/*.deb)
        if [[ -f "${deb_files[0]}" ]]; then
            log_info "Installing .deb packages..."
            dpkg -i "$packages_dir"/*.deb 2>/dev/null || {
                log_info "Fixing broken dependencies..."
                apt-get install -f -y >/dev/null 2>&1
            }
        fi
    elif command -v yum &> /dev/null || command -v dnf &> /dev/null; then
        package_manager="yum"
        local rpm_files=("$packages_dir"/*.rpm)
        if [[ -f "${rpm_files[0]}" ]]; then
            log_info "Installing .rpm packages..."
            if command -v dnf &> /dev/null; then
                dnf install -y "$packages_dir"/*.rpm >/dev/null 2>&1
            else
                yum localinstall -y "$packages_dir"/*.rpm >/dev/null 2>&1
            fi
        fi
    fi
    
    # Verify installation
    if verify_dependencies >/dev/null 2>&1; then
        log_success "โœ… Offline dependencies installed successfully"
        return 0
    else
        log_warning "Offline installation incomplete"
        return 1
    fi
}

# Enhanced dependency installation with offline support
install_dependencies() {
    log_info "๐Ÿ“ฆ Installing system dependencies..."
    
    # First try to verify if dependencies are already installed
    if verify_dependencies >/dev/null 2>&1; then
        log_success "All dependencies already installed"
        return 0
    fi
    
    # If offline mode is enabled, only try offline installation
    if [[ "$OFFLINE_MODE" == true ]]; then
        log_info "Offline mode enabled - using local packages only"
        if install_dependencies_offline; then
            return 0
        else
            log_error "Offline installation failed and online installation is disabled"
            log_error "Please check packages directory: $PROJECT_ROOT/packages"
            exit 1
        fi
    fi
    
    # Try offline installation first
    if install_dependencies_offline; then
        return 0
    fi
    
    # Fall back to online installation
    log_info "Attempting online installation..."
    
    if command -v apt-get &> /dev/null; then
        # Debian/Ubuntu
        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 ! dpkg -l | grep -q "^ii  $package "; then
                log_info "Installing $package..."
                apt-get install -y "$package" >/dev/null 2>&1
            fi
        done
        
    elif command -v yum &> /dev/null; then
        # RHEL/CentOS
        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 ! rpm -qa | grep -q "$package"; then
                log_info "Installing $package..."
                yum install -y "$package" >/dev/null 2>&1
            fi
        done
        
    else
        log_error "Unsupported package manager. Please install dependencies manually."
        exit 1
    fi
    
    log_success "Dependencies installed"
}

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 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
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++))
            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 "$@"