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)
101 rodolico 29
 
132 rodolico 30
our $VERSION = '3.0';
31
 
32
# find our location and use it for searching for libraries
33
BEGIN {
34
   use FindBin;
35
   use File::Spec;
36
   use lib File::Spec->catdir($FindBin::Bin);
37
   eval( 'use YAML::Tiny' );
38
}
39
 
101 rodolico 40
my $sourceDir = File::Spec->catdir($FindBin::Bin);
41
 
132 rodolico 42
use sysinfoconf;
43
use Data::Dumper;
44
use File::Basename;
45
use Getopt::Long;
101 rodolico 46
 
132 rodolico 47
our %install;
48
our %operatingSystems;
49
our %libraries;
50
our %binaries;
51
 
52
do "$sourceDir/installer_config.pl";
138 rodolico 53
#
54
# set up log file
55
my $logFile = $install{'application name'};
56
$logFile =~ s/ /_/g;
57
$logFile = "$sourceDir/$logFile.log";
139 rodolico 58
print "Logging to $logFile\n";
132 rodolico 59
 
138 rodolico 60
 
61
 
132 rodolico 62
Getopt::Long::Configure ("bundling"); # allow -vd --os='debian'
63
my $dryRun = 0;
64
my $os;
65
my $help = 0;
66
my $version = 0;
67
 
68
my @messages; # stores any messages we want to show up at the end
69
my @feedback; # store feedback when we execute command line actions
70
 
71
my %configuration;
72
 
73
# simple display if --help is passed
74
sub help {
75
   my $oses = join( ' ', keys %operatingSystems );
76
   print <<END
77
$0 --verbose=x --os="osname" --dryrun --help --version
78
 
79
--os      - osname is one of [$oses]
80
--dryrun  - do not actually do anything, just tell you what I'd do
138 rodolico 81
--version - display version and exit
132 rodolico 82
END
101 rodolico 83
}
84
 
138 rodolico 85
#######################################################
86
# function to simply log things
87
# first parameter is the priority, if <= $logDef->{'log level'} will print
88
# all subsequent parameters assumed to be strings to sent to the log
89
# returns 0 on failure
90
#         1 on success
91
#         2 if priority > log level
92
#        -1 if $logDef is unset
93
# currently, only logs to a file
94
#######################################################
95
sub logIt {
140 rodolico 96
   open LOG, ">>$logFile" or die "Could not append to $logFile: $!\n";
138 rodolico 97
   while ( my $t = shift ) {
98
      print LOG &timeStamp() . "\t$t\n";
99
   }
100
   close LOG;
101
   return 1;
102
}
101 rodolico 103
 
138 rodolico 104
 
132 rodolico 105
# attempt to locate the operating system.
106
# if found, will set some defaults for it.
107
sub setUpOperatingSystemSpecific {
108
   my ( $install, $operatingSystems, $os, $installDir ) = @_;
138 rodolico 109
   &logIt( 'Entering setUpOperatingSystemSpecific' );
110
   &logIt( "They passed $os in as the \$os" );
132 rodolico 111
   if ( $os ) {
112
      # We found the OS, set up some defaults
113
      $$install{'os'} = $os;
138 rodolico 114
      &logIt( "Setting keys for operating system" );
132 rodolico 115
      # merge operatingSystems into install
116
      foreach my $key ( keys %{$operatingSystems->{$os}} ) {
117
         if ( $key eq 'files' ) {
118
            $install->{'files'} = { %{$install->{'files'}}, %{$operatingSystems->{$os}->{'files'}} }
119
         } else {
120
            $install->{$key} = $operatingSystems->{ $os }->{$key};
121
         }
122
      } # if it is a known OS
123
   } # if
124
   return $os;
125
} # getOperatingSystem
126
 
