Subversion Repositories camp_sysinfo_client_3

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
101 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
5
 
132 rodolico 6
# install.pl
7
#
8
# installer for perl script, in this case, sysinfo
9
#
10
# Revision history
11
#
12
# Version 1.1.7 20161010 RWR
13
# Added ability to validate required libraries are installed
14
#
15
# version 1.2 20170327 RWR
16
# did some major modifications to correctly work on BSD systems also
17
#
18
# version 2.0 20190330 RWR
19
# changed it so all configs are YAML
20
#
21
# version 3.0 20191105 RWR
22
# set up so all options are presented on initial screen
23
# user can choose options to edit
24
# rest of install is automatic
138 rodolico 25
#
26
# version 3.1 20191112 RWR
27
# Added logging. Log file will be written to the install directory with
28
# the application name.log as the name (application name taken from installer_config.pl)
144 rodolico 29
#
30
# version 3.1.1 20191117 RWR
31
# Added the ability to verify cpan is installed and configured on systems which need it
32
#
101 rodolico 33
 
144 rodolico 34
our $VERSION = '3.1.1';
132 rodolico 35
 
36
# find our location and use it for searching for libraries
37
BEGIN {
38
   use FindBin;
39
   use File::Spec;
40
   use lib File::Spec->catdir($FindBin::Bin);
41
   eval( 'use YAML::Tiny' );
42
}
43
 
101 rodolico 44
my $sourceDir = File::Spec->catdir($FindBin::Bin);
45
 
132 rodolico 46
use Data::Dumper;
47
use File::Basename;
48
use Getopt::Long;
101 rodolico 49
 
132 rodolico 50
our %install;
51
our %operatingSystems;
52
our %libraries;
53
our %binaries;
54
 
55
do "$sourceDir/installer_config.pl";
138 rodolico 56
#
57
# set up log file
58
my $logFile = $install{'application name'};
59
$logFile =~ s/ /_/g;
60
$logFile = "$sourceDir/$logFile.log";
139 rodolico 61
print "Logging to $logFile\n";
132 rodolico 62
 
138 rodolico 63
 
64
 
132 rodolico 65
Getopt::Long::Configure ("bundling"); # allow -vd --os='debian'
66
my $dryRun = 0;
67
my $os;
68
my $help = 0;
69
my $version = 0;
70
 
71
my @messages; # stores any messages we want to show up at the end
72
my @feedback; # store feedback when we execute command line actions
73
 
74
my %configuration;
75
 
76
# simple display if --help is passed
77
sub help {
78
   my $oses = join( ' ', keys %operatingSystems );
79
   print <<END
80
$0 --verbose=x --os="osname" --dryrun --help --version
81
 
82
--os      - osname is one of [$oses]
83
--dryrun  - do not actually do anything, just tell you what I'd do
138 rodolico 84
--version - display version and exit
132 rodolico 85
END
101 rodolico 86
}
87
 
138 rodolico 88
#######################################################
144 rodolico 89
#
90
# timeStamp
91
#
92
# return current system date as YYYY-MM-DD HH:MM:SS
93
#
94
#######################################################
95
sub timeStamp {
96
   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
97
   return sprintf "%4d-%02d-%02d %02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec;
98
}
99
 
100
sub yesno {
101
   my ( $prompt, $default ) = @_;
102
   $default = 'yes' unless $default;
103
   my $answer = &getAnswer( $prompt, $default eq 'yes' ? ('yes','no' ) : ('no', 'yes') );
104
   return lc( substr( $answer, 0, 1 ) ) eq 'y';
105
}
106
 
