#!/usr/bin/perl # # Voicetronix installer script # # This script performs a fully automated installation on the Voicetronix # logger software, for logging Primary Rate ISDN (in either CAS or CCS # mode), or analogue PTSN. # # It allows easy set up of logger in one of several standard # configurations, or selection of individual options for installation in # a non-standard environment. # # The script is self updating - it will check for a new version of # itself before starting on anything else, and execute that instead. # BEGIN { eval { require LWP::Simple; }; if ($@) { system("apt-get install -y libwww-perl") == 0 or die "failed to install libwww-perl package"; } } ###################################################################### # Includes ###################################################################### # these should be standard use Getopt::Long; use Switch; use File::Temp qw/ tempfile /; use LWP::Simple; use LWP::UserAgent; use Cwd; use File::Copy; ###################################################################### # Constants ###################################################################### use constant VERSION => 2008070401; use constant SELFURL => 'http://www.voicetronix.com/Downloads/installers/logger_installer'; use constant INFOURL => 'http://www.voicetronix.com/Downloads/installers/version'; ###################################################################### # Global Variables ###################################################################### my %clo; # command line options # list of packages that must be installed if they are not already my @install = qw(ssh build-essential libncurses5-dev flex bison fakeroot libapache2-mod-perl2 libapache2-mod-apreq2 libapache2-request-perl libapache-session-perl libtemplate-perl libconfig-inifiles-perl sox lame genisoimage wodim apache2 debhelper pciutils-dev zlib1g-dev doxygen graphviz perl-suid ntp tmpreaper sysstat fail2ban openssl ssl-cert libsqlite3-dev sqlite3 acpid); # list of packages that must be removed if they are installed my @uninstall = qw(); # list of components (in the correct order for installation) my @components = qw(wanpipe vpb-driver vtlogger web-mvc logger-web); # information about components. This hash is populated by the # download_version_info function, which retrieves the required # information from the Voicetronix website. Format of the variable # is: # %component_info = ( # 'wanpipe' => { 'version' => 'x.x.x', 'url' => 'http://...' }, # 'vpb-driver' => { 'version' => 'x.x.x', 'url' => 'http://...' }, # 'vtlogger' => { 'version' => 'x.x.x', 'url' => 'http://...' }, # 'web-mvc' => { 'version' => 'x.x.x', 'url' => 'http://...' }, # 'logger-web' => { 'version' => 'x.x.x', 'url' => 'http://...' }, # ); # # Keys should correspond to the components listed in the @components # array. my %component_info = (); # logger technology: analogue or isdn my $tech; # logger style: novox (activate recordings based on signalling) or vox # (voice activated recording) my $style; # the number of channels my $channels; # a regex that matches extensions for the phone system being logged my $extension_regex = undef; ###################################################################### # Code Start ###################################################################### GetOptions( \%clo, 'help|h', 'version|v', 'dontupdate|d', 'multi-file-write|mfw', 'country=s', ) or Usage("invalid options"); Version() if $clo{'version'}; Usage() if $clo{'help'}; sub Version { print VERSION."\n"; exit 0; } sub Usage { my $problem = shift; print $problem."\n\n" if $problem; print "Usage: $0 [options]\n", < Specify country code (eg. 61 for AU, 1 for US) EOT exit 1; } &update_self() unless $clo{'dontupdate'}; &download_version_info(); &print_introduction(); &update_system(); &main_menu(); &print_afterwords(); exit 0; ###################################################################### # FUNCTION: update_self # DESCRIPTION: Download the latest version of this script and check # whether or not it is later than the running one. If # it is later, then execution switches to the new one # immediately. sub update_self() { my $version = VERSION; my $available_version = 0; my ($tmpfile, $tmpfilename) = tempfile(); my $ua = LWP::UserAgent->new; my $response = $ua->request(HTTP::Request->new(GET => SELFURL), sub { my $chunk = shift; print $tmpfile $chunk; } ); if ($response->is_success) { $available_version = `perl $tmpfilename --version`; chomp $available_version; } if ($version < $available_version) { print "New installer available, updating..."; # copy the new version overtop of this version, and restart open IN, "<$tmpfilename"; open OUT, ">$0"; while () { print OUT $_; } close(IN); close(OUT); unlink($tmpfilename); close($tmpfile); print " done.\n"; print "Running installer version $available_version.\n\n"; exec "perl $0"; } else { unlink($tmpfilename); close($tmpfile); } } ###################################################################### # FUNCTION: update_system # DESCRIPTION: This function ensures that the system matches that # which this script is expecting. There is a list of # packages that are expected to be installed, a list of # packages that are expected to NOT be installed, and # it is expected that all installed packages are up to # date including security updates. # GLOBALS: @install - list of packages that should be installed # @uninstall - list of packages that should be removed sub update_system() { # 1. ensure sources.list is appropriate # XXX: this one's difficult without constructing the file from scratch # ourselves... leave it for now. print "Performing system update (may take a while)... "; # 2. perform an apt-get update system('apt-get update >/dev/null'); # 3. install an appropriate kernel package if necessary. NOTE that we # save the output so we can work out later whether or not the kernel has # been upgraded, since this requires a reboot in order to install and # use the correct kernel headers my $output = `apt-get install -y linux-image-server`; # 4. perform an apt-get dist-upgrade system('apt-get dist-upgrade -y'); print "done.\n"; print "Removing conflicting packages... "; # 5. remove packages in the @uninstall list system('apt-get remove -y '.join(' ', @uninstall)); print "done.\n"; print "Installing new packages (may take a while)... "; # 6. install packages in the @install list my $packages = join(' ', @install); system("apt-get install -y $packages"); print "done.\n"; # check whether we upgraded the kernel (linux-image-* package) # because we need to reboot before continuing if we did if ($output !~ /linux-image-server is already the newest version/) { print <>>> PRESS ENTER TO REBOOT <<<< EOT my $key = <>; system("/sbin/shutdown -r now"); exit; } # 7. install an appropriate linux-headers package my $uname = `uname -r`; chomp($uname); print "Installing kernel headers (version $uname)... "; $output .= `apt-get install -y linux-headers-$uname`; print "done.\n"; } ###################################################################### # FUNCTION: download_version_info # DESCRIPTION: Grab information on the latest tested versions of logger # and the vpb-driver package from a specially crafted file # on the Voicetronix website. # File should be something like this: # # COMPONENT|VERSION|URL # vtlogger|1.4.2|http://www.voicetronix.com/Downloads/logger-1.x/vtlogger-1.4.2.tar.gz # driver|4.2.27|http://www.voicetronix.com/Downloads/vpb-driver-4.x/vpb-driver-4.2.27.tar.gz # wanpipe|3.3.1|http://www.voicetronix.com/Downloads/wanpipe/wanpipe-3.3.1.tgz # logger-web|3.0.10|http://www.voicetronix.com/Downloads/logger-1.x/logger-web-3.0.10.tar.gz # web-mvc|1.0.2|http://www.voicetronix.com/Downloads/logger-1.x/web-mvc-1.0.2.tar.gz # # GLOBALS: Downloaded info is saved to %component_info sub download_version_info { my $ua = LWP::UserAgent->new; my $response = $ua->request(HTTP::Request->new(GET => INFOURL), sub { my $chunk = shift; my @lines = split("\n", $chunk); foreach $chunk (@lines) { next if $chunk =~ /^#/; my @info = split('\|', $chunk); $component_info{$info[0]} = { 'version' => $info[1], 'url' => $info[2], }; } } ); unless ($response->is_success) { print "Unable to download versions file (".INFOURL.")\n"; exit(1); } } ###################################################################### # FUNCTION: print_introduction # DESCRIPTION: Print introductory information about this installer and # what it installs. sub print_introduction { print <; } ###################################################################### # FUNCTION: print_afterwords # DESCRIPTION: Tell the user any closing remarks necessary about the # installation they've just done. sub print_afterwords { print <; } } ###################################################################### # FUNCTION: main_menu # DESCRIPTION: Get choices from the user and action them. sub main_menu { print qq{ Main Menu ========= Please select the logger configuration you require: 1. E1 ISDN CCS Logger 2. E1 ISDN CAS Logger (voice activated recording) 3. T1/J1 ISDN CCS Logger 4. T1/J1 ISDN CAS Logger (voice activated recording) 5. Analogue Logger 6. Analogue Logger with voice activated recording Enter 1 - 6: }; my $selection = <>; chomp($selection); switch ($selection) { case 1 { $tech = 'e1'; $style = 'novox'; &select_extension_regex(); } case 2 { $tech = 'e1'; $style = 'vox'; } case 3 { $tech = 't1'; $style = 'novox'; &select_extension_regex(); } case 4 { $tech = 't1'; $style = 'vox'; } case 5 { $tech = 'analogue'; $style = 'novox'; @components = grep(!/wanpipe/, @components); } case 6 { $tech = 'analogue'; $style = 'vox'; @components = grep(!/wanpipe/, @components); } else { print "$selection is not a valid choice. Exiting.\n"; exit 1; } } &select_country_code(); &select_other_options(); foreach my $component (@components) { &install_component( $component, $component_info{$component}->{'version'}, $component_info{$component}->{'url'}, ); } } ###################################################################### # FUNCTION: select_extension_regex # DESCRIPTION: Provide a brief explanation of what the extension # regex is for and how to construct it, then prompt them # for it. This function should not be called unless an # extension regex is required. # GLOBALS: Saves the entered regex into $extension_regex sub select_extension_regex { print qq{ The configuration you have chosen stores call logs in per extension directories. In order to know which is the extension and which is the external number, you will need to provide information about the extension numbers in use on the system being logged. This takes the form of a regular expression that matches extensions and only extensions. For example, if extensions match the pattern of 812777 followed by any two digits, the regular expression could be: ^812777[0-9][0-9]\$ The different parts of this are: ^ : means "this is the start of the number" \$ : means "this is the end of the number" [0-9] : means any digit between 0 and 9 Please enter a regular expression that matches the extensions on the system to be logged. If you require assistance with this setting, just press ENTER and contact Voicetronix (by email to support\@voicetronix.com) for assistance. Your regular expression: }; my $re = <>; chomp $re; if ($re ne "") { $extension_regex = $re; } else { $extension_regex = '^[1-9][0-9]{4}$'; } } ###################################################################### # FUNCTION: select_country_code # DESCRIPTION: Prompt the user for their country code, for use in the # /etc/vpb/vpb.conf file. sub select_country_code { unless ($clo{'country'}) { print qq{ Please enter your country code (eg. 61 for AU, 1 for US): }; $clo{'country'} = <>; } } ###################################################################### # FUNCTION: select_other_options # DESCRIPTION: Prompt the user for non-standard configuration options # and set configuration options to indicate what they # want. sub select_other_options { my $done = 0; do { my $mfw = $clo{'multi-file-write'} ? 'yes' : 'no'; print qq{ The following extra options are selected: 1. Multi file write: $mfw Enter an item number to change, or Q to accept these settings: }; my $selection = <>; chomp($selection); switch ($selection) { case 1 { $clo{'multi-file-write'} = !$clo{'multi-file-write'}; } case /Q/i { $done = 1; } } } while (!$done); } ###################################################################### # FUNCTION: install_component # DESCRIPTION: This function calls the appropriate function to install # the required version of a component. Add extra parts # to this function when a new version of a component is # fully tested. # PARAMETERS: $component - the name of the component # $version - the version of the component to install # $url - the location of the component source tarball # RETURNS: undef on success # error explanation on failure sub install_component { my $component = shift; my $version = shift; my $url = shift; my $retval = undef; print "Downloading $component from $url..."; # download the file if we don't already have it unless (-s "$component-$version.tar.gz") { my $rc = getstore($url, "$component-$version.tar.gz"); if ($rc != 200) { $retval = qq(Unable to retrieve $component. Response code was $rc); } } print " done.\n"; print "Extracting and installing $component..."; system("rm -rf $component-$version"); system("tar zxf $component-$version.tar.gz"); my $topdir = &Cwd::cwd(); chdir("$component-$version"); switch ($component) { case 'wanpipe' { if ($tech =~ /^[te]1$/ ) { switch ($version) { case '3.2.1' { $retval = &install_wanpipe_3_2_1(); } case '3.3.1' { $retval = &install_wanpipe_3_3_1(); } case '3.3.10' { $retval = &install_wanpipe_3_3_10(); } else { $retval = qq(Version "$version" of "$component" is not supported by this installer"); } } } } case 'vpb-driver' { switch ($version) { case '4.2.27' { $retval = &install_vpb_driver_4_2_27(); } case /4\.2\.27\.test/ { $retval = &install_vpb_driver_4_2_27(); } case '4.2.28' { $retval = &install_vpb_driver_4_2_28(); } case '4.2.29' { $retval = &install_vpb_driver_4_2_29(); } case '4.2.32' { $retval = &install_vpb_driver_4_2_32(); } case '4.2.33' { $retval = &install_vpb_driver_4_2_33(); } case '4.2.34' { $retval = &install_vpb_driver_4_2_34(); } else { $retval = qq(Version "$version" of "$component" is not supported by this installer"); } } } case 'vtlogger' { switch ($version) { case '1.4.2' { $retval = &install_vtlogger_1_4_2(); } case '1.4.3' { $retval = &install_vtlogger_1_4_3(); } case '1.4.4' { $retval = &install_vtlogger_1_4_4(); } case '1.4.5' { $retval = &install_vtlogger_1_4_5(); } case '1.4.6' { $retval = &install_vtlogger_1_4_6(); } else { $retval = qq(Version "$version" of "$component" is not supported by this installer"); } } } case 'web-mvc' { switch ($version) { case '1.0.2' { $retval = &install_web_mvc_1_0_2(); } else { $retval = qq(Version "$version" of "$component" is not supported by this installer"); } } } case 'logger-web' { switch ($version) { case '3.0.10' { $retval = &install_logger_web_3_0_10(); } case '3.0.11' { $retval = &install_logger_web_3_0_11(); } else { $retval = qq(Version "$version" of "$component" is not supported by this installer"); } } } else { $retval = qq(Invalid component "$component"); } } chdir($topdir); if (defined($retval)) { print "$component: failure ($retval).\n"; print "Exiting.\n"; exit 1; } else { print " done.\n"; } return $retval; } sub install_wanpipe_3_2_1 { system("./Setup install --silent --protocol=AFT_TE1") == 0 or return "Setup failed"; system("wanrouter stop"); return undef; } sub install_wanpipe_3_3_1 { return &install_wanpipe_3_2_1(@_); } sub install_wanpipe_3_3_10 { system("bash ./Setup install --silent --protocol=AFT_TE1") == 0 or return "Setup failed"; system("wanrouter stop"); return undef; } sub install_vpb_driver_4_2_27 { my $flags = ""; my $configuratorflags = ""; if (($style eq 'vox') and ($tech =~ /^[te]1$/)) { $flags .= '--enable-caslog '; } if ($tech =~ /^[te]1$/) { $flags .= '--with-pri '; $configuratorflags .= " --isdn=$tech"; } if ($clo{'country'}) { $configuratorflags .= " --country=$clo{country}"; } if (($tech eq 't1') && ($style eq 'vox')) { open IN, "src/libvpb/openpri.cpp"; open OUT, ">src/libvpb/openpri.cpp.new"; while () { s/PRI_LAYER_1_ALAW/PRI_LAYER_1_ULAW/g; print OUT $_; } close IN; close OUT; rename "src/libvpb/openpri.cpp.new", "src/libvpb/openpri.cpp"; } system("./configure $flags") == 0 or return "configure $flags failed"; system("make") == 0 or return "make failed"; system("make install") == 0 or return "make install failed"; system("rm -rf /etc/vpb"); system("./src/utils/VpbConfigurator --logger $configuratorflags") == 0 or return "VpbConfigurator failed"; # now that we have all the necessary configuration files, we can start # up wanrouter for ISDN loggers if ($tech =~ /^[te]1$/) { system("wanrouter start") == 0 or return "wanrouter start failed"; } if ($tech eq 'analogue') { system("modprobe vtopenpci") == 0 or return "modprobe vtopenpci failed"; } # vpbconf is required later for counting the number of channels copy("vpb-detect/vpbconf", "/usr/local/bin/vpbconf"); chmod 0755, "/usr/local/bin/vpbconf"; return undef; } sub install_vpb_driver_4_2_28 { return &install_vpb_driver_4_2_27; } sub install_vpb_driver_4_2_29 { return &install_vpb_driver_4_2_27; } sub install_vpb_driver_4_2_32 { my $flags = ""; my $configuratorflags = ""; if (($style eq 'vox') and ($tech =~ /^[te]1$/)) { $flags .= '--enable-caslog '; $configuratorflags .= " --isdnsig=CAS"; } elsif ($tech =~ /^[te]1$/) { $configuratorflags .= " --isdnsig=CCS"; } if ($tech =~ /^[te]1$/) { $flags .= '--with-pri '; $configuratorflags .= " --isdn=$tech"; } if ($clo{'country'}) { $configuratorflags .= " --country=$clo{country}"; } if (($tech eq 't1') && ($style eq 'vox')) { open IN, "src/libvpb/openpri.cpp"; open OUT, ">src/libvpb/openpri.cpp.new"; while () { s/PRI_LAYER_1_ALAW/PRI_LAYER_1_ULAW/g; print OUT $_; } close IN; close OUT; rename "src/libvpb/openpri.cpp.new", "src/libvpb/openpri.cpp"; } system("./configure $flags") == 0 or return "configure $flags failed"; system("make") == 0 or return "make failed"; system("make install") == 0 or return "make install failed"; system("rm -rf /etc/vpb"); system("./src/utils/VpbConfigurator --logger $configuratorflags") == 0 or return "VpbConfigurator failed"; # now that we have all the necessary configuration files, we can start # up wanrouter for ISDN loggers if ($tech =~ /^[te]1$/) { system("wanrouter start") == 0 or return "wanrouter start failed"; } if ($tech eq 'analogue') { system("modprobe vtopenpci") == 0 or return "modprobe vtopenpci failed"; } # vpbconf is required later for counting the number of channels copy("vpb-detect/vpbconf", "/usr/local/bin/vpbconf"); chmod 0755, "/usr/local/bin/vpbconf"; return undef; } sub install_vpb_driver_4_2_33 { my $flags = ""; my $configuratorflags = ""; if (($style eq 'vox') and ($tech =~ /^[te]1$/)) { $flags .= '--enable-caslog '; $configuratorflags .= " --isdnsig=CAS"; } elsif ($tech =~ /^[te]1$/) { $configuratorflags .= " --isdnsig=CCS"; } if ($tech =~ /^[te]1$/) { $flags .= '--with-pri '; $configuratorflags .= " --isdn=$tech"; } if ($clo{'country'}) { $configuratorflags .= " --country=$clo{country}"; } system("./configure $flags") == 0 or return "configure $flags failed"; system("make") == 0 or return "make failed"; system("make install") == 0 or return "make install failed"; system("rm -rf /etc/vpb"); system("./src/utils/VpbConfigurator --logger $configuratorflags") == 0 or return "VpbConfigurator failed"; # now that we have all the necessary configuration files, we can start # up wanrouter for ISDN loggers if ($tech =~ /^[te]1$/) { system("wanrouter start") == 0 or return "wanrouter start failed"; } if ($tech eq 'analogue') { system("modprobe vtopenpci") == 0 or return "modprobe vtopenpci failed"; } # vpbconf is required later for counting the number of channels copy("vpb-detect/vpbconf", "/usr/local/bin/vpbconf"); chmod 0755, "/usr/local/bin/vpbconf"; return undef; } sub install_vpb_driver_4_2_34 { return &install_vpb_driver_4_2_33; } sub install_vtlogger_1_4_2 { my $flags = ''; if ($style eq 'vox') { $flags .= '--enable-vox-detect --enable-channel-dirs '; } elsif ($tech =~ /^[te]1$/) { $flags .= '--enable-extension-dirs '; } else { # analogue without vox detection $flags .= '--enable-channel-dirs '; } $flags .= '--with-sqlite '; if ($clo{'multi-file-write'}) { $flags .= '--with-mfw=2'; } system("./configure $flags") == 0 or return "configure $flags failed"; system("make") == 0 or return "make failed"; system("make install") == 0 or return "make install failed"; system("./utils/vtlogger_createdb") == 0 or return "vtlogger_createdb failed"; mkdir "/etc/vtlogger"; $channels = &count_channels(); system("./utils/genconf.pl $channels > /etc/vtlogger/channel.conf") == 0 or return "genconf.pl failed"; copy("./utils/init.d-script", "/etc/init.d/vtlogger"); chmod 0755, "/etc/init.d/vtlogger"; system("update-rc.d vtlogger start 50 2 3 4 5 . stop 50 0 1 6 .") == 0 or return "update-rc.d failed"; copy("./utils/vtlogpruner.sh", "/usr/local/bin/vtlogpruner"); chmod 0755,"/usr/local/bin/vtlogpruner"; open CRON, ">/etc/cron.daily/vtlogger"; print CRON </etc/default/vtlogger"; print DEFAULT </etc/cron.daily/vtlogger"; print CRON </etc/default/vtlogger"; print DEFAULT <