127
# validates the libraries needed exist
128
# simply eval's each library. If it doesn't exist, creates a list of
129
# commands to be executed to install it, and displays missing libraries
130
# offering to install them if possible.
131
sub validateLibraries {
132
   my ( $libraries, $os ) = @_;
138 rodolico 133
   &logIt( 'Entering validateLibraries' );
132 rodolico 134
   my %return;
135
   foreach my $lib ( keys %$libraries ) {
138 rodolico 136
      &logIt( "Checking on library $lib" );
132 rodolico 137
      eval( "use $lib;" );
138
      if ( $@ ) {
139
         $return{ $lib } = $libraries->{$lib}->{$os} ? $libraries->{$lib}->{$os} : 'UNK';
140
      }
141
   }
142
   return \%return;
143
} # validateLibraries
144
 
145
 
146
# check for any required binaries
147
sub validateBinaries {
148
   my ( $binaries, $os ) = @_;
138 rodolico 149
   &logIt( 'Entering validateBinaries' );
132 rodolico 150
   my %return;
151
   foreach my $bin ( keys %$binaries ) {
152
      unless ( `which $bin` ) {
153
         $return{$bin} = $binaries->{$bin}->{$os} ? $binaries->{$bin}->{$os} : 'UNK';
154
      }
155
   }
156
   return \%return;
157
} # validateBinaries
158
 
159
# get some input from the user and decide how to install/upgrade/remove/whatever
160
sub getInstallActions {
161
   my $install = shift;
138 rodolico 162
   &logIt( 'Entering getInstallActions' );
132 rodolico 163
   if ( -d $install->{'confdir'} ) {
164
      $install->{'action'} = "upgrade";
165
   } else {
166
      $install->{'action'} = 'install';
167
   }
168
   $install->{'build config'} = 'Y';
169
   $install->{'setup cron'} = 'Y';
101 rodolico 170
}
171
 
132 rodolico 172
# locate all items in $hash which have one of the $placeholder elements in it
173
# and replace, ie <binddir> is replaced with the actual binary directory
174
sub doPlaceholderSubstitution {
175
   my ($hash, $placeholder) = @_;
138 rodolico 176
   &logIt( 'Entering doPlaceholderSubstitution' );
132 rodolico 177
   return if ref $hash ne 'HASH';
178
   foreach my $key ( keys %$hash ) {
179
      if ( ref( $$hash{$key} ) ) {
180
         &doPlaceholderSubstitution( $$hash{$key}, $placeholder );
181
      } else {
182
         foreach my $place ( keys %$placeholder ) {
183
            $$hash{$key} =~ s/$place/$$placeholder{$place}/;
184
         } # foreach
185
      } # if..else
186
   } # foreach
187
   return;
188
}
189
 