107
# prompt the user for a response, then allow them to enter it
108
# the first response is considered the default and is printed
109
# in all caps if more than one exists
110
# first answer is used if they simply press the Enter
111
# key. The response is returned all lower case if more than one
112
# exists.
113
# it is assumed 
114
sub getAnswer {
115
   my ( $prompt, @answers ) = @_;
116
   $answers[0] = '' unless defined( $answers[0] );
117
   my $default = $answers[0];
118
   my $onlyOneAnswer = scalar( @answers ) == 1;
119
   print $prompt . '[ ';
120
   $answers[0] = uc $answers[0] unless $onlyOneAnswer;
121
   print join( ' | ', @answers ) . ' ]: ';
122
   my $thisAnswer = <>;
123
   chomp $thisAnswer;
124
   $thisAnswer = $default unless $thisAnswer;
125
   return $thisAnswer;
126
}
127
 
128
 
129
#######################################################
138 rodolico 130
# function to simply log things
131
# first parameter is the priority, if <= $logDef->{'log level'} will print
132
# all subsequent parameters assumed to be strings to sent to the log
133
# returns 0 on failure
134
#         1 on success
135
#         2 if priority > log level
136
#        -1 if $logDef is unset
137
# currently, only logs to a file
138
#######################################################
139
sub logIt {
140 rodolico 140
   open LOG, ">>$logFile" or die "Could not append to $logFile: $!\n";
138 rodolico 141
   while ( my $t = shift ) {
142
      print LOG &timeStamp() . "\t$t\n";
143
   }
144
   close LOG;
145
   return 1;
146
}
101 rodolico 147
 
138 rodolico 148
 
132 rodolico 149
# attempt to locate the operating system.
150
# if found, will set some defaults for it.
151
sub setUpOperatingSystemSpecific {
152
   my ( $install, $operatingSystems, $os, $installDir ) = @_;
138 rodolico 153
   &logIt( 'Entering setUpOperatingSystemSpecific' );
154
   &logIt( "They passed $os in as the \$os" );
132 rodolico 155
   if ( $os ) {
156
      # We found the OS, set up some defaults
157
      $$install{'os'} = $os;
138 rodolico 158
      &logIt( "Setting keys for operating system" );
132 rodolico 159
      # merge operatingSystems into install
160
      foreach my $key ( keys %{$operatingSystems->{$os}} ) {
161
         if ( $key eq 'files' ) {
162
            $install->{'files'} = { %{$install->{'files'}}, %{$operatingSystems->{$os}->{'files'}} }
163
         } else {
164
            $install->{$key} = $operatingSystems->{ $os }->{$key};
165
         }
166
      } # if it is a known OS
167
   } # if
168
   return $os;
169
} # getOperatingSystem
170
 
171
# validates the libraries needed exist
172
# simply eval's each library. If it doesn't exist, creates a list of
173
# commands to be executed to install it, and displays missing libraries
174
# offering to install them if possible.
175
sub validateLibraries {
176
   my ( $libraries, $os ) = @_;
138 rodolico 177
   &logIt( 'Entering validateLibraries' );
132 rodolico 178
   my %return;
179
   foreach my $lib ( keys %$libraries ) {
138 rodolico 180
      &logIt( "Checking on library $lib" );
132 rodolico 181
      eval( "use $lib;" );
182
      if ( $@ ) {
183
         $return{ $lib } = $libraries->{$lib}->{$os} ? $libraries->{$lib}->{$os} : 'UNK';
184
      }
185
   }
186
   return \%return;
187
} # validateLibraries
188
 
189
 
190
# check for any required binaries
191
sub validateBinaries {
192
   my ( $binaries, $os ) = @_;
138 rodolico 193
   &logIt( 'Entering validateBinaries' );
132 rodolico 194
   my %return;
195
   foreach my $bin ( keys %$binaries ) {
196
      unless ( `which $bin` ) {
197
         $return{$bin} = $binaries->{$bin}->{$os} ? $binaries->{$bin}->{$os} : 'UNK';
198
      }
199
   }
200
   return \%return;
201
} # validateBinaries
202
 
203
# get some input from the user and decide how to install/upgrade/remove/whatever
204
sub getInstallActions {
205
   my $install = shift;
138 rodolico 206
   &logIt( 'Entering getInstallActions' );
132 rodolico 207
   if ( -d $install->{'confdir'} ) {
208
      $install->{'action'} = "upgrade";
209
   } else {
210
      $install->{'action'} = 'install';
211
   }
212
   $install->{'build config'} = 'Y';
213
   $install->{'setup cron'} = 'Y';
101 rodolico 214
}
215
 
