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.