Subversion Repositories camp_sysinfo_client_3

Rev

Rev 122 | Blame | Last modification | View Log | Download | RSS feed

#! /usr/bin/env perl

use strict;
use warnings;

# install.pl
#
# installer for perl script, in this case, sysinfo
#
# Revision history
#
# Version 1.1.7 20161010 RWR
# Added ability to validate required libraries are installed
#
# version 1.2 20170327 RWR
# did some major modifications to correctly work on BSD systems also
#
# version 2.0 20190330 RWR
# changed it so all configs are YAML
#
# version 3.0 20191105 RWR
# set up so all options are presented on initial screen
# user can choose options to edit
# rest of install is automatic

our $VERSION = '3.0';

# find our location and use it for searching for libraries
BEGIN {
   use FindBin;
   use File::Spec;
   use lib File::Spec->catdir($FindBin::Bin);
   eval( 'use YAML::Tiny' );
}

my $sourceDir = File::Spec->catdir($FindBin::Bin);

use sysinfoconf;
use Data::Dumper;
use File::Basename;
use Getopt::Long;

our %install;
our %operatingSystems;
our %libraries;
our %binaries;

do "$sourceDir/installer_config.pl";

Getopt::Long::Configure ("bundling"); # allow -vd --os='debian'
# $verbose can have the following values
# 0 - do everything
# 1 - Do everything except the install, display what would be done
# 2 - Be verbose to STDERR
# 3 - Be very verbose
my $verbose = 0; # if test mode, simply show what would be done
my $dryRun = 0;
my $os;
my $help = 0;
my $version = 0;

my @messages; # stores any messages we want to show up at the end
my @feedback; # store feedback when we execute command line actions

my %configuration;

# simple display if --help is passed
sub help {
   my $oses = join( ' ', keys %operatingSystems );
   print <<END
$0 --verbose=x --os="osname" --dryrun --help --version

--os      - osname is one of [$oses]
--dryrun  - do not actually do anything, just tell you what I'd do
--verbose - x is 0 (normal) to 3 (horrible)
END
}


# attempt to locate the operating system.
# if found, will set some defaults for it.
sub setUpOperatingSystemSpecific {
   my ( $install, $operatingSystems, $os, $installDir ) = @_;
   print "They passed $os in as the \$os\n" if $verbose > 2;
   if ( $os ) {
      # We found the OS, set up some defaults
      $$install{'os'} = $os;
      print "Setting keys for operating system\n" if $verbose > 2;
      # merge operatingSystems into install
      foreach my $key ( keys %{$operatingSystems->{$os}} ) {
         if ( $key eq 'files' ) {
            $install->{'files'} = { %{$install->{'files'}}, %{$operatingSystems->{$os}->{'files'}} }
         } else {
            $install->{$key} = $operatingSystems->{ $os }->{$key};
         }
      } # if it is a known OS
   } # if
   return $os;
} # getOperatingSystem

# validates the libraries needed exist
# simply eval's each library. If it doesn't exist, creates a list of
# commands to be executed to install it, and displays missing libraries
# offering to install them if possible.
sub validateLibraries {
   my ( $libraries, $os ) = @_;
   my %return;
   foreach my $lib ( keys %$libraries ) {
      print "Checking on libarary $lib\n" if $verbose;
      eval( "use $lib;" );
      if ( $@ ) {
         $return{ $lib } = $libraries->{$lib}->{$os} ? $libraries->{$lib}->{$os} : 'UNK';
      }
   }
   return \%return;
} # validateLibraries


# check for any required binaries
sub validateBinaries {
   my ( $binaries, $os ) = @_;
   my %return;
   foreach my $bin ( keys %$binaries ) {
      unless ( `which $bin` ) {
         $return{$bin} = $binaries->{$bin}->{$os} ? $binaries->{$bin}->{$os} : 'UNK';
      }
   }
   return \%return;
} # validateBinaries
   
# get some input from the user and decide how to install/upgrade/remove/whatever
sub getInstallActions {
   my $install = shift;
   if ( -d $install->{'confdir'} ) {
      $install->{'action'} = "upgrade";
   } else {
      $install->{'action'} = 'install';
   }
   $install->{'build config'} = 'Y';
   $install->{'setup cron'} = 'Y';
}

# locate all items in $hash which have one of the $placeholder elements in it
# and replace, ie <binddir> is replaced with the actual binary directory
sub doPlaceholderSubstitution {
   my ($hash, $placeholder) = @_;
   return if ref $hash ne 'HASH';
   foreach my $key ( keys %$hash ) {
      if ( ref( $$hash{$key} ) ) {
         &doPlaceholderSubstitution( $$hash{$key}, $placeholder );
      } else {
         foreach my $place ( keys %$placeholder ) {
            $$hash{$key} =~ s/$place/$$placeholder{$place}/;
         } # foreach
      } # if..else
   } # foreach
   return;
}
   