132 rodolico 216
# locate all items in $hash which have one of the $placeholder elements in it
217
# and replace, ie <binddir> is replaced with the actual binary directory
218
sub doPlaceholderSubstitution {
219
   my ($hash, $placeholder) = @_;
138 rodolico 220
   &logIt( 'Entering doPlaceholderSubstitution' );
132 rodolico 221
   return if ref $hash ne 'HASH';
222
   foreach my $key ( keys %$hash ) {
223
      if ( ref( $$hash{$key} ) ) {
224
         &doPlaceholderSubstitution( $$hash{$key}, $placeholder );
225
      } else {
226
         foreach my $place ( keys %$placeholder ) {
227
            $$hash{$key} =~ s/$place/$$placeholder{$place}/;
228
         } # foreach
229
      } # if..else
230
   } # foreach
231
   return;
232
}
233
 
234
# This will go through and first, see if anything is a directory, in
235
# which case, we'll create new entries for all files in there.
236
# then, it will do keyword substitution of <bindir> and <confdir>
237
# to populate the target.
238
# When this is done, each file should have a source and target that is
239
# a fully qualified path and filename
240
sub populateSourceDir {
241
   my ( $install, $sourceDir ) = @_;
138 rodolico 242
   &logIt( 'Entering populateSourceDir' );
132 rodolico 243
   my %placeHolders = 
244
      ( 
245
        '<bindir>' => $$install{'bindir'},
246
        '<confdir>' => $$install{'confdir'},
247
        '<default owner>' => $$install{'default owner'},
248
        '<default group>' => $$install{'default group'},
249
        '<default permission>' => $$install{'default permission'},
250
        '<installdir>' => $sourceDir
251
      );
101 rodolico 252
 
132 rodolico 253
   my $allFiles = $$install{'files'};
101 rodolico 254
 
132 rodolico 255
   # find all directory entries and load files in that directory into $$install{'files'}
256
   foreach my $dir ( keys %$allFiles ) {
257
      if ( defined( $$allFiles{$dir}{'type'} ) && $$allFiles{$dir}{'type'} eq 'directory' ) {
138 rodolico 258
         &logIt( "Found directory $dir" );
132 rodolico 259
         if ( opendir( my $dh, "$sourceDir/$dir" ) ) {
260
            my @files = map{ $dir . '/' . $_ } grep { ! /^\./ && -f "$sourceDir/$dir/$_" } readdir( $dh );
138 rodolico 261
            &logIt( "\tFound files " . join( ' ', @files ) );
132 rodolico 262
            foreach my $file ( @files ) {
263
               $$allFiles{ $file }{'type'} = 'file';
264
               if ( $dir eq 'modules' ) {
265
                  $$allFiles{ $file }{'permission'} = ( $file =~ m/$$install{'modules'}/ ) ? '0700' : '0600';
266
               } else {
267
                  $$allFiles{ $file }{'permission'} = $$allFiles{ $dir }{'permission'};
268
               }
269
               $$allFiles{ $file }{'owner'} = $$allFiles{ $dir }{'owner'};
270
               $$allFiles{ $file }{'target'} = $$allFiles{ $dir }{'target'};
271
            } # foreach
272
            closedir $dh;
273
         } # if opendir
274
      } # if it is a directory
275
   } # foreach
276
   # find all files, and set the source directory, and add the filename to
277
   # the target
278
   foreach my $file ( keys %$allFiles ) {
279
      $$allFiles{$file}{'source'} = "$sourceDir/$file";
280
      $$allFiles{$file}{'target'} .= "/$file";
281
   } # foreach
101 rodolico 282
 
132 rodolico 283
   # finally, do place holder substitution. This recursively replaces all keys
284
   # in  %placeHolders with the values.
285
   &doPlaceholderSubstitution( $install, \%placeHolders );
101 rodolico 286
 
144 rodolico 287
   &logIt( "Exiting populateSourceDir with values\n" . Dumper( $install ) ) ;
101 rodolico 288
 
132 rodolico 289
   return 1;
290
} # populateSourceDir
291
 
