| 33 | rodolico | 1 | #! /usr/bin/env perl
 | 
        
           |  |  | 2 |   | 
        
           |  |  | 3 | use strict;
 | 
        
           |  |  | 4 | use warnings;
 | 
        
           |  |  | 5 |   | 
        
           | 42 | 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 | #
 | 
        
           | 50 | rodolico | 15 | # version 1.2 20170327 RWR
 | 
        
           |  |  | 16 | # did some major modifications to correctly work on BSD systems also
 | 
        
           | 94 | rodolico | 17 | #
 | 
        
           |  |  | 18 | # version 2.0 20190330 RWR
 | 
        
           |  |  | 19 | # changed it so all configs are YAML
 | 
        
           | 33 | rodolico | 20 |   | 
        
           | 94 | rodolico | 21 | our $VERSION = '2.0';
 | 
        
           | 42 | rodolico | 22 |   | 
        
           | 33 | rodolico | 23 | # find our location and use it for searching for libraries
 | 
        
           |  |  | 24 | BEGIN {
 | 
        
           |  |  | 25 |    use FindBin;
 | 
        
           |  |  | 26 |    use File::Spec;
 | 
        
           |  |  | 27 |    use lib File::Spec->catdir($FindBin::Bin);
 | 
        
           |  |  | 28 | }
 | 
        
           |  |  | 29 |   | 
        
           |  |  | 30 | use sysinfoconf;
 | 
        
           | 101 | rodolico | 31 | use YAML::Tiny;
 | 
        
           | 94 | rodolico | 32 | use Data::Dumper;
 | 
        
           | 33 | rodolico | 33 | use File::Basename;
 | 
        
           | 35 | rodolico | 34 | use Getopt::Long;
 | 
        
           |  |  | 35 | Getopt::Long::Configure ("bundling"); # allow -vd --os='debian'
 | 
        
           | 33 | rodolico | 36 |   | 
        
           | 35 | rodolico | 37 | # $verbose can have the following values
 | 
        
           | 33 | rodolico | 38 | # 0 - do everything
 | 
        
           |  |  | 39 | # 1 - Do everything except the install, display what would be done
 | 
        
           |  |  | 40 | # 2 - Be verbose to STDERR
 | 
        
           |  |  | 41 | # 3 - Be very verbose
 | 
        
           | 35 | rodolico | 42 | my $verbose = 0; # if test mode, simply show what would be done
 | 
        
           |  |  | 43 | my $dryRun = 0;
 | 
        
           |  |  | 44 | my $os;
 | 
        
           |  |  | 45 | my $help = 0;
 | 
        
           |  |  | 46 | my $version = 0;
 | 
        
           | 33 | rodolico | 47 |   | 
        
           |  |  | 48 | my $status; # exit status of the processes
 | 
        
           |  |  | 49 | my $sourceDir = File::Spec->catdir($FindBin::Bin);
 | 
        
           | 34 | rodolico | 50 | my $installType;
 | 
        
           | 33 | rodolico | 51 |   | 
        
           | 120 | rodolico | 52 | my @messages; # stores any messages we want to show up at the end
 | 
        
           |  |  | 53 | my @feedback; # store feedback when we execute command line actions
 | 
        
           | 57 | rodolico | 54 |   | 
        
           | 33 | rodolico | 55 | my %install = (  'bindir' => '/opt/camp/sysinfo-client',
 | 
        
           |  |  | 56 |                  'confdir' => '/etc/camp/sysinfo-client',
 | 
        
           | 34 | rodolico | 57 |                  'application name' => 'sysinfo client',
 | 
        
           | 50 | rodolico | 58 |                  'default group' => 'root',
 | 
        
           |  |  | 59 |                  'default owner' => 'root',
 | 
        
           |  |  | 60 |                  'default permission' => '0700',
 | 
        
           | 35 | rodolico | 61 |                  'configuration' => {
 | 
        
           |  |  | 62 |                           'configurator' => '<bindir>/configure.pl',
 | 
        
           | 94 | rodolico | 63 |                           'configuration file' => '<confdir>/sysinfo-client.yaml',
 | 
        
           |  |  | 64 |                           'configuration seed file' => 'sysinfo-client.seed.yaml',
 | 
        
           |  |  | 65 |                           'old configuration file' => '<confdir>/sysinfo-client.conf',
 | 
        
           | 35 | rodolico | 66 |                           'permission' => '700',
 | 
        
           | 50 | rodolico | 67 |                           'owner'      => '<default owner>',
 | 
        
           | 35 | rodolico | 68 |                           'target'     => '<confdir>'
 | 
        
           |  |  | 69 |                             },
 | 
        
           | 33 | rodolico | 70 |                  'files' => {
 | 
        
           |  |  | 71 |                            'configure.pl' => { 
 | 
        
           |  |  | 72 |                                  'type' => 'file',
 | 
        
           |  |  | 73 |                                  'permission' => '700', 
 | 
        
           | 50 | rodolico | 74 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 75 |                                  'target' =>  '<bindir>'
 | 
        
           |  |  | 76 |                               },
 | 
        
           |  |  | 77 |                            'sysinfo-client' => { 
 | 
        
           |  |  | 78 |                                  'type' => 'file',
 | 
        
           |  |  | 79 |                                  'permission' => '700',
 | 
        
           | 50 | rodolico | 80 |                                  'owner' => '<default owner>:<default group>',
 | 
        
           | 33 | rodolico | 81 |                                  'target' =>  '<bindir>'
 | 
        
           |  |  | 82 |                               },
 | 
        
           |  |  | 83 |                            'sysinfoconf.pm' => {
 | 
        
           |  |  | 84 |                                  'type' => 'file',
 | 
        
           |  |  | 85 |                                  'permission' => '600',
 | 
        
           | 50 | rodolico | 86 |                                  'owner' => '<default owner>:<default group>',
 | 
        
           | 33 | rodolico | 87 |                                  'target' =>  '<bindir>'
 | 
        
           |  |  | 88 |                               },
 | 
        
           |  |  | 89 |                            'notes' => { 
 | 
        
           |  |  | 90 |                                  'type' => 'file',
 | 
        
           |  |  | 91 |                                  'permission' => '600', 
 | 
        
           | 50 | rodolico | 92 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 93 |                                  'target' =>  '<bindir>'
 | 
        
           |  |  | 94 |                               },
 | 
        
           | 94 | rodolico | 95 |                            'sysinfo-client.conf.template.yaml' => { 
 | 
        
           | 33 | rodolico | 96 |                                  'type' => 'file',
 | 
        
           |  |  | 97 |                                  'permission' => '600', 
 | 
        
           | 50 | rodolico | 98 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 99 |                                  'target' =>  '<bindir>' 
 | 
        
           |  |  | 100 |                               },
 | 
        
           |  |  | 101 |                            'getSendEmail.pl' => { 
 | 
        
           |  |  | 102 |                                  'type' => 'file',
 | 
        
           |  |  | 103 |                                  'permission' => '700', 
 | 
        
           | 50 | rodolico | 104 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 105 |                                  'target' =>  '<bindir>' 
 | 
        
           |  |  | 106 |                               },
 | 
        
           |  |  | 107 |                            'install.pl' => {
 | 
        
           |  |  | 108 |                                  'type' => 'file',
 | 
        
           |  |  | 109 |                                  'permission' => '700', 
 | 
        
           | 50 | rodolico | 110 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 111 |                                  'target' =>  '<bindir>' 
 | 
        
           |  |  | 112 |                               },
 | 
        
           | 94 | rodolico | 113 |                            'sysinfo-client.seed.example.yaml' => { 
 | 
        
           | 33 | rodolico | 114 |                                  'type' => 'file',
 | 
        
           |  |  | 115 |                                  'permission' => '600', 
 | 
        
           | 50 | rodolico | 116 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 117 |                                  'target' =>  '<bindir>' 
 | 
        
           |  |  | 118 |                               },
 | 
        
           |  |  | 119 |                            'VERSION' => { 
 | 
        
           |  |  | 120 |                                  'type' => 'file',
 | 
        
           |  |  | 121 |                                  'permission' => '600', 
 | 
        
           | 50 | rodolico | 122 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 123 |                                  'target' =>  '<bindir>' 
 | 
        
           |  |  | 124 |                               },
 | 
        
           |  |  | 125 |                               'modules' => {
 | 
        
           |  |  | 126 |                                  'type' => 'directory',
 | 
        
           |  |  | 127 |                                  'permission' => '700', 
 | 
        
           | 50 | rodolico | 128 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 129 |                                  'target' =>  '<bindir>',
 | 
        
           |  |  | 130 |                                  'action' => 'chmod 700 *'
 | 
        
           |  |  | 131 |                               },
 | 
        
           |  |  | 132 |                               'scripts' => {
 | 
        
           |  |  | 133 |                                  'type' => 'directory',
 | 
        
           |  |  | 134 |                                  'permission' => '700', 
 | 
        
           | 50 | rodolico | 135 |                                  'owner' => '<default owner>:<default group>', 
 | 
        
           | 33 | rodolico | 136 |                                  'target' =>  '<bindir>',
 | 
        
           |  |  | 137 |                                  'action' => 'chmod 700 *'
 | 
        
           |  |  | 138 |                               },
 | 
        
           |  |  | 139 |                      }
 | 
        
           |  |  | 140 |                   );
 | 
        
           |  |  | 141 |   | 
        
           |  |  | 142 | # hash to set up os specific rules                  
 | 
        
           |  |  | 143 | my %operatingSystems = (
 | 
        
           |  |  | 144 |                   'debian' => {
 | 
        
           | 76 | rodolico | 145 |                      'regex'  => '(debian|mx|devuan)',
 | 
        
           | 33 | rodolico | 146 |                      'bindir' => '/opt/camp/sysinfo-client',
 | 
        
           |  |  | 147 |                      'confdir' => '/etc/camp/sysinfo-client',
 | 
        
           | 35 | rodolico | 148 |                      'crontab' => 'ln -s <bindir>/sysinfo-client /etc/cron.daily/sysinfo-client',
 | 
        
           | 57 | rodolico | 149 |                      'modules' => '((linux)|(dpkg)|(unix)|(all))',
 | 
        
           | 33 | rodolico | 150 |                   },
 | 
        
           |  |  | 151 |                   'ipfire' => {
 | 
        
           | 60 | rodolico | 152 |                      'regex'  => 'ipfire',
 | 
        
           | 33 | rodolico | 153 |                      'bindir' => '/opt/camp/sysinfo-client',
 | 
        
           |  |  | 154 |                      'confdir' => '/etc/camp/sysinfo-client',
 | 
        
           | 50 | rodolico | 155 |                      'crontab' => 'ln -s <bindir>/sysinfo-client /etc/fcron.daily/sysinfo-client.fcron',
 | 
        
           | 57 | rodolico | 156 |                      'modules' => '((ipfire)|(unix)|(all))',
 | 
        
           | 43 | rodolico | 157 |                   },
 | 
        
           |  |  | 158 |                   'freebsd' => {
 | 
        
           | 60 | rodolico | 159 |                      'regex' => 'freebsd',
 | 
        
           | 43 | rodolico | 160 |                      'bindir' => '/usr/local/opt/camp/sysinfo-client',
 | 
        
           |  |  | 161 |                      'confdir' => '/usr/local/etc/camp/sysinfo-client',
 | 
        
           | 50 | rodolico | 162 |                      'crontab' => 'ln -s <bindir>/sysinfo-client /etc/periodic/daily/sysinfo-client',
 | 
        
           | 57 | rodolico | 163 |                      'modules' => '((bsd)|(unix)|(all))',
 | 
        
           | 50 | rodolico | 164 |                     'default group' => 'wheel',
 | 
        
           |  |  | 165 |                     'default owner' => 'root',
 | 
        
           | 43 | rodolico | 166 |                   },
 | 
        
           | 94 | rodolico | 167 |                   'opnsense' => {
 | 
        
           |  |  | 168 |                      'fileexists' => '/conf/config.xml',
 | 
        
           |  |  | 169 |                      'bindir' => '/usr/local/opt/camp/sysinfo-client',
 | 
        
           |  |  | 170 |                      'confdir' => '/usr/local/etc/camp/sysinfo-client',
 | 
        
           |  |  | 171 |                      'crontab' => 'ln -s <bindir>/sysinfo-client /etc/periodic/daily/sysinfo-client',
 | 
        
           |  |  | 172 |                      'modules' => '((bsd)|(unix)|(all))',
 | 
        
           |  |  | 173 |                     'default group' => 'wheel',
 | 
        
           |  |  | 174 |                     'default owner' => 'root',
 | 
        
           | 99 | rodolico | 175 |                     'files' => {
 | 
        
           |  |  | 176 |                               'YAML' => { 
 | 
        
           |  |  | 177 |                                     'type' => 'directory',
 | 
        
           |  |  | 178 |                                     'permission' => '777', 
 | 
        
           |  |  | 179 |                                     'owner' => '<default owner>:<default group>', 
 | 
        
           |  |  | 180 |                                     'target' =>  '<bindir>'
 | 
        
           |  |  | 181 |                                  },
 | 
        
           |  |  | 182 |                               'Tiny.pm' => { 
 | 
        
           |  |  | 183 |                                     'type' => 'file',
 | 
        
           |  |  | 184 |                                     'permission' => '444',
 | 
        
           |  |  | 185 |                                     'owner' => '<default owner>:<default group>',
 | 
        
           |  |  | 186 |                                     'target' =>  '<bindir>/YAML'
 | 
        
           |  |  | 187 |                                  },
 | 
        
           | 120 | rodolico | 188 |                               'actions_sysinfo.conf' => {
 | 
        
           |  |  | 189 |                                     'type' => 'file',
 | 
        
           |  |  | 190 |                                     'permission' => '755',
 | 
        
           |  |  | 191 |                                     'owner' => '<default owner>:<default group>',
 | 
        
           |  |  | 192 |                                     'target' => '/usr/local/opnsense/service/conf/actions.d',
 | 
        
           |  |  | 193 |                                     'post action' => 'service configd restart',
 | 
        
           | 122 | rodolico | 194 |                                     'message' => 'No automatic run can be done. Please log in through the webui and enable the sysinfo cron job via System | Settings | Cron'
 | 
        
           | 120 | rodolico | 195 |                                  },
 | 
        
           | 99 | rodolico | 196 |                               },
 | 
        
           | 94 | rodolico | 197 |                  },
 | 
        
           | 43 | rodolico | 198 |   | 
        
           |  |  | 199 |                 );
 | 
        
           | 42 | rodolico | 200 |   | 
        
           | 81 | rodolico | 201 | my %configuration;
 | 
        
           |  |  | 202 |   | 
        
           | 42 | rodolico | 203 | # list of libraries used by the system. We will offer to install them if
 | 
        
           |  |  | 204 | # we know how. NOTE: I have chosen to put the full installation command
 | 
        
           |  |  | 205 | # for each library. This allows us to use the package selector OR a different
 | 
        
           |  |  | 206 | # piece of code on a per-library basis, but results in something like
 | 
        
           |  |  | 207 | #      apt-get -y install perl-modules
 | 
        
           |  |  | 208 | #      apt-get -y install libwww-perl
 | 
        
           |  |  | 209 | # instead of
 | 
        
           |  |  | 210 | #      apt-get -y install libwww-perl perl-modules
 | 
        
           |  |  | 211 | # flexibility vs efficiency in this case.
 | 
        
           |  |  | 212 | my %libraries = ( 
 | 
        
           |  |  | 213 |                   'File::Basename' => { 'debian' => 'apt-get -y install perl-modules' },
 | 
        
           |  |  | 214 |                   'Exporter' => { 'debian' => 'apt-get -y install perl-base' },
 | 
        
           | 50 | rodolico | 215 |                   'LWP' => { 
 | 
        
           |  |  | 216 |                              'debian' => 'apt-get -y install libwww-perl',
 | 
        
           | 122 | rodolico | 217 |                              'freebsd' => 'pkg install -y p5-libwww',
 | 
        
           |  |  | 218 |                              'opnsense' => 'pkg install -y p5-libwww',
 | 
        
           | 50 | rodolico | 219 |                            },
 | 
        
           | 42 | rodolico | 220 |                 );
 | 
        
           |  |  | 221 |   | 
        
           | 43 | rodolico | 222 | # utilities md5sum
 | 
        
           |  |  | 223 | # freebsd isomd5sum
 | 
        
           |  |  | 224 |   | 
        
           | 42 | rodolico | 225 | # validates the libraries needed exist
 | 
        
           |  |  | 226 | # simply eval's each library. If it doesn't exist, creates a list of
 | 
        
           |  |  | 227 | # commands to be executed to install it, and displays missing libraries
 | 
        
           |  |  | 228 | # offering to install them if possible.
 | 
        
           |  |  | 229 | sub validateLibraries {
 | 
        
           |  |  | 230 |    my ( $libraries, $os ) = @_;
 | 
        
           |  |  | 231 |    my @command;
 | 
        
           |  |  | 232 |    my @missingLibs;
 | 
        
           |  |  | 233 |    foreach my $lib ( keys %$libraries ) {
 | 
        
           | 122 | rodolico | 234 |       print "Checking on library $lib\n";
 | 
        
           | 42 | rodolico | 235 |       eval( "use $lib;" );
 | 
        
           |  |  | 236 |       if ( $@ ) {
 | 
        
           | 98 | rodolico | 237 |          print "\tNot found, adding\n";
 | 
        
           | 42 | rodolico | 238 |          push @command,$$libraries{$lib}{$os} if $$libraries{$lib}{$os};
 | 
        
           |  |  | 239 |          push @missingLibs, $lib;
 | 
        
           |  |  | 240 |       }
 | 
        
           |  |  | 241 |    }
 | 
        
           |  |  | 242 |    if ( @missingLibs ) { # we have missing libraries
 | 
        
           |  |  | 243 |       if ( @command ) {
 | 
        
           |  |  | 244 |          &runCommand( join( "\n", @command ) )
 | 
        
           |  |  | 245 |             if &yesno(
 | 
        
           |  |  | 246 |                         'Following libraries need to be installed: ' . 
 | 
        
           |  |  | 247 |                         join( ' ', @missingLibs ) . "\n" .
 | 
        
           |  |  | 248 |                         "I can install them with the following command(s)\n" . 
 | 
        
           |  |  | 249 |                         join( "\n", @command ) . "\nDo you want me to do this?\n"
 | 
        
           |  |  | 250 |                      );
 | 
        
           |  |  | 251 |       } else {
 | 
        
           |  |  | 252 |          die unless &yesno( 'Following libraries need to be installed: ' . 
 | 
        
           |  |  | 253 |                             join( ' ', @missingLibs ) . 
 | 
        
           |  |  | 254 |                             "\nand I don't know how to do this. Abort?" );
 | 
        
           |  |  | 255 |       }
 | 
        
           |  |  | 256 |    } # if
 | 
        
           |  |  | 257 | } # validateLibraries
 | 
        
           | 33 | rodolico | 258 |   | 
        
           | 94 | rodolico | 259 |   | 
        
           | 114 | rodolico | 260 |   | 
        
           |  |  | 261 |   | 
        
           | 33 | rodolico | 262 | # attempt to locate the operating system.
 | 
        
           |  |  | 263 | # if found, will set some defaults for it.
 | 
        
           | 94 | rodolico | 264 | sub setUpOperatingSystemSpecific {
 | 
        
           | 35 | rodolico | 265 |    my ( $install, $operatingSystems, $os ) = @_;
 | 
        
           | 107 | rodolico | 266 |    print "They passed $os in as the \$os\n" if $verbose > 2;
 | 
        
           | 35 | rodolico | 267 |    if ( $os ) {
 | 
        
           | 94 | rodolico | 268 |       # We found the OS, set up some defaults
 | 
        
           | 35 | rodolico | 269 |       $$install{'os'} = $os;
 | 
        
           |  |  | 270 |       print "Setting keys for operating system\n" if $verbose > 2;
 | 
        
           | 114 | rodolico | 271 |       # merge operatingSystems into install
 | 
        
           | 115 | rodolico | 272 |       foreach my $key ( keys %{$operatingSystems->{ $os }} ) {
 | 
        
           |  |  | 273 |          if ( $key eq 'files' ) {
 | 
        
           |  |  | 274 |             $install->{'files'} = { %{$install->{'files'}}, %{$operatingSystems->{$os}->{'files'}} }
 | 
        
           |  |  | 275 |          } else {
 | 
        
           |  |  | 276 |             $install->{$key} = $operatingSystems->{ $os }->{$key};
 | 
        
           |  |  | 277 |          }
 | 
        
           |  |  | 278 |       } # if it is a known OS
 | 
        
           | 35 | rodolico | 279 |    } # if
 | 
        
           |  |  | 280 |    return $os;
 | 
        
           | 33 | rodolico | 281 | } # getOperatingSystem
 | 
        
           |  |  | 282 |   | 
        
           | 37 | rodolico | 283 |   | 
        
           | 34 | rodolico | 284 | # get some input from the user and decide how to install/upgrade/remove/whatever
 | 
        
           |  |  | 285 | sub getInstallActions {
 | 
        
           |  |  | 286 |    my $install = shift;
 | 
        
           |  |  | 287 |    if ( ! &yesno( "This looks like a $$install{'os'} machine, correct?" ) ) {
 | 
        
           |  |  | 288 |       die "User Aborted\n" if &yesno( "If we continue, I will set this up like a $$install{'os'} system. Abort?" );
 | 
        
           |  |  | 289 |    }
 | 
        
           |  |  | 290 |    if ( -d $$install{'confdir'} ) {
 | 
        
           |  |  | 291 |       $$install{'action'} = &getAnswer( "It looks like $$install{'application name'} is already installed, what do you want to do?", 
 | 
        
           |  |  | 292 |                             ( "upgrade","remove", "overwrite" )
 | 
        
           |  |  | 293 |                           );
 | 
        
           |  |  | 294 |    } else {
 | 
        
           |  |  | 295 |       if ( &yesno( "This looks like a fresh install, correct?" ) ) {
 | 
        
           |  |  | 296 |          $$install{'action'} = 'install';
 | 
        
           |  |  | 297 |          $$install{'preseed config'} = &yesno( "Preseed the configuration file?" );
 | 
        
           |  |  | 298 |       } else {
 | 
        
           |  |  | 299 |          die "Can not continue at this time: Do not understand your system\n";
 | 
        
           |  |  | 300 |       }
 | 
        
           |  |  | 301 |    }
 | 
        
           |  |  | 302 |    $$install{'build config'} = &yesno( "Edit config file when done?" );
 | 
        
           |  |  | 303 |    $$install{'setup cron'} = &yesno( "Set up for automatic running via crontab?" );
 | 
        
           |  |  | 304 | }
 | 
        
           | 33 | rodolico | 305 |   | 
        
           | 34 | rodolico | 306 | sub showWork { 
 | 
        
           |  |  | 307 |    my $install = shift;
 | 
        
           | 91 | rodolico | 308 |    print Dump( \%install );
 | 
        
           | 34 | rodolico | 309 | }
 | 
        
           | 35 | rodolico | 310 |   | 
        
           |  |  | 311 |   | 
        
           |  |  | 312 | sub doPlaceholderSubstitution {
 | 
        
           |  |  | 313 |    my ($hash, $placeholder) = @_;
 | 
        
           |  |  | 314 |    return if ref $hash ne 'HASH';
 | 
        
           |  |  | 315 |    foreach my $key ( keys %$hash ) {
 | 
        
           |  |  | 316 |       if ( ref( $$hash{$key} ) ) {
 | 
        
           |  |  | 317 |          &doPlaceholderSubstitution( $$hash{$key}, $placeholder );
 | 
        
           |  |  | 318 |       } else {
 | 
        
           |  |  | 319 |          foreach my $place ( keys %$placeholder ) {
 | 
        
           |  |  | 320 |             $$hash{$key} =~ s/$place/$$placeholder{$place}/;
 | 
        
           |  |  | 321 |          } # foreach
 | 
        
           |  |  | 322 |       } # if..else
 | 
        
           |  |  | 323 |    } # foreach
 | 
        
           |  |  | 324 |    return;
 | 
        
           |  |  | 325 | }
 | 
        
           | 34 | rodolico | 326 |   | 
        
           |  |  | 327 | # This will go through and first, see if anything is a directory, in
 | 
        
           |  |  | 328 | # which case, we'll create new entries for all files in there.
 | 
        
           |  |  | 329 | # then, it will do keyword substitution of <bindir> and <confdir>
 | 
        
           |  |  | 330 | # to populate the target.
 | 
        
           |  |  | 331 | # When this is done, each file should have a source and target that is
 | 
        
           |  |  | 332 | # a fully qualified path and filename
 | 
        
           | 33 | rodolico | 333 | sub populateSourceDir {
 | 
        
           |  |  | 334 |    my ( $install, $sourceDir ) = @_;
 | 
        
           | 35 | rodolico | 335 |    my %placeHolders = 
 | 
        
           |  |  | 336 |       ( 
 | 
        
           |  |  | 337 |         '<bindir>' => $$install{'bindir'},
 | 
        
           | 50 | rodolico | 338 |         '<confdir>' => $$install{'confdir'},
 | 
        
           |  |  | 339 |         '<default owner>' => $$install{'default owner'},
 | 
        
           |  |  | 340 |         '<default group>' => $$install{'default group'},
 | 
        
           |  |  | 341 |         '<default permission>' => $$install{'default permission'}
 | 
        
           | 35 | rodolico | 342 |       );
 | 
        
           | 33 | rodolico | 343 |   | 
        
           |  |  | 344 |    my $allFiles = $$install{'files'};
 | 
        
           |  |  | 345 |   | 
        
           | 35 | rodolico | 346 |    # find all directory entries and load files in that directory into $$install{'files'}
 | 
        
           | 33 | rodolico | 347 |    foreach my $dir ( keys %$allFiles ) {
 | 
        
           |  |  | 348 |       if ( defined( $$allFiles{$dir}{'type'} ) && $$allFiles{$dir}{'type'} eq 'directory' ) {
 | 
        
           | 35 | rodolico | 349 |          print "Found directory $dir\n" if $verbose > 2;
 | 
        
           | 33 | rodolico | 350 |          if ( opendir( my $dh, "$sourceDir/$dir" ) ) {
 | 
        
           |  |  | 351 |             my @files = map{ $dir . '/' . $_ } grep { ! /^\./ && -f "$sourceDir/$dir/$_" } readdir( $dh );
 | 
        
           | 35 | rodolico | 352 |             print "\tFound files " . join( ' ', @files ) . "\n" if $verbose > 2;
 | 
        
           | 33 | rodolico | 353 |             foreach my $file ( @files ) {
 | 
        
           |  |  | 354 |                $$allFiles{ $file }{'type'} = 'file';
 | 
        
           | 37 | rodolico | 355 |                if ( $dir eq 'modules' ) {
 | 
        
           |  |  | 356 |                   $$allFiles{ $file }{'permission'} = ( $file =~ m/$$install{'modules'}/ ) ? '0700' : '0600';
 | 
        
           |  |  | 357 |                } else {
 | 
        
           |  |  | 358 |                   $$allFiles{ $file }{'permission'} = $$allFiles{ $dir }{'permission'};
 | 
        
           |  |  | 359 |                }
 | 
        
           | 33 | rodolico | 360 |                $$allFiles{ $file }{'owner'} = $$allFiles{ $dir }{'owner'};
 | 
        
           |  |  | 361 |                $$allFiles{ $file }{'target'} = $$allFiles{ $dir }{'target'};
 | 
        
           |  |  | 362 |             } # foreach
 | 
        
           |  |  | 363 |             closedir $dh;
 | 
        
           |  |  | 364 |          } # if opendir
 | 
        
           |  |  | 365 |       } # if it is a directory
 | 
        
           |  |  | 366 |    } # foreach
 | 
        
           | 35 | rodolico | 367 |    # find all files, and set the source directory, and add the filename to
 | 
        
           |  |  | 368 |    # the target
 | 
        
           | 33 | rodolico | 369 |    foreach my $file ( keys %$allFiles ) {
 | 
        
           |  |  | 370 |       $$allFiles{$file}{'source'} = "$sourceDir/$file";
 | 
        
           |  |  | 371 |       $$allFiles{$file}{'target'} .= "/$file";
 | 
        
           |  |  | 372 |    } # foreach
 | 
        
           | 35 | rodolico | 373 |   | 
        
           |  |  | 374 |    # finally, do place holder substitution. This recursively replaces all keys
 | 
        
           |  |  | 375 |    # in  %placeHolders with the values.
 | 
        
           |  |  | 376 |    &doPlaceholderSubstitution( $install, \%placeHolders );
 | 
        
           |  |  | 377 |   | 
        
           | 91 | rodolico | 378 |    print Dump( $install ) if $verbose > 2;
 | 
        
           | 35 | rodolico | 379 |   | 
        
           | 33 | rodolico | 380 |    return 1;
 | 
        
           |  |  | 381 | } # populateSourceDir
 | 
        
           |  |  | 382 |   | 
        
           | 34 | rodolico | 383 | # there is a file named VERSIONS. We get the values out of the install
 | 
        
           |  |  | 384 | # directory and (if it exists) the target so we can decide what needs
 | 
        
           |  |  | 385 | # to be updated.
 | 
        
           | 33 | rodolico | 386 | sub getVersions {
 | 
        
           |  |  | 387 |    my $install = shift;
 | 
        
           |  |  | 388 |    my $currentVersionFile = $$install{'files'}{'VERSION'}{'target'};
 | 
        
           |  |  | 389 |    my $newVersionFile = $$install{'files'}{'VERSION'}{'source'};
 | 
        
           |  |  | 390 |    if ( open FILE,"<$currentVersionFile" ) {
 | 
        
           |  |  | 391 |       while ( my $line = <FILE> ) {
 | 
        
           |  |  | 392 |          chomp $line;
 | 
        
           |  |  | 393 |          my ( $filename, $version, $checksum ) = split( "\t", $line );
 | 
        
           |  |  | 394 |          $$install{'files'}{$filename}{'installed version'} = $version ? $version : '';
 | 
        
           |  |  | 395 |       }
 | 
        
           |  |  | 396 |       close FILE;
 | 
        
           |  |  | 397 |    }
 | 
        
           |  |  | 398 |    if ( open FILE,"<$newVersionFile" ) {
 | 
        
           |  |  | 399 |       while ( my $line = <FILE> ) {
 | 
        
           |  |  | 400 |          chomp $line;
 | 
        
           |  |  | 401 |          my ( $filename, $version, $checksum ) = split( "\t", $line );
 | 
        
           |  |  | 402 |          $$install{'files'}{$filename}{'new version'} = $version ? $version : '';
 | 
        
           |  |  | 403 |       }
 | 
        
           |  |  | 404 |       close FILE;
 | 
        
           |  |  | 405 |    }
 | 
        
           | 40 | rodolico | 406 |    foreach my $file ( keys %{$$install{'files'}} ) {
 | 
        
           |  |  | 407 |       $$install{'files'}{$file}{'installed version'} = -2 unless defined $$install{'files'}{$file}{'installed version'};
 | 
        
           |  |  | 408 |       $$install{'files'}{$file}{'new version'} = -1 unless defined $$install{'files'}{$file}{'new version'};
 | 
        
           |  |  | 409 |    }
 | 
        
           | 33 | rodolico | 410 |    return 1;
 | 
        
           |  |  | 411 | } # getVersions
 | 
        
           |  |  | 412 |   | 
        
           | 34 | rodolico | 413 | # this actually does the installation, except for the configuration
 | 
        
           | 33 | rodolico | 414 | sub doInstall {
 | 
        
           |  |  | 415 |    my $install = shift;
 | 
        
           |  |  | 416 |    my $fileList = $$install{'files'};
 | 
        
           |  |  | 417 |   | 
        
           | 50 | rodolico | 418 |    &checkDirectoryExists( $$install{'bindir'} . '/', $$install{'default permission'}, "$$install{'default owner'}:$$install{'default group'}" );
 | 
        
           | 33 | rodolico | 419 |    foreach my $file ( keys %$fileList ) {
 | 
        
           |  |  | 420 |       next unless ( $$fileList{$file}{'type'} && $$fileList{$file}{'type'} eq 'file' );
 | 
        
           | 40 | rodolico | 421 |       next if $$install{'action'} eq 'upgrade' && ! defined( $$fileList{$file}{'installed version'} )
 | 
        
           |  |  | 422 |               ||
 | 
        
           |  |  | 423 |               ( $$fileList{$file}{'new version'} eq $$fileList{$file}{'installed version'} );
 | 
        
           | 50 | rodolico | 424 |       &checkDirectoryExists( $$fileList{$file}{'target'}, $$install{'default permission'}, "$$install{'default owner'}:$$install{'default group'}" );
 | 
        
           | 33 | rodolico | 425 |       &runCommand( 
 | 
        
           |  |  | 426 |             "cp $$fileList{$file}{'source'} $$fileList{$file}{'target'}",
 | 
        
           |  |  | 427 |             "chmod $$fileList{$file}{'permission'} $$fileList{$file}{'target'}",
 | 
        
           |  |  | 428 |             "chown  $$fileList{$file}{'owner'} $$fileList{$file}{'target'}"
 | 
        
           |  |  | 429 |             );
 | 
        
           | 120 | rodolico | 430 |             # if there is a post action, run it and store any return in @feedback
 | 
        
           |  |  | 431 |             push @feedback, `$fileList->{$file}->{'post action'}` if defined $fileList->{$file}->{'post action'};
 | 
        
           |  |  | 432 |             # if there is a message to be passed, store it in @messages
 | 
        
           |  |  | 433 |             push @messages, $fileList->{$file}->{'meesage'} if defined $fileList->{$file}->{'message'};
 | 
        
           | 33 | rodolico | 434 |    } # foreach file
 | 
        
           |  |  | 435 |    return 1;
 | 
        
           |  |  | 436 | }
 | 
        
           |  |  | 437 |   | 
        
           | 35 | rodolico | 438 | sub postInstall {
 | 
        
           |  |  | 439 |    my $install = shift;
 | 
        
           | 37 | rodolico | 440 |   | 
        
           | 35 | rodolico | 441 |    # set up crontab, if necessary
 | 
        
           | 37 | rodolico | 442 |    &runCommand( $$install{'crontab'} ) if defined ( $$install{'crontab'} );
 | 
        
           |  |  | 443 |   | 
        
           |  |  | 444 |    # seed configuration, if needed
 | 
        
           |  |  | 445 |    if ( $$install{'build config'} ) {
 | 
        
           | 43 | rodolico | 446 |       my $config;
 | 
        
           |  |  | 447 |       my @fileList;
 | 
        
           | 37 | rodolico | 448 |   | 
        
           | 94 | rodolico | 449 |       # the order is important here as, if multiple files exist, latter ones can
 | 
        
           |  |  | 450 |       # overwrite values in the previous. We do a push so the first value pushed
 | 
        
           |  |  | 451 |       # is processed last, ie has higher priority.
 | 
        
           |  |  | 452 |       push @fileList, $install->{'configuration'}->{'old configuration file'};
 | 
        
           |  |  | 453 |       push @fileList, $install->{'configuration'}->{'configuration file'};
 | 
        
           |  |  | 454 |   | 
        
           |  |  | 455 |       my $seedFile = $install->{'configuration'}->{'configuration seed file'};
 | 
        
           |  |  | 456 |       if ( -e $seedFile  && &yesno( 'Add installation seed file? ' ) ) {
 | 
        
           | 43 | rodolico | 457 |          push @fileList, $seedFile;
 | 
        
           | 37 | rodolico | 458 |       } # if preload seed file
 | 
        
           |  |  | 459 |   | 
        
           | 92 | rodolico | 460 |       $config = &makeConfig( @fileList );
 | 
        
           | 54 | rodolico | 461 |       # if ScriptDirs and moduleDirs not populated, do so from our configuration
 | 
        
           | 88 | rodolico | 462 |       if ( ! $$config{'scriptDirs'} || ! scalar @{ $$config{'scriptDirs'} }  ) {
 | 
        
           | 90 | rodolico | 463 | #         my @temp = ( $$install{'files'}{'scripts'}{'target'} );
 | 
        
           |  |  | 464 | #         $$config{'scriptDirs'} = \@temp;
 | 
        
           |  |  | 465 |          $config->{'scriptDirs'} = [ $install->{'files'}->{'scripts'}->{'target'} ];
 | 
        
           |  |  | 466 |   | 
        
           | 54 | rodolico | 467 |       }
 | 
        
           | 88 | rodolico | 468 |       if ( ! $$config{'moduleDirs'} || ! @{ $$config{'moduleDirs'} }  ) {
 | 
        
           | 90 | rodolico | 469 |          $config->{'moduleDirs'} = [ $install->{'files'}->{'modules'}->{'target'} ];
 | 
        
           |  |  | 470 | #         $$config{'moduleDirs'} = [ $$install{'files'}{'modules'}{'target'} ];
 | 
        
           | 54 | rodolico | 471 |       }
 | 
        
           |  |  | 472 | #      print Dumper ($config ) ; die;
 | 
        
           | 43 | rodolico | 473 |       my $content = &showConf( $config );
 | 
        
           | 35 | rodolico | 474 |       print $content;
 | 
        
           | 37 | rodolico | 475 |       print &writeConfig( $$install{'configuration'}{'configuration file'} , $content ) . "\n"
 | 
        
           |  |  | 476 |          if ( &yesno( "Write the above configuration to $$install{'configuration'}{'configuration file'}?" ) );
 | 
        
           |  |  | 477 |    } # if we are building/merging configuration
 | 
        
           | 120 | rodolico | 478 |    if ( @feedback ) {
 | 
        
           |  |  | 479 |       print "We got some messages while doing the install, please review below\n" . join( "\n", @feedback ) . "\n";
 | 
        
           |  |  | 480 |    }
 | 
        
           |  |  | 481 |    if ( @messages ) {
 | 
        
           |  |  | 482 |       print "\n\nFollowing are some important messages for your installation\n" . join( "\n", @messages ) . "\n";
 | 
        
           |  |  | 483 |    }
 | 
        
           | 35 | rodolico | 484 | }
 | 
        
           | 34 | rodolico | 485 |   | 
        
           | 35 | rodolico | 486 | sub help {
 | 
        
           |  |  | 487 |    my $oses = join( ' ', keys %operatingSystems );
 | 
        
           |  |  | 488 |    print <<END
 | 
        
           |  |  | 489 | $0 --verbose=x --os="osname" --dryrun --help --version
 | 
        
           | 34 | rodolico | 490 |   | 
        
           | 35 | rodolico | 491 | --os      - osname is one of [$oses]
 | 
        
           |  |  | 492 | --dryrun  - do not actually do anything, just tell you what I'd do
 | 
        
           |  |  | 493 | --verbose - x is 0 (normal) to 3 (horrible)
 | 
        
           |  |  | 494 | END
 | 
        
           |  |  | 495 | }
 | 
        
           | 34 | rodolico | 496 |   | 
        
           | 35 | rodolico | 497 |   | 
        
           |  |  | 498 |   | 
        
           |  |  | 499 | ##########################################
 | 
        
           |  |  | 500 | # Main Loop
 | 
        
           |  |  | 501 | ##########################################
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 | # handle any command line parameters that may have been passed in
 | 
        
           |  |  | 504 |   | 
        
           |  |  | 505 | GetOptions (
 | 
        
           |  |  | 506 |             "verbose|v=i" => \$verbose, # verbosity level, 0-9
 | 
        
           |  |  | 507 |             "os|o=s"      => \$os,      # pass in the operating system
 | 
        
           |  |  | 508 |             "dryrun|n"    => \$dryRun,  # do NOT actually do anything
 | 
        
           |  |  | 509 |             'help|h'      => \$help,
 | 
        
           |  |  | 510 |             'version|V'   => \$version
 | 
        
           |  |  | 511 |             ) or die "Error parsing command line\n";
 | 
        
           |  |  | 512 |   | 
        
           |  |  | 513 | if ( $help ) { &help() ; exit; }
 | 
        
           |  |  | 514 | if ( $version ) { print "$0 version $VERSION\n"; exit; }
 | 
        
           |  |  | 515 | &setDryRun( $dryRun ); # tell the library whether this is a dry run or not
 | 
        
           |  |  | 516 |   | 
        
           | 107 | rodolico | 517 | $os = `$sourceDir/determineOS` unless $os;
 | 
        
           | 94 | rodolico | 518 | # figure out if we know our operating system and set up special stuff for it
 | 
        
           | 115 | rodolico | 519 |   | 
        
           | 94 | rodolico | 520 | $os = &setUpOperatingSystemSpecific( \%install, \%operatingSystems, $os );
 | 
        
           | 33 | rodolico | 521 |   | 
        
           | 115 | rodolico | 522 | #$os = &setUpOperatingSystemSpecific( \%install, \%operatingSystems, 'freebsd' );
 | 
        
           |  |  | 523 | #print Dumper( \%install ); die;
 | 
        
           |  |  | 524 |   | 
        
           | 42 | rodolico | 525 | &validateLibraries( \%libraries, $os );
 | 
        
           |  |  | 526 |   | 
        
           | 35 | rodolico | 527 | $installType = &getInstallActions( \%install );
 | 
        
           | 34 | rodolico | 528 |   | 
        
           | 33 | rodolico | 529 | # based on the defaults, flesh out the install hash
 | 
        
           |  |  | 530 | $status = &populateSourceDir( \%install, $sourceDir );
 | 
        
           |  |  | 531 |   | 
        
           | 35 | rodolico | 532 |   | 
        
           | 33 | rodolico | 533 | $status = &getVersions( \%install );
 | 
        
           |  |  | 534 |   | 
        
           | 35 | rodolico | 535 | &showWork( \%install );
 | 
        
           |  |  | 536 | die unless &yesno( "Ready to run? Select No to abort." );
 | 
        
           |  |  | 537 |   | 
        
           | 94 | rodolico | 538 | #my $config = &makeConfig( $install{'configuration'}{'configuration seed file'}, $install{'configuration'}{'configuration file'}, $install{'configuration'}{'old configuration file'} );
 | 
        
           |  |  | 539 | #print Dumper( $config );
 | 
        
           |  |  | 540 | #die;
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |   | 
        
           | 33 | rodolico | 543 | $status = &doInstall( \%install );
 | 
        
           |  |  | 544 |   | 
        
           | 35 | rodolico | 545 | $status = &postInstall( \%install );
 | 
        
           | 34 | rodolico | 546 |   | 
        
           | 57 | rodolico | 547 | # create uninstaller script
 | 
        
           |  |  | 548 | # find the last non-space string in the crontab value. This is the target of the link
 | 
        
           |  |  | 549 | $install{'crontab'} =~ m/([^ ]+)$/;
 | 
        
           | 94 | rodolico | 550 | my $uninstall = "#! /usr/bin/env sh\n\n# Uninstall syinfo\nrm -fR $install{'bindir'} $1\n";
 | 
        
           | 57 | rodolico | 551 | my $outFileName = $install{'bindir'} . '/uninstall';
 | 
        
           |  |  | 552 | open UNINSTALL, ">$outFileName" or die "could not write to $outFileName: $!\n";
 | 
        
           |  |  | 553 | print UNINSTALL $uninstall;
 | 
        
           |  |  | 554 | close UNINSTALL;
 | 
        
           |  |  | 555 | qx ( chmod 700 $outFileName );
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 | print "Uninstall script has been created at $outFileName\n";
 | 
        
           |  |  | 558 |   | 
        
           | 37 | rodolico | 559 | if ( ( -x $install{'configuration'}{'configurator'} ) && $install{'build config'} ) {
 | 
        
           |  |  | 560 |    exec( $install{'configuration'}{'configurator'} );
 | 
        
           |  |  | 561 | } else {
 | 
        
           |  |  | 562 |    print "Done, you should check the files in $install{'bindir'} and $install{'confdir'} before running\n";
 | 
        
           |  |  | 563 |    print "If you need help configuring, the helper app at\n$install{'configuration'}{'configurator'}\ncan be used.\n";
 | 
        
           |  |  | 564 | }
 | 
        
           |  |  | 565 |   | 
        
           | 35 | rodolico | 566 | 1;   
 | 
        
           | 40 | rodolico | 567 |   | 
        
           |  |  | 568 |   | 
        
           |  |  | 569 | # clean will look for any file in bindir which is NOT in the list of available files and remove them
 | 
        
           |  |  | 570 | # if files already exist in install, preserve their permissions
 |