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