Madagascar / projects / autoSMART / scripts / autosmart-collector.pl
f16725e 3 months ago History
1 contributor
348 lines | 8.831kb
#!/usr/bin/perl

use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin/../lib";

use SmartCollector;
use Getopt::Long;
use POSIX qw(strftime);

=head1 NAME

autosmart-collector.pl - SMART data collection daemon for Proxmox cluster

=head1 SYNOPSIS

    autosmart-collector.pl [OPTIONS]

=head1 OPTIONS

    --cluster-config FILE  Cluster configuration file (default: /etc/pve/autoSMART/cluster.conf)
    --local-config FILE    Local configuration file (default: /etc/default/autosmart)
    --daemon              Run as daemon
    --once                Run once and exit (for cron jobs)
    --device PATH         Collect from specific device only
    --debug               Enable debug logging
    --help                Show this help

=head1 DESCRIPTION

This script collects SMART data from HDDs in a Proxmox cluster environment.
Configuration is split between cluster-wide settings in /etc/pve/autoSMART/
and local node settings in /etc/default/autosmart.

=cut

# Configuration
my $cluster_config = '/etc/pve/autoSMART/cluster.conf';
my $local_config = '/etc/default/autosmart';
my $daemon_mode = 0;
my $run_once = 0;
my $specific_device = '';
my $debug = 0;
my $help = 0;

GetOptions(
    'cluster-config=s' => \$cluster_config,
    'local-config=s'   => \$local_config,
    'daemon'           => \$daemon_mode,
    'once'             => \$run_once,
    'device=s'         => \$specific_device,
    'debug'            => \$debug,
    'help'             => \$help,
) or die "Error parsing command line arguments\n";

if ($help) {
    print_help();
    exit 0;
}

# Load local configuration for environment setup
my %local_settings = load_local_config($local_config);

# Override debug flag from local config if not specified
unless ($debug) {
    $debug = ($local_settings{AUTOSMART_DEBUG_ENABLED} eq 'true') ? 
             ($local_settings{AUTOSMART_DEBUG_LEVEL} || 1) : 0;
}

# Validate configuration files
unless (-f $cluster_config) {
    die "Cluster configuration not found: $cluster_config\n";
}

unless (-f $local_config) {
    die "Local configuration not found: $local_config\n";
}

# Check for emergency stop
if (-f ($local_settings{AUTOSMART_EMERGENCY_STOP_FILE} || '/etc/autosmart/EMERGENCY_STOP')) {
    die "Emergency stop file detected - autoSMART is disabled\n";
}

# Initialize collector with Proxmox cluster configuration
my $collector = SmartCollector->new(
    cluster_config => $cluster_config,
    local_config   => $local_config,
    debug          => $debug,
);

log_message("autoSMART collector starting for cluster node...");

if ($specific_device) {
    # Collect from specific device
    collect_specific_device($collector, $specific_device);
} elsif ($run_once) {
    # Single collection run
    run_collection_cycle($collector);
} elsif ($daemon_mode) {
    # Daemon mode
    run_daemon($collector, \%local_settings);
} else {
    # Default: single collection run
    run_collection_cycle($collector);
}

log_message("autoSMART collector finished");

=head2 load_local_config

Load local configuration from /etc/default/autosmart

=cut

