1 contributor
#!/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 "$@"