# This will go through and first, see if anything is a directory, in
# which case, we'll create new entries for all files in there.
# then, it will do keyword substitution of <bindir> and <confdir>
# to populate the target.
# When this is done, each file should have a source and target that is
# a fully qualified path and filename
sub populateSourceDir {
   my ( $install, $sourceDir ) = @_;
   my %placeHolders = 
      ( 
        '<bindir>' => $$install{'bindir'},
        '<confdir>' => $$install{'confdir'},
        '<default owner>' => $$install{'default owner'},
        '<default group>' => $$install{'default group'},
        '<default permission>' => $$install{'default permission'},
        '<installdir>' => $sourceDir
      );

   my $allFiles = $$install{'files'};

   # find all directory entries and load files in that directory into $$install{'files'}
   foreach my $dir ( keys %$allFiles ) {
      if ( defined( $$allFiles{$dir}{'type'} ) && $$allFiles{$dir}{'type'} eq 'directory' ) {
         print "Found directory $dir\n" if $verbose > 2;
         if ( opendir( my $dh, "$sourceDir/$dir" ) ) {
            my @files = map{ $dir . '/' . $_ } grep { ! /^\./ && -f "$sourceDir/$dir/$_" } readdir( $dh );
            print "\tFound files " . join( ' ', @files ) . "\n" if $verbose > 2;
            foreach my $file ( @files ) {
               $$allFiles{ $file }{'type'} = 'file';
               if ( $dir eq 'modules' ) {
                  $$allFiles{ $file }{'permission'} = ( $file =~ m/$$install{'modules'}/ ) ? '0700' : '0600';
               } else {
                  $$allFiles{ $file }{'permission'} = $$allFiles{ $dir }{'permission'};
               }
               $$allFiles{ $file }{'owner'} = $$allFiles{ $dir }{'owner'};
               $$allFiles{ $file }{'target'} = $$allFiles{ $dir }{'target'};
            } # foreach
            closedir $dh;
         } # if opendir
      } # if it is a directory
   } # foreach
   # find all files, and set the source directory, and add the filename to
   # the target
   foreach my $file ( keys %$allFiles ) {
      $$allFiles{$file}{'source'} = "$sourceDir/$file";
      $$allFiles{$file}{'target'} .= "/$file";
   } # foreach

   # finally, do place holder substitution. This recursively replaces all keys
   # in  %placeHolders with the values.
   &doPlaceholderSubstitution( $install, \%placeHolders );

   print Dump( $install ) if $verbose > 2;

   return 1;
} # populateSourceDir

sub GetPermission {
   my $install = shift;
   print "Ready to install, please verify the following\n";
   print "A. Operating System:  " . $install->{'os'} . "\n";
   print "B. Installation Type: " . $install->{'action'} . "\n";
   print "C. Using Seed file: " . $install->{'configuration seed file'} . "\n" if -e $install->{'configuration seed file'};
   print "D. Target Binary Directory: " . $install->{'bindir'} . "\n";
   print "E. Target Configuration Directory: " . $install->{'confdir'} . "\n";
   print "F. Automatic Running: " . ( $install->{'crontab'} ? $install->{'crontab'} : 'No' ) . "\n";
   print "G. Install Missing Perl Libraries\n";
   foreach my $task ( sort keys %{ $install->{'missing libraries'} } ) {
      print "\t$task -> " . $install->{'missing libraries'}->{$task} . "\n";
   }
   print "H. Install Missing Binaries\n";
   foreach my $task ( sort keys %{ $install->{'missing binaries'} } ) {
      print "\t$task -> " . $install->{'missing binaries'}->{$task} . "\n";
   }
   return &yesno( "Do you want to proceed?" );
}

# note, this fails badly if you stick non-numerics in the version number
# simply create a "number" from the version which may have an arbitrary
# number of digits separated by a period, for example
# 1.2.5
# while there are digits, divide current calculation by 100, then add the last
# digit popped of. So, 1.2.5 would become
# 1 + 2/100 + 5/10000, or 1.0205
# and 1.25.16 would become 1.2516
# 
# Will give invalid results if any set of digits is more than 99
sub dotVersion2Number {
   my $dotVersion = shift;

   my @t = split( '\.', $dotVersion );
   #print "\n$dotVersion\n" . join( "\n", @t ) . "\n";
   my $return = 0;
   while ( @t ) {
      $return /= 100;
      $return += pop @t;
   }
   #print "$return\n";
   return $return;
}

# there is a file named VERSIONS. We get the values out of the install
# directory and (if it exists) the target so we can decide what needs
# to be updated.
sub getVersions {
   my $install = shift;
   my $currentVersionFile = $install->{'files'}->{'VERSION'}->{'target'};
   my $newVersionFile = $install->{'files'}->{'VERSION'}->{'source'};
   if ( open FILE,"<$currentVersionFile" ) {
      while ( my $line = <FILE> ) {
         chomp $line;
         my ( $filename, $version, $checksum ) = split( "\t", $line );
         $install{'files'}->{$filename}->{'installed version'} = $version ? $version : '';
      }
      close FILE;
   }
   if ( open FILE,"<$newVersionFile" ) {
      while ( my $line = <FILE> ) {
         chomp $line;
         my ( $filename, $version, $checksum ) = split( "\t", $line );
         $install->{'files'}->{$filename}->{'new version'} = $version ? $version : '';
      }
      close FILE;
   }
   foreach my $file ( keys %{$$install{'files'}} ) {
      $install{'files'}->{$file}->{'installed version'} = -2 unless defined $install->{'files'}->{$file}->{'installed version'};
      $install{'files'}->{$file}->{'new version'} = -1 unless defined $install->{'files'}->{$file}->{'new version'};
   }
   return 1;
} # getVersions


