Subversion Repositories camp_sysinfo_client_3

Rev

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;