190
# This will go through and first, see if anything is a directory, in
191
# which case, we'll create new entries for all files in there.
192
# then, it will do keyword substitution of <bindir> and <confdir>
193
# to populate the target.
194
# When this is done, each file should have a source and target that is
195
# a fully qualified path and filename
196
sub populateSourceDir {
197
   my ( $install, $sourceDir ) = @_;
138 rodolico 198
   &logIt( 'Entering populateSourceDir' );
132 rodolico 199
   my %placeHolders = 
200
      ( 
201
        '<bindir>' => $$install{'bindir'},
202
        '<confdir>' => $$install{'confdir'},
203
        '<default owner>' => $$install{'default owner'},
204
        '<default group>' => $$install{'default group'},
205
        '<default permission>' => $$install{'default permission'},
206
        '<installdir>' => $sourceDir
207
      );
101 rodolico 208
 
132 rodolico 209
   my $allFiles = $$install{'files'};
101 rodolico 210
 
132 rodolico 211
   # find all directory entries and load files in that directory into $$install{'files'}
212
   foreach my $dir ( keys %$allFiles ) {
213
      if ( defined( $$allFiles{$dir}{'type'} ) && $$allFiles{$dir}{'type'} eq 'directory' ) {
138 rodolico 214
         &logIt( "Found directory $dir" );
132 rodolico 215
         if ( opendir( my $dh, "$sourceDir/$dir" ) ) {
216
            my @files = map{ $dir . '/' . $_ } grep { ! /^\./ && -f "$sourceDir/$dir/$_" } readdir( $dh );
138 rodolico 217
            &logIt( "\tFound files " . join( ' ', @files ) );
132 rodolico 218
            foreach my $file ( @files ) {
219
               $$allFiles{ $file }{'type'} = 'file';
220
               if ( $dir eq 'modules' ) {
221
                  $$allFiles{ $file }{'permission'} = ( $file =~ m/$$install{'modules'}/ ) ? '0700' : '0600';
222
               } else {
223
                  $$allFiles{ $file }{'permission'} = $$allFiles{ $dir }{'permission'};
224
               }
225
               $$allFiles{ $file }{'owner'} = $$allFiles{ $dir }{'owner'};
226
               $$allFiles{ $file }{'target'} = $$allFiles{ $dir }{'target'};
227
            } # foreach
228
            closedir $dh;
229
         } # if opendir
230
      } # if it is a directory
231
   } # foreach
232
   # find all files, and set the source directory, and add the filename to
233
   # the target
234
   foreach my $file ( keys %$allFiles ) {
235
      $$allFiles{$file}{'source'} = "$sourceDir/$file";
236
      $$allFiles{$file}{'target'} .= "/$file";
237
   } # foreach
101 rodolico 238
 
132 rodolico 239
   # finally, do place holder substitution. This recursively replaces all keys
240
   # in  %placeHolders with the values.
241
   &doPlaceholderSubstitution( $install, \%placeHolders );
101 rodolico 242
 
138 rodolico 243
   &logIt( "Exiting populateSourceDir with values\n" . Data::Dumper->Dump([$install],[qw($install)]) ) ;
101 rodolico 244
 
132 rodolico 245
   return 1;
246
} # populateSourceDir
247
 
248
sub GetPermission {
249
   my $install = shift;
138 rodolico 250
   &logIt( 'Entering GetPermission' );
132 rodolico 251
   print "Ready to install, please verify the following\n";
252
   print "A. Operating System:  " . $install->{'os'} . "\n";
253
   print "B. Installation Type: " . $install->{'action'} . "\n";
254
   print "C. Using Seed file: " . $install->{'configuration seed file'} . "\n" if -e $install->{'configuration seed file'};
255
   print "D. Target Binary Directory: " . $install->{'bindir'} . "\n";
256
   print "E. Target Configuration Directory: " . $install->{'confdir'} . "\n";
257
   print "F. Automatic Running: " . ( $install->{'crontab'} ? $install->{'crontab'} : 'No' ) . "\n";
258
   print "G. Install Missing Perl Libraries\n";
259
   foreach my $task ( sort keys %{ $install->{'missing libraries'} } ) {
260
      print "\t$task -> " . $install->{'missing libraries'}->{$task} . "\n";
101 rodolico 261
   }
132 rodolico 262
   print "H. Install Missing Binaries\n";
263
   foreach my $task ( sort keys %{ $install->{'missing binaries'} } ) {
264
      print "\t$task -> " . $install->{'missing binaries'}->{$task} . "\n";
265
   }
266
   return &yesno( "Do you want to proceed?" );
101 rodolico 267
}
268
 
132 rodolico 269
# note, this fails badly if you stick non-numerics in the version number
270
# simply create a "number" from the version which may have an arbitrary
271
# number of digits separated by a period, for example
272
# 1.2.5
273
# while there are digits, divide current calculation by 100, then add the last
274
# digit popped of. So, 1.2.5 would become
275
# 1 + 2/100 + 5/10000, or 1.0205
276
# and 1.25.16 would become 1.2516
277
# 
278
# Will give invalid results if any set of digits is more than 99
279
sub dotVersion2Number {
280
   my $dotVersion = shift;
281
 
282
   my @t = split( '\.', $dotVersion );
283
   #print "\n$dotVersion\n" . join( "\n", @t ) . "\n";
284
   my $return = 0;
285
   while ( @t ) {
286
      $return /= 100;
287
      $return += pop @t;
288
   }
289
   #print "$return\n";
290
   return $return;
291
}
292
 