# this actually does the installation, except for the configuration
sub doInstall {
   my $install = shift;
   my $fileList = $install->{'files'};
   
   &checkDirectoryExists( $install->{'bindir'} . '/', $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
   foreach my $file ( keys %$fileList ) {
      next unless ( $fileList->{$file}->{'type'} && $fileList->{$file}->{'type'} eq 'file' );

      next if $install->{'action'} eq 'upgrade' && ! defined( $fileList->{$file}->{'installed version'} )
              ||
              ( &dotVersion2Number( $fileList->{$file}->{'new version'} ) <= &dotVersion2Number($fileList->{$file}->{'installed version'} ) );
      &checkDirectoryExists( $fileList->{$file}->{'target'}, $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
      &runCommand( 
            "cp $fileList->{$file}->{'source'} $fileList->{$file}->{'target'}",
            "chmod $fileList->{$file}->{'permission'} $fileList->{$file}->{'target'}",
            "chown $fileList->{$file}->{'owner'} $fileList->{$file}->{'target'}"
            );
      # if there is a post action, run it and store any return in @feedback
      push @feedback, `$fileList->{$file}->{'post action'}` if defined $fileList->{$file}->{'post action'};
      # if there is a message to be passed, store it in @messages
      push @messages, $fileList->{$file}->{'meesage'} if defined $fileList->{$file}->{'message'};
   } # foreach file
   # set up crontab, if necessary
   &runCommand( $install->{'crontab'} ) if defined ( $install->{'crontab'} );
   return 1;
}

sub postInstall {
   my $install = shift;

   # seed configuration, if needed
   if ( $$install{'build config'} ) {
      my $config;
      my @fileList;

      # the order is important here as, if multiple files exist, latter ones can
      # overwrite values in the previous. We do a push so the first value pushed
      # is processed last, ie has higher priority.
      push @fileList, $install->{'configuration'}->{'old configuration file'};
      push @fileList, $install->{'configuration'}->{'configuration file'};

      my $seedFile = $install->{'configuration'}->{'configuration seed file'};
      if ( -e $install->{'configuration seed file'} ) {
         push @fileList, $install->{'configuration seed file'};
      } # if preload seed file

      $config = &makeConfig( @fileList );
      # if ScriptDirs and moduleDirs not populated, do so from our configuration
      if ( ! $$config{'scriptDirs'} || ! scalar @{ $$config{'scriptDirs'} }  ) {
         $config->{'scriptDirs'} = [ $install->{'files'}->{'scripts'}->{'target'} ];
      }
      if ( ! $$config{'moduleDirs'} || ! @{ $$config{'moduleDirs'} }  ) {
         $config->{'moduleDirs'} = [ $install->{'files'}->{'modules'}->{'target'} ];
      }
      my $content = &showConf( $config );
      return &writeConfig( '' , $content );
   } # if we are building/merging configuration
}

# installs binaries and libraries
sub installOSStuff {
   my $install = shift;
   my @actions = values( %{ $install->{'missing libraries'} } );
   push @actions, values( %{ $install->{'missing binaries'} } );
   foreach my $action ( @actions ) {
      print "$action\n";
      &runCommand( $action );
   }
}
   

################################################################
#               Main Code                                      #
################################################################

# handle any command line parameters that may have been passed in

GetOptions (
            "verbose|v=i" => \$verbose, # verbosity level, 0-9
            "os|o=s"      => \$os,      # pass in the operating system
            "dryrun|n"    => \$dryRun,  # do NOT actually do anything
            'help|h'      => \$help,
            'version|V'   => \$version
            ) or die "Error parsing command line\n";
                  
if ( $help ) { &help() ; exit; }
if ( $version ) { print "$0 version $VERSION\n"; exit; }

$install{'os'} = &setUpOperatingSystemSpecific( \%install, \%operatingSystems, $os ? $os : `$sourceDir/determineOS`, $sourceDir );
$install{'missing libraries'} = &validateLibraries( \%libraries, $install{'os'} );
$install{'missing binaries'} = &validateBinaries( \%binaries, $install{'os'} );
&getInstallActions( \%install );
populateSourceDir( \%install, $sourceDir );

if ( &GetPermission( \%install ) ) {
   &installOSStuff( \%install );
   &getVersions( \%install ) unless $install{'action'} eq 'new';
#   print Dump( \%install );
#   <>;
   &doInstall( \%install );
} else {
   die "Please fix whatever needs to be done and try again\n";
}

my $filename = &postInstall( \%install );
my $confFileName = $install{'configuration'}{'configuration file'};
print "Running configure.pl with $filename, output to $confFileName\n";

exec( "$install{bindir}/configure.pl -f $filename -o $confFileName" );


########### ADD LOGGING

Generated by GNU Enscript 1.6.5.90.