Blame | Last modification | View Log | Download | RSS feed
#! /usr/bin/env perl
# Copyright (c) 2025, R. W. Rodolico
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use warnings;
use strict;
use open ':std', ':encoding(utf8)';
use utf8;
# sysinfo-client-interactive
# Author: R. W. Rodolico
# Interactive configuration script for sysinfo-client
# This script provides an interactive menu interface for configuring
# sysinfo-client settings, then creates a temporary configuration file
# and executes sysinfo-client with that configuration
# find our location and use it for searching for libraries
BEGIN {
use FindBin;
use File::Spec;
use Cwd 'abs_path';
use File::Basename;
use lib dirname( abs_path( __FILE__ ) );
eval( 'use YAML::Tiny;' );
eval( 'use DataTransport;' );
eval( 'use File::Temp qw/ tempfile /;' );
}
# contains the directory our script is in
my $sourceDir = dirname( abs_path( __FILE__ ) );
# define the version number
use version 0.77; our $VERSION = version->declare("v4.0.0");
# see https://perldoc.perl.org/Getopt/Long.html
use Getopt::Long;
Getopt::Long::Configure ("bundling");
#######################################################
#
# getDMIDecode( $key, $type )
#
# Retrieves system information using dmidecode command
#
# Parameters
# $key - The field name to search for in dmidecode output
# $type - Optional dmidecode type parameter (e.g., 'system')
#
# Returns
# The value associated with the key, or empty string if not found
#
# Uses dmidecode to query system BIOS/DMI information such as
# UUID, serial numbers, and other hardware identifiers
#
#######################################################
sub getDMIDecode {
my ( $key, $type ) = @_;
my $command = 'dmidecode';
my $bogusValues = [
'notspecified', # Not Specified
'notapplicable', # Not Applicable
'tobefilledbyoe.m.', # To Be Filled By O.E.M.
'none', # None
'oem', # OEM
'defaultstring', # Default string
'undefined', # Undefined
'00000000-0000-0000-0000-000000000000',
'0123456789',
'03000200-0400-0500-0006-000700080009',
'notsettable', # not settable
'notavailable', # Not Available
];
$command .= " -t $type " if $type;
$command .= " | grep -i '$key'";
my $value = `$command`;
chomp $value;
#die "$key\t$value\n$value\n";
# get the value after the colon
if ( $value =~ m/:\s*(.*)\s*$/ ) {
$value = $1;
} else {
return '';
}
$value =~ s/\s//g;
$value = lc $value;
# go through bogus values and set to empty if we find this is one of them
$value = '' if grep { $value eq $_ } @$bogusValues;
return $value;
}
#######################################################
#
# getHostName
#
# return hostname from hostname -f
#
#######################################################
sub getHostName {
my $hostname = lc $^O eq 'mswin32' ? `hostname` : `hostname -f`;
chomp $hostname;
return $hostname;
}
#######################################################
#
# interactiveConfig( $config )
#
# Interactively prompts user to configure system settings
#
# Parameters
# $config - reference to configuration hash
#
# Returns
# Reference to updated configuration hash
#
# Presents a menu allowing the user to set:
# - Host name
# - Client name
# - Serial number (auto-detected via dmidecode if not set)
# - UUID (auto-detected via dmidecode if not set)
# - Modules directory
# - Scripts directory
#
#######################################################
sub interactiveConfig {
my $config = shift;
$config->{'moduleDirs'} = $config->{'moduleDirs'}[0] if ref($config->{'moduleDirs'}) eq 'ARRAY';
$config->{'scriptDirs'} = $config->{'scriptDirs'}[0] if ref($config->{'scriptDirs'}) eq 'ARRAY';
#$config->{'UUID'} = getDMIDecode( 'uuid', 'system' ) unless $config->{'UUID'};
#$config->{'serialNumber'} = getDMIDecode( 'serial number', 'system' ) unless $config->{'serialNumber'};
my %menu = (
1 => {'prompt' => 'Host Name', 'key' => 'hostname' },
2 => {'prompt' => 'Client Name', 'key' => 'clientName' },
3 => {'prompt' => 'Serial Number', 'key' => 'serialNumber' },
4 => {'prompt' => 'UUID', 'key' => 'UUID' },
5 => {'prompt' => 'Modules Directory', 'key' => 'moduleDirs' },
6 => {'prompt' => 'Scripts Directory', 'key' => 'scriptDirs' }
);
my $choice = 'quit';
while ( $choice ) {
print "\n=== Sysinfo Client Interactive Configuration ===\n\n";
foreach my $menuItem ( sort {$a <=> $b} keys %menu ) {
print "$menuItem. " . $menu{$menuItem}{'prompt'} . ': ' . $config->{$menu{$menuItem}{'key'}} . "\n";
}
print "\nEnter Menu Item to change, or press Enter to proceed: ";
$choice = <>;
chomp $choice;
last unless $choice;
if ( exists $menu{$choice} ) {
print $menu{$choice}{'prompt'} . ' [' . $config->{$menu{$choice}{'key'}} . ']: ';
my $value = <>;
chomp $value;
$config->{$menu{$choice}{'key'}} = $value if ($value);
# if we don't have a serial number, try to create one by hashing hostname-clientname
if ( ! $config->{serialNumber} && $config->{clientName} && $config->{hostname} ) {
$config->{'serialNumber'} = qx(echo `hostname -f`-$config->{clientName} | md5sum | cut -d' ' -f1);
chomp $config->{'serialNumber'};
print "Auto-detected Serial Number: " . $config->{'serialNumber'} . "\n" if $config->{'serialNumber'};
}
} else {
print "Invalid choice. Please try again.\n";
}
}
# Convert back to arrays for YAML output
$config->{'moduleDirs'} = [ $config->{'moduleDirs'} ] unless ref($config->{'moduleDirs'}) eq 'ARRAY';
$config->{'scriptDirs'} = [ $config->{'scriptDirs'} ] unless ref($config->{'scriptDirs'}) eq 'ARRAY';
return $config;
}
#######################################################
#
# help()
#
# Display help message showing command-line options
#
#######################################################
sub help {
use File::Basename;
print basename($0) . " $VERSION\n";
print <<END
$0 [options]
Interactive configuration tool for sysinfo-client.
Prompts for configuration values and runs sysinfo-client with those settings.
Options:
--version - display version and exit
--help - display this help message
-c,
--client='xxx' - Pre-set client name
-s,
--serial='xxx' - Pre-set serial number
-h,
--hostname='xxx' - Pre-set hostname
-m,
--modules=/path/ - Pre-set path to modules
--scripts=/path/ - Pre-set path to scripts
-p,
--periodic - Pass periodic flag to sysinfo-client
-t,
--test - Pass test flag to sysinfo-client
--outputType - Set output file type for sysinfo-client
After interactive configuration, this script creates a temporary
configuration file and executes sysinfo-client with it.
END
}
# Initialize default configuration
my %configuration = (
'moduleDirs' => ["$sourceDir/modules"],
'scriptDirs' => ["$sourceDir/scripts"],
'clientName' => '',
'serialNumber' => getDMIDecode( 'serial number', 'system' ),
'logging' => {
'log type' => 'file',
'log level' => 4,
'log path' => '/tmp/sysinfo.log',
},
'UUID' => getDMIDecode( 'uuid', 'system' ),
'hostname' => &getHostName(),
'transports' => {
'3' => {
'-name-' => 'saveLocal',
'sendScript' => 'save_local',
'output directory' => "$sourceDir/reports"
}
}
);
# Command line options
my $version;
my $help_flag;
my $periodic = 0;
my $test = 0;
my $outputType;
GetOptions (
'help' => \$help_flag,
'version' => \$version,
'client|c=s' => \$configuration{clientName},
'serial|s=s' => \$configuration{serialNumber},
'hostname=s' => \$configuration{hostname},
'modules|m=s' => \$configuration{moduleDirs},
'scripts=s' => \$configuration{scriptDirs},
'periodic|p' => \$periodic,
'test|t' => \$test,
'outputType|o=s' => \$outputType
) or die "Error parsing command line\n";
if ( $help_flag ) { &help(); exit; }
if ( $version ) { use File::Basename; print basename($0) . " $VERSION\n"; exit; }
# Run interactive configuration
print "\nStarting interactive configuration for sysinfo-client...\n";
%configuration = %{ &interactiveConfig( \%configuration ) };
# Validate required fields
die "Client name is required!\n" unless $configuration{'clientName'};
# Create temporary configuration file
my ($fh, $tempConfigFile) = tempfile(
'sysinfo-client-XXXXXX',
SUFFIX => '.dat',
DIR => '/tmp',
UNLINK => 1
);
print "\nCreating temporary configuration file: $tempConfigFile\n";
# Write configuration to DataTransport file
my $dt = DataTransport->new();
$dt->writeFile(\%configuration, $tempConfigFile) or die "Failed to write configuration file: $!\n";
close $fh;
# Build command to execute sysinfo-client
my $sysinfoClient = "$sourceDir/sysinfo-client";
die "Cannot find sysinfo-client at $sysinfoClient\n" unless -f $sysinfoClient;
my $command = "perl $sysinfoClient --config=$tempConfigFile --configtype=datatransport";
$command .= " --periodic" if $periodic;
$command .= " --test" if $test;
$command .= " --outputType=$outputType" if $outputType;
print "\nExecuting: $command\n\n";
print "="x60 . "\n\n";
# Execute sysinfo-client with the temporary config file
system($command);
my $exitCode = $? >> 8;
print "\n" . "="x60 . "\n";
print "sysinfo-client completed with exit code: $exitCode\n";
# Temporary file will be automatically cleaned up when script ends due to UNLINK => 1
exit $exitCode;