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