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