292
sub GetPermission {
293
   my $install = shift;
138 rodolico 294
   &logIt( 'Entering GetPermission' );
132 rodolico 295
   print "Ready to install, please verify the following\n";
296
   print "A. Operating System:  " . $install->{'os'} . "\n";
297
   print "B. Installation Type: " . $install->{'action'} . "\n";
298
   print "C. Using Seed file: " . $install->{'configuration seed file'} . "\n" if -e $install->{'configuration seed file'};
299
   print "D. Target Binary Directory: " . $install->{'bindir'} . "\n";
300
   print "E. Target Configuration Directory: " . $install->{'confdir'} . "\n";
301
   print "F. Automatic Running: " . ( $install->{'crontab'} ? $install->{'crontab'} : 'No' ) . "\n";
302
   print "G. Install Missing Perl Libraries\n";
303
   foreach my $task ( sort keys %{ $install->{'missing libraries'} } ) {
144 rodolico 304
      print "\t$task -> " . $install->{'missing libraries'}->{$task}->{'command'} . ' ' . $install->{'missing libraries'}->{$task}->{'parameter'} . "\n";
101 rodolico 305
   }
132 rodolico 306
   print "H. Install Missing Binaries\n";
307
   foreach my $task ( sort keys %{ $install->{'missing binaries'} } ) {
144 rodolico 308
      print "\t$task -> " . $install->{'missing binaries'}->{$task}->{'command'} . ' ' . $install->{'missing binaries'}->{$task}->{'parameter'} . "\n";
132 rodolico 309
   }
310
   return &yesno( "Do you want to proceed?" );
101 rodolico 311
}
312
 
132 rodolico 313
# note, this fails badly if you stick non-numerics in the version number
314
# simply create a "number" from the version which may have an arbitrary
315
# number of digits separated by a period, for example
316
# 1.2.5
317
# while there are digits, divide current calculation by 100, then add the last
318
# digit popped of. So, 1.2.5 would become
319
# 1 + 2/100 + 5/10000, or 1.0205
320
# and 1.25.16 would become 1.2516
321
# 
322
# Will give invalid results if any set of digits is more than 99
323
sub dotVersion2Number {
324
   my $dotVersion = shift;
325
 
326
   my @t = split( '\.', $dotVersion );
327
   #print "\n$dotVersion\n" . join( "\n", @t ) . "\n";
328
   my $return = 0;
329
   while ( @t ) {
330
      $return /= 100;
331
      $return += pop @t;
332
   }
333
   #print "$return\n";
334
   return $return;
335
}
336
 
337
# there is a file named VERSIONS. We get the values out of the install
338
# directory and (if it exists) the target so we can decide what needs
339
# to be updated.
340
sub getVersions {
341
   my $install = shift;
138 rodolico 342
   &logIt( 'Entering getVersions' );
132 rodolico 343
   my $currentVersionFile = $install->{'files'}->{'VERSION'}->{'target'};
344
   my $newVersionFile = $install->{'files'}->{'VERSION'}->{'source'};
345
   if ( open FILE,"<$currentVersionFile" ) {
346
      while ( my $line = <FILE> ) {
347
         chomp $line;
348
         my ( $filename, $version, $checksum ) = split( "\t", $line );
349
         $install{'files'}->{$filename}->{'installed version'} = $version ? $version : '';
350
      }
351
      close FILE;
352
   }
353
   if ( open FILE,"<$newVersionFile" ) {
354
      while ( my $line = <FILE> ) {
355
         chomp $line;
356
         my ( $filename, $version, $checksum ) = split( "\t", $line );
357
         $install->{'files'}->{$filename}->{'new version'} = $version ? $version : '';
358
      }
359
      close FILE;
360
   }
361
   foreach my $file ( keys %{$$install{'files'}} ) {
362
      $install{'files'}->{$file}->{'installed version'} = -2 unless defined $install->{'files'}->{$file}->{'installed version'};
363
      $install{'files'}->{$file}->{'new version'} = -1 unless defined $install->{'files'}->{$file}->{'new version'};
364
   }
365
   return 1;
366
} # getVersions
367
 
