#! /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 < ["$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;