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