Changed filename to postinstall-new
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/7ba725b1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/7ba725b1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/7ba725b1 Branch: refs/heads/master Commit: 7ba725b101e2bc2f4ebfdd183b2c353b7374e077 Parents: 7054561 Author: peryder <pery...@cisco.com> Authored: Wed Dec 7 10:59:30 2016 -0500 Committer: Dan Kirkwood <dang...@gmail.com> Committed: Fri Jan 27 09:52:53 2017 -0700 ---------------------------------------------------------------------- traffic_ops/install/bin/postinstall-new | 781 +++++++++++++++++++ .../install/bin/postinstall-new-integrated | 781 ------------------- 2 files changed, 781 insertions(+), 781 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7ba725b1/traffic_ops/install/bin/postinstall-new ---------------------------------------------------------------------- diff --git a/traffic_ops/install/bin/postinstall-new b/traffic_ops/install/bin/postinstall-new new file mode 100755 index 0000000..9972daa --- /dev/null +++ b/traffic_ops/install/bin/postinstall-new @@ -0,0 +1,781 @@ +#!/usr/bin/perl + +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/install/lib/perl5 /opt/traffic_ops/app/local/lib/perl5 /opt/traffic_ops/app/lib); +$ENV{PATH} = "/opt/traffic_ops/install/bin:$ENV{PATH}"; +$ENV{PERL5LIB} = "/opt/traffic_ops/install/lib:/opt/traffic_ops/install/lib/perl5:/opt/traffic_ops/app/local/lib/perl5:/opt/traffic_ops/app/lib"; + +use strict; +use warnings; + +use Safe; +use POSIX; +use File::Basename qw{dirname}; +use File::Path qw{make_path}; +use InstallUtils qw{ :all }; +use BuildPerlDeps qw{ :all }; +use GenerateCert qw{ :all }; +use ProfileCleanup qw { :all }; +use Digest::SHA1 qw(sha1_hex); +use Data::Dumper qw(Dumper); +use Scalar::Util qw(looks_like_number); +use Getopt::Long; + +# paths of the output configuration files +our $databaseConfFile = "/opt/traffic_ops/app/conf/production/database.conf"; +our $dbConfFile = "/opt/traffic_ops/app/db/dbconf.yml"; +our $cdnConfFile = "/opt/traffic_ops/app/conf/cdn.conf"; +our $ldapConfFile = "/opt/traffic_ops/app/conf/ldap.conf"; +our $usersConfFile = "/opt/traffic_ops/install/data/json/users.json"; +our $profilesConfFile = "/opt/traffic_ops/install/data/profiles/"; +our $opensslConfFile = "/opt/traffic_ops/install/bin/openssl_configuration.json"; +our $paramConfFile = "/opt/traffic_ops/install/data/json/profiles.json"; + +our $profile_dir = "/opt/traffic_ops/install/data/profiles/"; +our $post_install_cfg = "/opt/traffic_ops/install/data/json/post_install.json"; + +our $reconfigure_defaults = "/opt/traffic_ops/.reconfigure_defaults"; + +our $parameters; + +# old way of reconfiguring postinstall - only here to check for file and let user know it is deprecated +my $reconfigure_file = "/opt/traffic_ops/.reconfigure"; + +# whether or not to reconfigure traffic ops +my $reconfigure; + +# whether to create a config file with default values +my $dumpDefaults; + +# log file for the installer +our $logFile = "/var/log/traffic_ops/postinstall.log"; + +# maximum size the uncompressed log file should be before rotating it - rotating it copies the current log +# file to the same name appended with .bkp replacing the old backup if any is there +my $maxLogSize = 1000000; #bytes + +# log file for cpan - this log becomes large and is rotated every install +our $cpanLogFile = "/var/log/traffic_ops/cpan.log"; + +# configuration file output with answers which can be used as input to postinstall +our $outputConfigFile = "/var/log/traffic_ops/configuration_file.json"; + +sub getDbDriver { + return "mymysql"; +} + +sub getInstallPath { + my $relPath = shift; + return join( '/', "/tmp/traffic_ops", $relPath ); +} + +# given a var to the hash of config_var and question, will return the question +sub getConfigQuestion { + my $var = shift; + foreach my $key ( keys $var ) { + if ( $key ne "hidden" && $key ne "config_var" ) { + return $key; + } + } +} + +# question: The question given in the config file +# config_answer: The answer given in the config file - if no config file given will be defaultInput +# hidden: Whether or not the answer should be hidden from the terminal and logs, ex. passwords +# +# Determines if the script is being run in complete interactive mode and prompts user - otherwise +# returns answer to question in config or defaults + +sub getField { + my $question = shift; + my $config_answer = shift; + my $hidden = shift; + + # if there is no config file and not in automatic mode prompt for all questions with default answers + if ( !$::inputFile && !$::automatic ) { + + # if hidden then dont show password in terminal + if ($hidden) { + return promptPasswordVerify($question); + } + else { + return promptUser( $question, $config_answer ); + } + } + + return $config_answer; +} + +# userInput: The entire input config file which is either user input or the defaults +# fileName: The name of the output config file given by the input config file +# +# Loops through an input config file and determines answers to each question using getField +# and returns the hash of answers + +sub getConfig { + my $userInput = shift; + my $fileName = shift; + + my %config; + + if ( !defined $userInput->{$fileName} ) { + logger( "No $fileName found in config", "error" ); + } + + logger( "===========$fileName===========", "info" ); + + foreach my $var ( @{ $userInput->{$fileName} } ) { + my $question = getConfigQuestion($var); + my $hidden = $var->{"hidden"} if ( exists $var->{"hidden"} ); + my $answer = $config{ $var->{"config_var"} } = getField( $question, $var->{$question}, $hidden ); + + $config{ $var->{"config_var"} } = $answer; + if ( !$hidden ) { + logger( "$question: $answer", "info" ); + } + } + return %config; +} + +# userInput: The entire input config file which is either user input or the defaults +# dbFileName: The filename of the output config file for the database +# toDBFileName: The filename of the output config file for the Traffic Ops database +# +# Generates a config file for the database based on the questions and answers in the input config file + +sub generateDbConf { + my $userInput = shift; + my $dbFileName = shift; + my $toDBFileName = shift; + + my %dbconf = getConfig( $userInput, $dbFileName ); + + make_path( dirname($dbFileName), { mode => 0755 } ); + writeJson( $dbFileName, \%dbconf ); + logger( "Database configuration has been saved", "info" ); + + # broken out into separate file/config area + my %todbconf = getConfig( $userInput, $toDBFileName ); + + # No YAML library installed, but this is a simple file.. + open( my $fh, '>', $toDBFileName ) or errorOut("Can't write to $toDBFileName!"); + print $fh "version: 1.0\n"; + print $fh "name: dbconf.yml\n\n"; + print $fh "production:\n"; + print $fh " driver: ", getDbDriver() . "\n"; + print $fh " open: tcp:$dbconf{hostname}:$dbconf{port}*$dbconf{dbname}/$dbconf{user}/$dbconf{password}\n"; + close $fh; + + return \%todbconf; +} + +# userInput: The entire input config file which is either user input or the defaults +# fileName: The filename of the output config file +# +# Generates a config file for the CDN + +sub generateCdnConf { + my $userInput = shift; + my $fileName = shift; + + my %cdnConfiguration = getConfig( $userInput, $fileName ); + + # First, read existing one -- already loaded with a bunch of stuff + my $cdnConf; + if ( -f $fileName ) { + $cdnConf = Safe->new->rdo($fileName) or errorOut("Error loading $fileName: $@"); + } + if ( lc $cdnConfiguration{genSecret} =~ /^y(?:es)?/ ) { + my @secrets = @{ $cdnConf->{secrets} }; + my $newSecret = randomWord(); + unshift @secrets, randomWord(); + if ( $cdnConfiguration{keepSecrets} > 0 && $#secrets > $cdnConfiguration{keepSecrets} - 1 ) { + + # Shorten the array to requested length + $#secrets = $cdnConfiguration{keepSecrets} - 1; + } + } + writePerl( $fileName, $cdnConf ); +} + +# userInput: The entire input config file which is either user input or the defaults +# fileName: The filename of the output config file +# +# Generates an LDAP config file + +sub generateLdapConf { + my $userInput = shift; + my $fileName = shift; + + my $useLdap = $userInput->{$fileName}[0]->{"Do you want to set up LDAP?"}; + + if ( $useLdap eq "no" || $useLdap eq "n" ) { + logger( "Not setting up ldap", "info" ); + + return; + } + + my %ldapConf = getConfig( $userInput, $fileName ); + + make_path( dirname($fileName), { mode => 0755 } ); + writeJson( $fileName, \%ldapConf ); +} + +sub generateUsersConf { + my $userInput = shift; + my $fileName = shift; + + my %user = (); + my %config = getConfig( $userInput, $fileName ); + + $user{username} = $config{tmAdminUser}; + $user{password} = sha1_hex( $config{tmAdminPw} ); + + writeJson( $fileName, \%user ); + $user{password} = $config{tmAdminPw}; + return \%user; +} + +sub generateProfilesDir { + my $userInput = shift; + my $fileName = shift; + + my $userIn = $userInput->{$fileName}; +} + +sub generateOpenSSLConf { + my $userInput = shift; + my $fileName = shift; + + if ( !defined $userInput->{$fileName} ) { + logger( "No OpenSSL Configuration - questions will be asked", "info" ); + + # write an empty config so openssl does not use an old file + writeJson( $fileName, my %emptyConfig ); + return; + } + + my %config = getConfig( $userInput, $fileName ); + + writeJson( $fileName, \%config ); +} + +sub generateParamConf { + my $userInput = shift; + my $fileName = shift; + + my %config = getConfig( $userInput, $fileName ); + return \%config; +} + +# check default values for missing config_var parameter +sub sanityCheckDefaults { + foreach my $file ( ( keys $::defaultInputs ) ) { + foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) { + my $question = getConfigQuestion($defaultValue); + + if ( !defined $defaultValue->{"config_var"} + || $defaultValue->{"config_var"} eq "" ) + { + errorOut("Question '$question' in file '$file' has no config_var"); + } + } + } +} + +# userInput: The entire input config file which is either user input or the defaults +# +# Checks the input config file against the default inputs. If there is a question located in the default inputs which +# is not located in the input config file it will output a warning message. +# +# This does not check the other way meaning questions which are present in defaults but not present in the input config +# file will not be checked + +sub sanityCheckConfig { + my $userInput = shift; + my $diffs = 0; + + foreach my $file ( ( keys $::defaultInputs ) ) { + if ( !defined $userInput->{$file} ) { + logger( "File '$file' found in defaults but not config file", "warn" ); + $userInput->{$file} = []; + } + + foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) { + + my $found = 0; + foreach my $configValue ( @{ $userInput->{$file} } ) { + if ( $defaultValue->{"config_var"} eq $configValue->{"config_var"} ) { + $found = 1; + } + } + + # if the question is not found in the config file add it from defaults + if ( !$found ) { + my $question = getConfigQuestion($defaultValue); + logger( "Question '$question' found in defaults but not in '$file'", "warn" ); + + my %temp; + my $answer; + my $hidden = exists $defaultValue->{"hidden"} && $defaultValue->{"hidden"} ? 1 : 0; + + # in automatic mode add the missing question with default answer + if ($::automatic) { + $answer = $defaultValue->{$question}; + logger( "Adding question '$question' with default answer " . ( $hidden ? "" : "'$answer'" ), "info" ); + } + + # in interactive mode prompt the user for answer to missing question + else { + logger( "Prompting user for answer", "info" ); + if ($hidden) { + $answer = promptPasswordVerify($question); + } + else { + $answer = promptUser( $question, $defaultValue->{$question} ); + } + } + + %temp = ( + "config_var" => $defaultValue->{"config_var"}, + $question => $answer + ); + + if ($hidden) { + $temp{"hidden"} .= "true"; + } + + push $userInput->{$file}, \%temp; + + $diffs++; + } + } + } + + logger( "File sanity check complete - found $diffs difference" . ( $diffs == 1 ? "" : "s" ), "info" ); +} + +# A function which returns the default inputs data structure. These questions and answers will be used if there is no +# user input config file or if there are questions in the input config file which do not have answers + +sub getDefaults { + return { + $::databaseConfFile => [ + { + "Database type" => "mysql", + "config_var" => "type" + }, + { + "Database name" => "traffic_ops", + "config_var" => "dbname" + }, + { + "Database server hostname IP or FQDN" => "localhost", + "config_var" => "hostname" + }, + { + "Database port number" => "3306", + "config_var" => "port" + }, + { + "Traffic Ops database user" => "traffic_ops", + "config_var" => "user" + }, + { + "Password for Traffic Ops database user" => "", + "config_var" => "password", + "hidden" => "true" + } + ], + $::dbConfFile => [ + { + "Database server root (admin) user" => "root", + "config_var" => "dbAdminUser" + }, + { + "Password for database server admin" => "", + "config_var" => "dbAdminPw", + "hidden" => "true" + } + ], + $::cdnConfFile => [ + { + "Generate a new secret?" => "yes", + "config_var" => "genSecret" + }, + { + "Number of secrets to keep?" => "10", + "config_var" => "keepSecrets" + } + ], + $::ldapConfFile => [ + { + "Do you want to set up LDAP?" => "no", + "config_var" => "setupLdap" + }, + { + "LDAP server hostname" => "", + "config_var" => "hostname" + }, + { + "LDAP Admin DN" => "", + "config_var" => "admin_dn" + }, + { + "LDAP Admin Password" => "", + "config_var" => "password", + "hidden" => "true" + }, + { + "LDAP Search Base" => "", + "config_var" => "search_base" + } + ], + $::usersConfFile => [ + { + "Administration username for Traffic Ops" => "admin", + "config_var" => "tmAdminUser" + }, + { + "Password for the admin user" => "", + "config_var" => "tmAdminPw", + "hidden" => "true" + } + ], + $::profilesConfFile => [], + $::opensslConfFile => [ + { + "Country Name (2 letter code)" => "XX", + "config_var" => "country" + }, + { + "State or Province Name (full name)" => "San Jose", + "config_var" => "state" + }, + { + "Locality Name (eg, city)" => "Default City", + "config_var" => "locality" + }, + { + "Organization Name (eg, company)" => "Default Company Ltd", + "config_var" => "company" + }, + { + "Organizational Unit Name (eg, section)" => "", + "config_var" => "org_unit" + }, + { + "Common Name (eg, your name or your server's hostname)" => "cisco.com", + "config_var" => "common_name" + }, + { + "RSA Passphrase" => "", + "config_var" => "rsaPassword", + "hidden" => "true" + } + ], + $::paramConfFile => [ + { + "Traffic Ops url" => "https://localhost", + "config_var" => "tm.url" + }, + { + "Human-readable CDN Name. (No whitespace, please)" => "kabletown_cdn", + "config_var" => "cdn_name" + }, + { + "Health Polling Interval (milliseconds)" => "8000", + "config_var" => "health_polling_int" + }, + { + "DNS sub-domain for which your CDN is authoritative" => "cdn1.kabletown.net", + "config_var" => "dns_subdomain" + }, + { + "TLD SOA admin" => "traffic_ops", + "config_var" => "soa_admin" + }, + { + "TrafficServer Drive Prefix" => "/dev/sd", + "config_var" => "driver_prefix" + }, + { + "TrafficServer RAM Drive Prefix" => "/dev/ram", + "config_var" => "ram_drive_prefix" + }, + { + "TrafficServer RAM Drive Letters (comma separated)" => "0,1,2,3,4,5,6,7", + "config_var" => "ram_drive_letters" + }, + { + "Health Threshold Load Average" => "25", + "config_var" => "health_thresh_load_avg" + }, + { + "Health Threshold Available Bandwidth in Kbps" => "1750000", + "config_var" => "health_thresh_kbps" + }, + { + "Traffic Server Health Connection Timeout (milliseconds)" => "2000", + "config_var" => "health_connect_timeout" + } + + ] + }; +} + +# carried over from old postinstall +# +# todbconf: The database configuration to be used +# opensslconf: The openssl configuration if any + +sub setupDatabase { + my $todbconf = shift; + my $opensslconf = shift; + + # + # Call mysql initialization script. + # + logger( "Creating database with user: $todbconf->{dbAdminUser}", "info" ); + my $result = execCommand( "/opt/traffic_ops/install/bin/create_db", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} ); + if ( $result != 0 ) { + errorOut("Failed to create the database"); + } + + logger( "Setting up database", "info" ); + chdir("/opt/traffic_ops/app"); + $result = execCommand( "/usr/bin/perl", "db/admin.pl", "--env=production", "setup" ); + + if ( $result != 0 ) { + errorOut("Database initialization failed"); + } + else { + logger( "Database initialization succeeded", "info" ); + } + + $result = execCommand( "/opt/traffic_ops/install/bin/dataload", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} ); + if ( $result != 0 ) { + logger( "Failed to load seed data", "error" ); + } + + logger( "Downloading MaxMind data", "info" ); + chdir("/opt/traffic_ops/app/public/routing"); + $result = execCommand("/usr/bin/wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz"); + if ( $result != 0 ) { + logger( "Failed to download MaxMind data", "error" ); + } + + logger( "Copying coverage zone file to public dir", "info" ); + $result = execCommand("/bin/mv /opt/traffic_ops/app/public/coverage-zone.json ."); + if ( $result != 0 ) { + logger( "Failed to copy coverage zone file", "error" ); + } + + if ( -x "/usr/bin/openssl" ) { + logger( "Installing SSL Certificates", "info" ); + $result = GenerateCert::createCert($opensslconf); + + if ( $result != 0 ) { + errorOut("SSL Certificate Installation failed"); + } + else { + logger( "SSL Certificates have been installed", "info" ); + } + } + else { + logger( "Unable to install SSL certificates as openssl is not installed", "error" ); + logger( "Install openssl and then run /opt/traffic_ops/install/bin/generateCert to install SSL certificates", "error" ); + exit 4; + } + +} + +# -cfile - Input File: The input config file used to ask and answer questions +# -a - Automatic mode: If there are questions in the config file which do not have answers, the script +# will look to the defaults for the answer. If the answer is not in the defaults +# the script will exit +# -r - Reconfigure: Whether or not to reconfigure the database and check perl dependencies - This will rereate the database +# -defaults - Defaults: Writes out a configuration file with defaults which can be used as input +# -debug - Debug Mode: More output to the terminal +# -h - Help: Basic command line help menu + +sub main { + our $inputFile = ""; + our $automatic = 0; + our $debug = 0; + my $help = 0; + + my $usageString = "Usage: postinstall [-a] [-debug] [-defaults] [-r] -cfile=[config_file]\n"; + + GetOptions( + "cfile=s" => \$inputFile, + "automatic" => \$automatic, + "reconfigure" => \$reconfigure, + "defaults" => \$dumpDefaults, + "debug" => \$debug, + "help" => \$help + ) or die($usageString); + + # stores the default questions and answers + our $defaultInputs = getDefaults(); + + if ($help) { + print $usageString; + exit(0); + } + + # check if the user running postinstall is root + if ( $ENV{USER} ne "root" ) { + errorOut("You must run this script as the root user"); + } + + if ( -f "$logFile.gz" ) { + execCommand( "/bin/gunzip", "$logFile.gz" ); + } + + logger( "Starting postinstall", "info" ); + + logger( "Debug is on", "info" ); + + if ($::automatic) { + logger( "Running in automatic mode", "info" ); + } + + # check if the reconfigure_file is present on the system - if it is let the user know its deprecated + # and exit with an error + if ( -f $reconfigure_file ) { + logger( "$reconfigure_file file is reprecated - please remove and rerun postinstall", "error" ); + return; + } + + if ($dumpDefaults) { + logger( "Writing default configuration file to $outputConfigFile", "info" ); + writeJson( $outputConfigFile, $::defaultInputs ); + return; + } + + logger( "Postinstall " . ( defined $reconfigure ? "in" : "not" ) . " in reconfigure mode", "info" ); + + # check if the user has root access + if ( $ENV{USER} ne "root" ) { + errorOut("You must run this script as the root user"); + } + + rotateLog($cpanLogFile); + + if ( -s $::logFile > $maxLogSize ) { + logger( "Postinstall log above max size of $maxLogSize bytes - rotating", "info" ); + rotateLog($logFile); + } + + # used to store the questions and answers provided by the user + my $userInput; + + # if no input file provided use the defaults + if ( $::inputFile eq "" ) { + logger( "No input file given - using defaults", "info" ); + $userInput = $::defaultInputs; + } + else { + logger( "Using input file $::inputFile", "info" ); + + # check if the input file exists + errorOut("File '$::inputFile' not found") if ( !-f $::inputFile ); + + # read and store the input file + $userInput = readJson($::inputFile); + } + + # sanity check the defaults if running them automatically + sanityCheckDefaults(); + + # check the input config file against the defaults to check for missing questions + sanityCheckConfig($userInput) if ( $inputFile ne "" ); + + chdir("/opt/traffic_ops/install/bin"); + + # if the reconfigure file exists or reconfigure is set then rebuild the perl deps + if ( -f $reconfigure_file || $reconfigure ) { + my $rc = BuildPerlDeps::build(1); + if ( $rc != 0 ) { + errorOut("Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error"); + } + $rc = execCommand( "./download_web_deps", "-i" ); + if ( $rc != 0 ) { + errorOut("Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error"); + } + } + else { + my $rc = BuildPerlDeps::build(); + if ( $rc != 0 ) { + errorOut("Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error"); + } + $rc = execCommand( "./download_web_deps", "-i" ); + if ( $rc != 0 ) { + errorOut("Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error"); + } + } + + # The generator functions handle checking input/default/automatic mode + + # todbconf will be used later when setting up the database + my $todbconf = generateDbConf( $userInput, $::databaseConfFile, $::dbConfFile ); + generateCdnConf( $userInput, $::cdnConfFile ); + generateLdapConf( $userInput, $::ldapConfFile ); + my $adminconf = generateUsersConf( $userInput, $::usersConfFile ); + generateProfilesDir( $userInput, $::profilesConfFile ); + generateOpenSSLConf( $userInput, $::opensslConfFile ); + my $paramconf = generateParamConf( $userInput, $::paramConfFile ); + + # if the reconfigure file exists or the reconfigure command line arg is set then setup the database + if ( -f $reconfigure_file || $reconfigure ) { + if ($::automatic) { + setupDatabase( $todbconf, $::opensslConfFile ); + } + else { + setupDatabase( $todbconf, 0 ); + } + } + + # remove the reconfigure file if it exists + if ( -f $reconfigure_file ) { + logger( "Removing reconfigure file", "info" ); + unlink($reconfigure_file); + } + + logger( "Starting Traffic Ops", "info" ); + execCommand("/sbin/service traffic_ops start"); + + logger( "Waiting for Traffic Ops to start", "info" ); + + if ( !profiles_exist( $adminconf, $paramconf->{"tm.url"} ) ) { + logger( "Creating default profiles...", "info" ); + replace_profile_templates($paramconf); + import_profiles($adminconf); + profiles_exist( $adminconf, $paramconf->{"tm.url"} ); # call again to create $reconfigure_defaults file if import was successful + } + else { + logger( "Not creating default profiles", "info" ); + } + + logger("Postinstall complete"); + + execCommand( "/bin/gzip", "$logFile" ); +} + +main; + +# vi:syntax=perl http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7ba725b1/traffic_ops/install/bin/postinstall-new-integrated ---------------------------------------------------------------------- diff --git a/traffic_ops/install/bin/postinstall-new-integrated b/traffic_ops/install/bin/postinstall-new-integrated deleted file mode 100755 index 9972daa..0000000 --- a/traffic_ops/install/bin/postinstall-new-integrated +++ /dev/null @@ -1,781 +0,0 @@ -#!/usr/bin/perl - -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/install/lib/perl5 /opt/traffic_ops/app/local/lib/perl5 /opt/traffic_ops/app/lib); -$ENV{PATH} = "/opt/traffic_ops/install/bin:$ENV{PATH}"; -$ENV{PERL5LIB} = "/opt/traffic_ops/install/lib:/opt/traffic_ops/install/lib/perl5:/opt/traffic_ops/app/local/lib/perl5:/opt/traffic_ops/app/lib"; - -use strict; -use warnings; - -use Safe; -use POSIX; -use File::Basename qw{dirname}; -use File::Path qw{make_path}; -use InstallUtils qw{ :all }; -use BuildPerlDeps qw{ :all }; -use GenerateCert qw{ :all }; -use ProfileCleanup qw { :all }; -use Digest::SHA1 qw(sha1_hex); -use Data::Dumper qw(Dumper); -use Scalar::Util qw(looks_like_number); -use Getopt::Long; - -# paths of the output configuration files -our $databaseConfFile = "/opt/traffic_ops/app/conf/production/database.conf"; -our $dbConfFile = "/opt/traffic_ops/app/db/dbconf.yml"; -our $cdnConfFile = "/opt/traffic_ops/app/conf/cdn.conf"; -our $ldapConfFile = "/opt/traffic_ops/app/conf/ldap.conf"; -our $usersConfFile = "/opt/traffic_ops/install/data/json/users.json"; -our $profilesConfFile = "/opt/traffic_ops/install/data/profiles/"; -our $opensslConfFile = "/opt/traffic_ops/install/bin/openssl_configuration.json"; -our $paramConfFile = "/opt/traffic_ops/install/data/json/profiles.json"; - -our $profile_dir = "/opt/traffic_ops/install/data/profiles/"; -our $post_install_cfg = "/opt/traffic_ops/install/data/json/post_install.json"; - -our $reconfigure_defaults = "/opt/traffic_ops/.reconfigure_defaults"; - -our $parameters; - -# old way of reconfiguring postinstall - only here to check for file and let user know it is deprecated -my $reconfigure_file = "/opt/traffic_ops/.reconfigure"; - -# whether or not to reconfigure traffic ops -my $reconfigure; - -# whether to create a config file with default values -my $dumpDefaults; - -# log file for the installer -our $logFile = "/var/log/traffic_ops/postinstall.log"; - -# maximum size the uncompressed log file should be before rotating it - rotating it copies the current log -# file to the same name appended with .bkp replacing the old backup if any is there -my $maxLogSize = 1000000; #bytes - -# log file for cpan - this log becomes large and is rotated every install -our $cpanLogFile = "/var/log/traffic_ops/cpan.log"; - -# configuration file output with answers which can be used as input to postinstall -our $outputConfigFile = "/var/log/traffic_ops/configuration_file.json"; - -sub getDbDriver { - return "mymysql"; -} - -sub getInstallPath { - my $relPath = shift; - return join( '/', "/tmp/traffic_ops", $relPath ); -} - -# given a var to the hash of config_var and question, will return the question -sub getConfigQuestion { - my $var = shift; - foreach my $key ( keys $var ) { - if ( $key ne "hidden" && $key ne "config_var" ) { - return $key; - } - } -} - -# question: The question given in the config file -# config_answer: The answer given in the config file - if no config file given will be defaultInput -# hidden: Whether or not the answer should be hidden from the terminal and logs, ex. passwords -# -# Determines if the script is being run in complete interactive mode and prompts user - otherwise -# returns answer to question in config or defaults - -sub getField { - my $question = shift; - my $config_answer = shift; - my $hidden = shift; - - # if there is no config file and not in automatic mode prompt for all questions with default answers - if ( !$::inputFile && !$::automatic ) { - - # if hidden then dont show password in terminal - if ($hidden) { - return promptPasswordVerify($question); - } - else { - return promptUser( $question, $config_answer ); - } - } - - return $config_answer; -} - -# userInput: The entire input config file which is either user input or the defaults -# fileName: The name of the output config file given by the input config file -# -# Loops through an input config file and determines answers to each question using getField -# and returns the hash of answers - -sub getConfig { - my $userInput = shift; - my $fileName = shift; - - my %config; - - if ( !defined $userInput->{$fileName} ) { - logger( "No $fileName found in config", "error" ); - } - - logger( "===========$fileName===========", "info" ); - - foreach my $var ( @{ $userInput->{$fileName} } ) { - my $question = getConfigQuestion($var); - my $hidden = $var->{"hidden"} if ( exists $var->{"hidden"} ); - my $answer = $config{ $var->{"config_var"} } = getField( $question, $var->{$question}, $hidden ); - - $config{ $var->{"config_var"} } = $answer; - if ( !$hidden ) { - logger( "$question: $answer", "info" ); - } - } - return %config; -} - -# userInput: The entire input config file which is either user input or the defaults -# dbFileName: The filename of the output config file for the database -# toDBFileName: The filename of the output config file for the Traffic Ops database -# -# Generates a config file for the database based on the questions and answers in the input config file - -sub generateDbConf { - my $userInput = shift; - my $dbFileName = shift; - my $toDBFileName = shift; - - my %dbconf = getConfig( $userInput, $dbFileName ); - - make_path( dirname($dbFileName), { mode => 0755 } ); - writeJson( $dbFileName, \%dbconf ); - logger( "Database configuration has been saved", "info" ); - - # broken out into separate file/config area - my %todbconf = getConfig( $userInput, $toDBFileName ); - - # No YAML library installed, but this is a simple file.. - open( my $fh, '>', $toDBFileName ) or errorOut("Can't write to $toDBFileName!"); - print $fh "version: 1.0\n"; - print $fh "name: dbconf.yml\n\n"; - print $fh "production:\n"; - print $fh " driver: ", getDbDriver() . "\n"; - print $fh " open: tcp:$dbconf{hostname}:$dbconf{port}*$dbconf{dbname}/$dbconf{user}/$dbconf{password}\n"; - close $fh; - - return \%todbconf; -} - -# userInput: The entire input config file which is either user input or the defaults -# fileName: The filename of the output config file -# -# Generates a config file for the CDN - -sub generateCdnConf { - my $userInput = shift; - my $fileName = shift; - - my %cdnConfiguration = getConfig( $userInput, $fileName ); - - # First, read existing one -- already loaded with a bunch of stuff - my $cdnConf; - if ( -f $fileName ) { - $cdnConf = Safe->new->rdo($fileName) or errorOut("Error loading $fileName: $@"); - } - if ( lc $cdnConfiguration{genSecret} =~ /^y(?:es)?/ ) { - my @secrets = @{ $cdnConf->{secrets} }; - my $newSecret = randomWord(); - unshift @secrets, randomWord(); - if ( $cdnConfiguration{keepSecrets} > 0 && $#secrets > $cdnConfiguration{keepSecrets} - 1 ) { - - # Shorten the array to requested length - $#secrets = $cdnConfiguration{keepSecrets} - 1; - } - } - writePerl( $fileName, $cdnConf ); -} - -# userInput: The entire input config file which is either user input or the defaults -# fileName: The filename of the output config file -# -# Generates an LDAP config file - -sub generateLdapConf { - my $userInput = shift; - my $fileName = shift; - - my $useLdap = $userInput->{$fileName}[0]->{"Do you want to set up LDAP?"}; - - if ( $useLdap eq "no" || $useLdap eq "n" ) { - logger( "Not setting up ldap", "info" ); - - return; - } - - my %ldapConf = getConfig( $userInput, $fileName ); - - make_path( dirname($fileName), { mode => 0755 } ); - writeJson( $fileName, \%ldapConf ); -} - -sub generateUsersConf { - my $userInput = shift; - my $fileName = shift; - - my %user = (); - my %config = getConfig( $userInput, $fileName ); - - $user{username} = $config{tmAdminUser}; - $user{password} = sha1_hex( $config{tmAdminPw} ); - - writeJson( $fileName, \%user ); - $user{password} = $config{tmAdminPw}; - return \%user; -} - -sub generateProfilesDir { - my $userInput = shift; - my $fileName = shift; - - my $userIn = $userInput->{$fileName}; -} - -sub generateOpenSSLConf { - my $userInput = shift; - my $fileName = shift; - - if ( !defined $userInput->{$fileName} ) { - logger( "No OpenSSL Configuration - questions will be asked", "info" ); - - # write an empty config so openssl does not use an old file - writeJson( $fileName, my %emptyConfig ); - return; - } - - my %config = getConfig( $userInput, $fileName ); - - writeJson( $fileName, \%config ); -} - -sub generateParamConf { - my $userInput = shift; - my $fileName = shift; - - my %config = getConfig( $userInput, $fileName ); - return \%config; -} - -# check default values for missing config_var parameter -sub sanityCheckDefaults { - foreach my $file ( ( keys $::defaultInputs ) ) { - foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) { - my $question = getConfigQuestion($defaultValue); - - if ( !defined $defaultValue->{"config_var"} - || $defaultValue->{"config_var"} eq "" ) - { - errorOut("Question '$question' in file '$file' has no config_var"); - } - } - } -} - -# userInput: The entire input config file which is either user input or the defaults -# -# Checks the input config file against the default inputs. If there is a question located in the default inputs which -# is not located in the input config file it will output a warning message. -# -# This does not check the other way meaning questions which are present in defaults but not present in the input config -# file will not be checked - -sub sanityCheckConfig { - my $userInput = shift; - my $diffs = 0; - - foreach my $file ( ( keys $::defaultInputs ) ) { - if ( !defined $userInput->{$file} ) { - logger( "File '$file' found in defaults but not config file", "warn" ); - $userInput->{$file} = []; - } - - foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) { - - my $found = 0; - foreach my $configValue ( @{ $userInput->{$file} } ) { - if ( $defaultValue->{"config_var"} eq $configValue->{"config_var"} ) { - $found = 1; - } - } - - # if the question is not found in the config file add it from defaults - if ( !$found ) { - my $question = getConfigQuestion($defaultValue); - logger( "Question '$question' found in defaults but not in '$file'", "warn" ); - - my %temp; - my $answer; - my $hidden = exists $defaultValue->{"hidden"} && $defaultValue->{"hidden"} ? 1 : 0; - - # in automatic mode add the missing question with default answer - if ($::automatic) { - $answer = $defaultValue->{$question}; - logger( "Adding question '$question' with default answer " . ( $hidden ? "" : "'$answer'" ), "info" ); - } - - # in interactive mode prompt the user for answer to missing question - else { - logger( "Prompting user for answer", "info" ); - if ($hidden) { - $answer = promptPasswordVerify($question); - } - else { - $answer = promptUser( $question, $defaultValue->{$question} ); - } - } - - %temp = ( - "config_var" => $defaultValue->{"config_var"}, - $question => $answer - ); - - if ($hidden) { - $temp{"hidden"} .= "true"; - } - - push $userInput->{$file}, \%temp; - - $diffs++; - } - } - } - - logger( "File sanity check complete - found $diffs difference" . ( $diffs == 1 ? "" : "s" ), "info" ); -} - -# A function which returns the default inputs data structure. These questions and answers will be used if there is no -# user input config file or if there are questions in the input config file which do not have answers - -sub getDefaults { - return { - $::databaseConfFile => [ - { - "Database type" => "mysql", - "config_var" => "type" - }, - { - "Database name" => "traffic_ops", - "config_var" => "dbname" - }, - { - "Database server hostname IP or FQDN" => "localhost", - "config_var" => "hostname" - }, - { - "Database port number" => "3306", - "config_var" => "port" - }, - { - "Traffic Ops database user" => "traffic_ops", - "config_var" => "user" - }, - { - "Password for Traffic Ops database user" => "", - "config_var" => "password", - "hidden" => "true" - } - ], - $::dbConfFile => [ - { - "Database server root (admin) user" => "root", - "config_var" => "dbAdminUser" - }, - { - "Password for database server admin" => "", - "config_var" => "dbAdminPw", - "hidden" => "true" - } - ], - $::cdnConfFile => [ - { - "Generate a new secret?" => "yes", - "config_var" => "genSecret" - }, - { - "Number of secrets to keep?" => "10", - "config_var" => "keepSecrets" - } - ], - $::ldapConfFile => [ - { - "Do you want to set up LDAP?" => "no", - "config_var" => "setupLdap" - }, - { - "LDAP server hostname" => "", - "config_var" => "hostname" - }, - { - "LDAP Admin DN" => "", - "config_var" => "admin_dn" - }, - { - "LDAP Admin Password" => "", - "config_var" => "password", - "hidden" => "true" - }, - { - "LDAP Search Base" => "", - "config_var" => "search_base" - } - ], - $::usersConfFile => [ - { - "Administration username for Traffic Ops" => "admin", - "config_var" => "tmAdminUser" - }, - { - "Password for the admin user" => "", - "config_var" => "tmAdminPw", - "hidden" => "true" - } - ], - $::profilesConfFile => [], - $::opensslConfFile => [ - { - "Country Name (2 letter code)" => "XX", - "config_var" => "country" - }, - { - "State or Province Name (full name)" => "San Jose", - "config_var" => "state" - }, - { - "Locality Name (eg, city)" => "Default City", - "config_var" => "locality" - }, - { - "Organization Name (eg, company)" => "Default Company Ltd", - "config_var" => "company" - }, - { - "Organizational Unit Name (eg, section)" => "", - "config_var" => "org_unit" - }, - { - "Common Name (eg, your name or your server's hostname)" => "cisco.com", - "config_var" => "common_name" - }, - { - "RSA Passphrase" => "", - "config_var" => "rsaPassword", - "hidden" => "true" - } - ], - $::paramConfFile => [ - { - "Traffic Ops url" => "https://localhost", - "config_var" => "tm.url" - }, - { - "Human-readable CDN Name. (No whitespace, please)" => "kabletown_cdn", - "config_var" => "cdn_name" - }, - { - "Health Polling Interval (milliseconds)" => "8000", - "config_var" => "health_polling_int" - }, - { - "DNS sub-domain for which your CDN is authoritative" => "cdn1.kabletown.net", - "config_var" => "dns_subdomain" - }, - { - "TLD SOA admin" => "traffic_ops", - "config_var" => "soa_admin" - }, - { - "TrafficServer Drive Prefix" => "/dev/sd", - "config_var" => "driver_prefix" - }, - { - "TrafficServer RAM Drive Prefix" => "/dev/ram", - "config_var" => "ram_drive_prefix" - }, - { - "TrafficServer RAM Drive Letters (comma separated)" => "0,1,2,3,4,5,6,7", - "config_var" => "ram_drive_letters" - }, - { - "Health Threshold Load Average" => "25", - "config_var" => "health_thresh_load_avg" - }, - { - "Health Threshold Available Bandwidth in Kbps" => "1750000", - "config_var" => "health_thresh_kbps" - }, - { - "Traffic Server Health Connection Timeout (milliseconds)" => "2000", - "config_var" => "health_connect_timeout" - } - - ] - }; -} - -# carried over from old postinstall -# -# todbconf: The database configuration to be used -# opensslconf: The openssl configuration if any - -sub setupDatabase { - my $todbconf = shift; - my $opensslconf = shift; - - # - # Call mysql initialization script. - # - logger( "Creating database with user: $todbconf->{dbAdminUser}", "info" ); - my $result = execCommand( "/opt/traffic_ops/install/bin/create_db", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} ); - if ( $result != 0 ) { - errorOut("Failed to create the database"); - } - - logger( "Setting up database", "info" ); - chdir("/opt/traffic_ops/app"); - $result = execCommand( "/usr/bin/perl", "db/admin.pl", "--env=production", "setup" ); - - if ( $result != 0 ) { - errorOut("Database initialization failed"); - } - else { - logger( "Database initialization succeeded", "info" ); - } - - $result = execCommand( "/opt/traffic_ops/install/bin/dataload", $todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} ); - if ( $result != 0 ) { - logger( "Failed to load seed data", "error" ); - } - - logger( "Downloading MaxMind data", "info" ); - chdir("/opt/traffic_ops/app/public/routing"); - $result = execCommand("/usr/bin/wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz"); - if ( $result != 0 ) { - logger( "Failed to download MaxMind data", "error" ); - } - - logger( "Copying coverage zone file to public dir", "info" ); - $result = execCommand("/bin/mv /opt/traffic_ops/app/public/coverage-zone.json ."); - if ( $result != 0 ) { - logger( "Failed to copy coverage zone file", "error" ); - } - - if ( -x "/usr/bin/openssl" ) { - logger( "Installing SSL Certificates", "info" ); - $result = GenerateCert::createCert($opensslconf); - - if ( $result != 0 ) { - errorOut("SSL Certificate Installation failed"); - } - else { - logger( "SSL Certificates have been installed", "info" ); - } - } - else { - logger( "Unable to install SSL certificates as openssl is not installed", "error" ); - logger( "Install openssl and then run /opt/traffic_ops/install/bin/generateCert to install SSL certificates", "error" ); - exit 4; - } - -} - -# -cfile - Input File: The input config file used to ask and answer questions -# -a - Automatic mode: If there are questions in the config file which do not have answers, the script -# will look to the defaults for the answer. If the answer is not in the defaults -# the script will exit -# -r - Reconfigure: Whether or not to reconfigure the database and check perl dependencies - This will rereate the database -# -defaults - Defaults: Writes out a configuration file with defaults which can be used as input -# -debug - Debug Mode: More output to the terminal -# -h - Help: Basic command line help menu - -sub main { - our $inputFile = ""; - our $automatic = 0; - our $debug = 0; - my $help = 0; - - my $usageString = "Usage: postinstall [-a] [-debug] [-defaults] [-r] -cfile=[config_file]\n"; - - GetOptions( - "cfile=s" => \$inputFile, - "automatic" => \$automatic, - "reconfigure" => \$reconfigure, - "defaults" => \$dumpDefaults, - "debug" => \$debug, - "help" => \$help - ) or die($usageString); - - # stores the default questions and answers - our $defaultInputs = getDefaults(); - - if ($help) { - print $usageString; - exit(0); - } - - # check if the user running postinstall is root - if ( $ENV{USER} ne "root" ) { - errorOut("You must run this script as the root user"); - } - - if ( -f "$logFile.gz" ) { - execCommand( "/bin/gunzip", "$logFile.gz" ); - } - - logger( "Starting postinstall", "info" ); - - logger( "Debug is on", "info" ); - - if ($::automatic) { - logger( "Running in automatic mode", "info" ); - } - - # check if the reconfigure_file is present on the system - if it is let the user know its deprecated - # and exit with an error - if ( -f $reconfigure_file ) { - logger( "$reconfigure_file file is reprecated - please remove and rerun postinstall", "error" ); - return; - } - - if ($dumpDefaults) { - logger( "Writing default configuration file to $outputConfigFile", "info" ); - writeJson( $outputConfigFile, $::defaultInputs ); - return; - } - - logger( "Postinstall " . ( defined $reconfigure ? "in" : "not" ) . " in reconfigure mode", "info" ); - - # check if the user has root access - if ( $ENV{USER} ne "root" ) { - errorOut("You must run this script as the root user"); - } - - rotateLog($cpanLogFile); - - if ( -s $::logFile > $maxLogSize ) { - logger( "Postinstall log above max size of $maxLogSize bytes - rotating", "info" ); - rotateLog($logFile); - } - - # used to store the questions and answers provided by the user - my $userInput; - - # if no input file provided use the defaults - if ( $::inputFile eq "" ) { - logger( "No input file given - using defaults", "info" ); - $userInput = $::defaultInputs; - } - else { - logger( "Using input file $::inputFile", "info" ); - - # check if the input file exists - errorOut("File '$::inputFile' not found") if ( !-f $::inputFile ); - - # read and store the input file - $userInput = readJson($::inputFile); - } - - # sanity check the defaults if running them automatically - sanityCheckDefaults(); - - # check the input config file against the defaults to check for missing questions - sanityCheckConfig($userInput) if ( $inputFile ne "" ); - - chdir("/opt/traffic_ops/install/bin"); - - # if the reconfigure file exists or reconfigure is set then rebuild the perl deps - if ( -f $reconfigure_file || $reconfigure ) { - my $rc = BuildPerlDeps::build(1); - if ( $rc != 0 ) { - errorOut("Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error"); - } - $rc = execCommand( "./download_web_deps", "-i" ); - if ( $rc != 0 ) { - errorOut("Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error"); - } - } - else { - my $rc = BuildPerlDeps::build(); - if ( $rc != 0 ) { - errorOut("Failed to install perl dependencies, check the console output and rerun postinstall once you've resolved the error"); - } - $rc = execCommand( "./download_web_deps", "-i" ); - if ( $rc != 0 ) { - errorOut("Failed to install Traffic Ops Web dependencies, check the console output and rerun postinstall once you've resolved the error"); - } - } - - # The generator functions handle checking input/default/automatic mode - - # todbconf will be used later when setting up the database - my $todbconf = generateDbConf( $userInput, $::databaseConfFile, $::dbConfFile ); - generateCdnConf( $userInput, $::cdnConfFile ); - generateLdapConf( $userInput, $::ldapConfFile ); - my $adminconf = generateUsersConf( $userInput, $::usersConfFile ); - generateProfilesDir( $userInput, $::profilesConfFile ); - generateOpenSSLConf( $userInput, $::opensslConfFile ); - my $paramconf = generateParamConf( $userInput, $::paramConfFile ); - - # if the reconfigure file exists or the reconfigure command line arg is set then setup the database - if ( -f $reconfigure_file || $reconfigure ) { - if ($::automatic) { - setupDatabase( $todbconf, $::opensslConfFile ); - } - else { - setupDatabase( $todbconf, 0 ); - } - } - - # remove the reconfigure file if it exists - if ( -f $reconfigure_file ) { - logger( "Removing reconfigure file", "info" ); - unlink($reconfigure_file); - } - - logger( "Starting Traffic Ops", "info" ); - execCommand("/sbin/service traffic_ops start"); - - logger( "Waiting for Traffic Ops to start", "info" ); - - if ( !profiles_exist( $adminconf, $paramconf->{"tm.url"} ) ) { - logger( "Creating default profiles...", "info" ); - replace_profile_templates($paramconf); - import_profiles($adminconf); - profiles_exist( $adminconf, $paramconf->{"tm.url"} ); # call again to create $reconfigure_defaults file if import was successful - } - else { - logger( "Not creating default profiles", "info" ); - } - - logger("Postinstall complete"); - - execCommand( "/bin/gzip", "$logFile" ); -} - -main; - -# vi:syntax=perl