293
# there is a file named VERSIONS. We get the values out of the install
294
# directory and (if it exists) the target so we can decide what needs
295
# to be updated.
296
sub getVersions {
297
   my $install = shift;
138 rodolico 298
   &logIt( 'Entering getVersions' );
132 rodolico 299
   my $currentVersionFile = $install->{'files'}->{'VERSION'}->{'target'};
300
   my $newVersionFile = $install->{'files'}->{'VERSION'}->{'source'};
301
   if ( open FILE,"<$currentVersionFile" ) {
302
      while ( my $line = <FILE> ) {
303
         chomp $line;
304
         my ( $filename, $version, $checksum ) = split( "\t", $line );
305
         $install{'files'}->{$filename}->{'installed version'} = $version ? $version : '';
306
      }
307
      close FILE;
308
   }
309
   if ( open FILE,"<$newVersionFile" ) {
310
      while ( my $line = <FILE> ) {
311
         chomp $line;
312
         my ( $filename, $version, $checksum ) = split( "\t", $line );
313
         $install->{'files'}->{$filename}->{'new version'} = $version ? $version : '';
314
      }
315
      close FILE;
316
   }
317
   foreach my $file ( keys %{$$install{'files'}} ) {
318
      $install{'files'}->{$file}->{'installed version'} = -2 unless defined $install->{'files'}->{$file}->{'installed version'};
319
      $install{'files'}->{$file}->{'new version'} = -1 unless defined $install->{'files'}->{$file}->{'new version'};
320
   }
321
   return 1;
322
} # getVersions
323
 
324
 