sub load_local_config {
    my $config_file = shift;
    
    my %settings = ();
    
    return %settings unless -f $config_file;
    
    open my $fh, '<', $config_file 
        or die "Cannot read local config: $config_file: $!";
    
    while (my $line = <$fh>) {
        chomp $line;
        next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
        
        if ($line =~ /^(\w+)=(.+)$/) {
            my ($key, $value) = ($1, $2);
            $value =~ s/^["']|["']$//g;  # Remove quotes
            $settings{$key} = $value;
        }
    }
    
    close $fh;
    
    return %settings;
}

=head2 collect_specific_device

Collect SMART data from a specific device

=cut

sub collect_specific_device {
    my ($collector, $device_path) = @_;
    
    log_message("Collecting SMART data from $device_path");
    
    my $smart_data = $collector->collect_smart_data($device_path);
    
    unless ($smart_data) {
        log_message("ERROR: Failed to collect SMART data from $device_path");
        exit 1;
    }
    
    # Create minimal drive info for storage
    my $drive_info = {
        device_path => $device_path,
        serial      => $smart_data->{serial_number} || 'unknown',
        model       => $smart_data->{model_name} || 'unknown',
        size_gb     => 0,
        madagascar_id => "manual_$device_path",
    };
    
    if ($collector->store_smart_data($drive_info, $smart_data)) {
        log_message("Successfully stored SMART data for $device_path");
    } else {
        log_message("ERROR: Failed to store SMART data for $device_path");
        exit 1;
    }
}

=head2 run_collection_cycle

Execute one complete collection cycle

=cut

sub run_collection_cycle {
    my $collector = shift;
    
    log_message("Starting collection cycle");
    
    my $result = $collector->collect_all();
    
    log_message(sprintf(
        "Collection cycle complete: %d successful, %d failed, %d total",
        $result->{successful},
        $result->{failed},
        $result->{total}
    ));
    
    # Exit with error code if any collections failed
    if ($result->{failed} > 0) {
        exit 1;
    }
}

=head2 run_daemon

Run as daemon with periodic collection

=cut

sub run_daemon {
    my $collector = shift;
    
    # Get collection interval from config
    my $cfg = Config::Simple->new("$config_dir/smart.conf");
    my $interval = $cfg->param('monitoring.collection_interval') || 300;
    
    log_message("Running in daemon mode (interval: ${interval}s)");
    
    # Set up signal handlers for graceful shutdown
    my $running = 1;
    
    $SIG{TERM} = sub {
        log_message("Received SIGTERM, shutting down gracefully");
        $running = 0;
    };
    
    $SIG{INT} = sub {
        log_message("Received SIGINT, shutting down gracefully");
        $running = 0;
    };
    
    # Main daemon loop
    while ($running) {
        my $start_time = time();
        
        eval {
            run_collection_cycle($collector);
        };
        
        if ($@) {
            log_message("ERROR in collection cycle: $@");
        }
        
        # Calculate sleep time to maintain interval
        my $elapsed = time() - $start_time;
        my $sleep_time = $interval - $elapsed;
        
        if ($sleep_time > 0) {
            log_message("Sleeping for ${sleep_time}s until next collection");
            
            # Sleep in small chunks to allow signal handling
            while ($sleep_time > 0 && $running) {
                my $chunk = $sleep_time > 5 ? 5 : $sleep_time;
                sleep($chunk);
                $sleep_time -= $chunk;
            }
        } else {
            log_message("WARNING: Collection took longer than interval (${elapsed}s > ${interval}s)");
        }
    }
    
    log_message("Daemon shutdown complete");
}

=head2 log_message

Log message with timestamp

=cut

sub log_message {
    my $message = shift;
    
    my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime());
    print "[$timestamp] autosmart-collector: $message\n";
}

=head2 print_help

Display help information

=cut

sub print_help {
    print <<'EOF';
autoSMART Data Collector v1.0

USAGE:
    autosmart-collector.pl [OPTIONS]

OPTIONS:
    --config-dir DIR     Configuration directory (default: /etc/autosmart)
    --daemon             Run as daemon with periodic collection
    --once               Run once and exit (useful for cron jobs)
    --device PATH        Collect from specific device only (e.g., /dev/sda)
    --debug              Enable debug logging
    --help               Show this help message

EXAMPLES:
    # Run once (for cron jobs)
    autosmart-collector.pl --once

    # Run as daemon
    autosmart-collector.pl --daemon

    # Collect from specific device
    autosmart-collector.pl --device /dev/sda

    # Run with debug logging
    autosmart-collector.pl --debug --once

    # Use custom config directory
    autosmart-collector.pl --config-dir /opt/autosmart/config --once

CONFIGURATION:
    Configuration files should be in /etc/autosmart/ or specified directory:
    - smart.conf        SMART monitoring settings
    - database.conf     PostgreSQL connection settings

DAEMON MODE:
    In daemon mode, the collector runs continuously and collects data at
    intervals specified in smart.conf (monitoring.collection_interval).
    
    Send SIGTERM or SIGINT for graceful shutdown.

CRON MODE:
    Use --once flag for cron-based scheduling:
    
    # Collect every 5 minutes
    */5 * * * * /usr/local/bin/autosmart-collector.pl --once

EXIT CODES:
    0   Success
    1   Error (failed collections, missing config, etc.)

EOF
}

__END__

=head1 AUTHOR

AutoSMART Development Team

=head1 LICENSE

This software is part of the autoSMART project.

=cut