144 rodolico 368
# checks if a directory exists and, if not, creates it
369
my %directories; # holds list of directories already created so no need to do an I/O
132 rodolico 370
 
144 rodolico 371
sub checkDirectoryExists {
372
   my ( $filename,$mod,$owner ) = @_;
373
   $mod = "0700" unless $mod;
374
   $owner = "root:root" unless $owner;
375
   &logIt( "Checking Directory for $filename with $mod and $owner" );
376
   my ($fn, $dirname) = fileparse( $filename );
377
   logIt( "\tParsing out $dirname and $filename" );
378
   return '' if exists $directories{$dirname};
379
   if ( -d $dirname ) {
380
      $directories{$dirname} = 1;
381
      return '';
382
   }
383
   if ( &runCommand( "mkdir -p $dirname", "chmod $mod $dirname", "chown $owner $dirname" ) ) {
384
      $directories{$dirname} = 1;
385
   }
386
   return '';   
387
}
388
 
389
# runs a system command. Also, if in testing mode, simply shows what
390
# would have been done.
391
sub runCommand {
392
   while ( my $command = shift ) {
393
      if ( $dryRun ) {
394
         print "$command\n";
395
      } else {
396
         `$command`;
397
      }
398
   }
399
   return 1;
400
} # runCommand
401
 
402
 
403
 
404
 