325
# this actually does the installation, except for the configuration
326
sub doInstall {
327
   my $install = shift;
138 rodolico 328
   &logIt( 'Entering doInstall' );
132 rodolico 329
   my $fileList = $install->{'files'};
330
 
331
   &checkDirectoryExists( $install->{'bindir'} . '/', $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
332
   foreach my $file ( keys %$fileList ) {
333
      next unless ( $fileList->{$file}->{'type'} && $fileList->{$file}->{'type'} eq 'file' );
334
 
335
      next if $install->{'action'} eq 'upgrade' && ! defined( $fileList->{$file}->{'installed version'} )
336
              ||
337
              ( &dotVersion2Number( $fileList->{$file}->{'new version'} ) <= &dotVersion2Number($fileList->{$file}->{'installed version'} ) );
338
      &checkDirectoryExists( $fileList->{$file}->{'target'}, $install->{'default permission'}, $install->{'default owner'} . ':' . $install->{'default group'} );
339
      &runCommand( 
340
            "cp $fileList->{$file}->{'source'} $fileList->{$file}->{'target'}",
341
            "chmod $fileList->{$file}->{'permission'} $fileList->{$file}->{'target'}",
342
            "chown $fileList->{$file}->{'owner'} $fileList->{$file}->{'target'}"
343
            );
344
      # if there is a post action, run it and store any return in @feedback
345
      push @feedback, `$fileList->{$file}->{'post action'}` if defined $fileList->{$file}->{'post action'};
346
      # if there is a message to be passed, store it in @messages
347
      push @messages, $fileList->{$file}->{'meesage'} if defined $fileList->{$file}->{'message'};
348
   } # foreach file
349
   # set up crontab, if necessary
350
   &runCommand( $install->{'crontab'} ) if defined ( $install->{'crontab'} );
351
   return 1;
352
}
353
 
354
sub postInstall {
355
   my $install = shift;
356
 
138 rodolico 357
   &logIt( 'Entering postInstall' );
132 rodolico 358
   # seed configuration, if needed
359
   if ( $$install{'build config'} ) {
360
      my $config;
361
      my @fileList;
362
 
363
      # the order is important here as, if multiple files exist, latter ones can
364
      # overwrite values in the previous. We do a push so the first value pushed
365
      # is processed last, ie has higher priority.
366
      push @fileList, $install->{'configuration'}->{'old configuration file'};
367
      push @fileList, $install->{'configuration'}->{'configuration file'};
368
 
369
      my $seedFile = $install->{'configuration'}->{'configuration seed file'};
370
      if ( -e $install->{'configuration seed file'} ) {
371
         push @fileList, $install->{'configuration seed file'};
372
      } # if preload seed file
373
 
374
      $config = &makeConfig( @fileList );
375
      # if ScriptDirs and moduleDirs not populated, do so from our configuration
376
      if ( ! $$config{'scriptDirs'} || ! scalar @{ $$config{'scriptDirs'} }  ) {
377
         $config->{'scriptDirs'} = [ $install->{'files'}->{'scripts'}->{'target'} ];
378
      }
379
      if ( ! $$config{'moduleDirs'} || ! @{ $$config{'moduleDirs'} }  ) {
380
         $config->{'moduleDirs'} = [ $install->{'files'}->{'modules'}->{'target'} ];
381
      }
382
      my $content = &showConf( $config );
383
      return &writeConfig( '' , $content );
384
   } # if we are building/merging configuration
385
}
386
 
387
# installs binaries and libraries
388
sub installOSStuff {
389
   my $install = shift;
390
   my @actions = values( %{ $install->{'missing libraries'} } );
391
   push @actions, values( %{ $install->{'missing binaries'} } );
392
   foreach my $action ( @actions ) {
140 rodolico 393
      &logIt( $action );
132 rodolico 394
      &runCommand( $action );
395
   }
396
}
397
 
398
 
399
################################################################
400
#               Main Code                                      #
401
################################################################
402
 
403
# handle any command line parameters that may have been passed in
404
 
405
GetOptions (
406
            "os|o=s"      => \$os,      # pass in the operating system
407
            "dryrun|n"    => \$dryRun,  # do NOT actually do anything
408
            'help|h'      => \$help,
138 rodolico 409
            'version|v'   => \$version
132 rodolico 410
            ) or die "Error parsing command line\n";
411
 
412
if ( $help ) { &help() ; exit; }
413
if ( $version ) { print "$0 version $VERSION\n"; exit; }
414
 
138 rodolico 415
&logIt( 'Beginning installation' );
416
 
132 rodolico 417
$install{'os'} = &setUpOperatingSystemSpecific( \%install, \%operatingSystems, $os ? $os : `$sourceDir/determineOS`, $sourceDir );
138 rodolico 418
&logIt( "Operating System is $install{'os'}" );
419
 
132 rodolico 420
$install{'missing libraries'} = &validateLibraries( \%libraries, $install{'os'} );
138 rodolico 421
&logIt( "Missing Libraries\n" . Data::Dumper->Dump( [$install{'missing libraries'}],[qw($install{'missing libraries'})])  );
422
 
132 rodolico 423
$install{'missing binaries'} = &validateBinaries( \%binaries, $install{'os'} );
138 rodolico 424
&logIt( "Missing binaries\n" . Data::Dumper->Dump( [$install{'missing binaries'}],[qw($install{'missing binaries'})]) );
425
 
132 rodolico 426
&getInstallActions( \%install );
138 rodolico 427
&logIt( "Completed getInstallActions\n" .  Data::Dumper->Dump( [\%install],[qw(%install)]) );
428
 
132 rodolico 429
populateSourceDir( \%install, $sourceDir );
430
 
431
if ( &GetPermission( \%install ) ) {
432
   &installOSStuff( \%install );
433
   &getVersions( \%install ) unless $install{'action'} eq 'new';
434
   &doInstall( \%install );
435
} else {
436
   die "Please fix whatever needs to be done and try again\n";
138 rodolico 437
   &logIt( "User chose to kill process" );
132 rodolico 438
}
439
 
440
my $filename = &postInstall( \%install );
441
my $confFileName = $install{'configuration'}{'configuration file'};
140 rodolico 442
&logIt( "Running $install{bindir}/configure.pl -f $filename -o $confFileName" );
132 rodolico 443
 
444
exec( "$install{bindir}/configure.pl -f $filename -o $confFileName" );