132 rodolico 405
# this actually does the installation, except for the configuration
406
sub doInstall {
407
   my $install = shift;
138 rodolico 408
   &logIt( 'Entering doInstall' );
132 rodolico 409
   my $fileList = $install->{'files'};
410
 
411
   &checkDirectoryExists( $install->{'bindir'} . '/', $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
412
   foreach my $file ( keys %$fileList ) {
413
      next unless ( $fileList->{$file}->{'type'} && $fileList->{$file}->{'type'} eq 'file' );
414
 
415
      next if $install->{'action'} eq 'upgrade' && ! defined( $fileList->{$file}->{'installed version'} )
416
              ||
417
              ( &dotVersion2Number( $fileList->{$file}->{'new version'} ) <= &dotVersion2Number($fileList->{$file}->{'installed version'} ) );
418
      &checkDirectoryExists( $fileList->{$file}->{'target'}, $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
419
      &runCommand( 
420
            "cp $fileList->{$file}->{'source'} $fileList->{$file}->{'target'}",
421
            "chmod $fileList->{$file}->{'permission'} $fileList->{$file}->{'target'}",
422
            "chown $fileList->{$file}->{'owner'} $fileList->{$file}->{'target'}"
423
            );
424
      # if there is a post action, run it and store any return in @feedback
425
      push @feedback, `$fileList->{$file}->{'post action'}` if defined $fileList->{$file}->{'post action'};
426
      # if there is a message to be passed, store it in @messages
427
      push @messages, $fileList->{$file}->{'meesage'} if defined $fileList->{$file}->{'message'};
428
   } # foreach file
429
   # set up crontab, if necessary
430
   &runCommand( $install->{'crontab'} ) if defined ( $install->{'crontab'} );
431
   return 1;
432
}
433
 
434
 
435
# installs binaries and libraries
436
sub installOSStuff {
437
   my $install = shift;
144 rodolico 438
   my %commands;
132 rodolico 439
   my @actions = values( %{ $install->{'missing libraries'} } );
440
   push @actions, values( %{ $install->{'missing binaries'} } );
441
   foreach my $action ( @actions ) {
144 rodolico 442
      $commands{$action->{'command'}} .= ' ' . $action->{'parameter'};
443
      #&logIt( $action );
444
      #&runCommand( $action );
132 rodolico 445
   }
144 rodolico 446
   foreach my $command ( keys %commands ) {
447
      print "Running command $command $commands{$command}\n";
448
      `$command $commands{$command}`;
449
   }
132 rodolico 450
}
144 rodolico 451
 
452
################################################################
453
# validateCPAN
454
#
455
# some of the systems will need to run cpan to get some perl modules.
456
# this will go through each of them and see if command starts with cpan
457
# and, if so, will check that cpan is installed and configured for root.
458
#
459
# when cpan is installed, it requires one manual run as root from the cli
460
# to determine where to get the files. If that is not done, cpan can not
461
# be controlled by a program. We check to see if cpan is installed, then
462
# verify /root/.cpan has been created by the configuration tool
463
################################################################
464
 
465
 
466
sub validateCPAN {
467
   my $libraries = shift;
468
   my $needCPAN = 0;
469
   foreach my $app ( keys %$libraries ) {
470
      if ( $libraries->{$app}->{'command'} =~ m/^cpan/ ) {
471
         $needCPAN = 1;
472
         last;
473
      }
474
   }
475
   return unless $needCPAN;
476
   if ( `which cpan` ) {
477
      die "****ERROR****\nWe need cpan, and it is installed, but not configured\nrun cpan as root one time to configure it\n" unless -d '/root/.cpan';
478
   } else {
479
      die 'In order to install on this OS, we need cpan, which should have been installed with perl.' .
480
          " Can not continue until cpan is installed and configured\n";
481
   }
482
}   
132 rodolico 483
 
484
 
485
################################################################
486
#               Main Code                                      #
487
################################################################
488
 
489
# handle any command line parameters that may have been passed in
490
 
491
GetOptions (
492
            "os|o=s"      => \$os,      # pass in the operating system
493
            "dryrun|n"    => \$dryRun,  # do NOT actually do anything
494
            'help|h'      => \$help,
138 rodolico 495
            'version|v'   => \$version
132 rodolico 496
            ) or die "Error parsing command line\n";
497
 
498
if ( $help ) { &help() ; exit; }
499
if ( $version ) { print "$0 version $VERSION\n"; exit; }
500
 
138 rodolico 501
&logIt( 'Beginning installation' );
502
 
132 rodolico 503
$install{'os'} = &setUpOperatingSystemSpecific( \%install, \%operatingSystems, $os ? $os : `$sourceDir/determineOS`, $sourceDir );
138 rodolico 504
&logIt( "Operating System is $install{'os'}" );
505
 
132 rodolico 506
$install{'missing libraries'} = &validateLibraries( \%libraries, $install{'os'} );
144 rodolico 507
&logIt( "Missing Libraries\n" . Dumper( $install{'missing libraries'} ) );
138 rodolico 508
 
132 rodolico 509
$install{'missing binaries'} = &validateBinaries( \%binaries, $install{'os'} );
144 rodolico 510
&logIt( "Missing binaries\n" . Dumper( $install{'missing binaries'} ) );
138 rodolico 511
 
144 rodolico 512
if ( $install{'missing libraries'} ) {
513
   &validateCPAN( $install{'missing libraries'} );
514
}
515
 
132 rodolico 516
&getInstallActions( \%install );
144 rodolico 517
&logIt( "Completed getInstallActions\n" .  Dumper( \%install ) );
138 rodolico 518
 
144 rodolico 519
&populateSourceDir( \%install, $sourceDir );
132 rodolico 520
 
521
if ( &GetPermission( \%install ) ) {
522
   &installOSStuff( \%install );
523
   &getVersions( \%install ) unless $install{'action'} eq 'new';
524
   &doInstall( \%install );
525
} else {
526
   die "Please fix whatever needs to be done and try again\n";
138 rodolico 527
   &logIt( "User chose to kill process" );
132 rodolico 528
}
529
 
144 rodolico 530
&logIt( "Installation done, running \&postInstall if it exists" );
132 rodolico 531
 
144 rodolico 532
&postInstall( \%install )if defined( &postInstall );
533
 
534
1;