Edit File: migrate_ea3_to_ea4
#!/usr/local/cpanel/3rdparty/bin/perl # cpanel - migrate_ea3_to_ea4 Copyright 2017 cPanel, Inc. # All rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited package scripts::migrate_ea3_to_ea4; use strict; use warnings; use v5.014; use Try::Tiny; use Cpanel::Imports; use Cpanel::Config::Httpd (); use Cpanel::Config::Httpd::EA4 (); use Cpanel::CloudLinux::Installed (); use Umask::Local (); use Cpanel::EA4::EA3Map (); use Cpanel::EA4::Util (); use Cpanel::EA4::MigrateBlockers (); use Cpanel::EtcCpanel (); use Cpanel::JSON (); use Cpanel::Notify (); use Cpanel::Sys::OS (); use Cpanel::Template (); use Cpanel::SafeRun::Simple (); use Cpanel::Daemonizer::Tiny (); use Whostmgr::ModSecurity (); use Whostmgr::ModSecurity::ModsecCpanelConf (); use Cpanel::TempFile (); use Cpanel::YAML (); use File::Path::Tiny (); use Getopt::Long (); use Cpanel::HTTP::Client (); use IO::Prompt (); use Cpanel::GenSysInfo (); use Cpanel::DataStore (); use Cpanel::EA4::MigrateBlocker (); use Cpanel::FindBin (); use Cpanel::SafeRun::Full (); use Cpanel::AdvConfig (); use Cpanel::AdvConfig::apache (); use Cpanel::SysPkgs::YUM (); use Cpanel::Logger (); use Cpanel::SafeRun::Errors (); use Cpanel::PackMan (); use Cpanel::DNSONLY (); use Cpanel::ProgLang::Supported::php::Ini (); use Cpanel::EA4::ConvertPHPINI (); use Cpanel::SafeDir::Read (); use POSIX qw{:sys_wait_h}; our $base_log_dir = '/usr/local/cpanel/logs/packman'; if ( !-d $base_log_dir ) { mkdir($base_log_dir) or die "Failed to mkdir($base_log_dir): $!\n"; } my $timestamp = scalar( localtime( time() ) ); $timestamp =~ s/\s+/\_/g; our $logfile_path = "$base_log_dir/migrate_ea3_to_ea4.$timestamp.log"; our $logger = Cpanel::Logger->new( { 'alternate_logfile' => $logfile_path } ); my $show_log_link = 0; ########################################################################### # Base apache EA3 directory our $base_apache = '/usr/local/apache'; our $new_base_apache = '/etc/apache2'; our $tools_dir = '/usr/local/bin'; our $tmp_dir = '/var/cpanel/tmp'; our $base_ea4_cfgdir = '/etc/cpanel/ea4'; our $apxs = '/usr/bin/apxs'; ########################################################################### our $ea3_profile_fname = "/var/cpanel/easy/apache/profile/_main.yaml"; our $ea4_custom_profile_dir = "/etc/cpanel/ea4/profiles/custom"; our $install_profile; our $hook_dir = '/var/cpanel/ea4_migration_hooks'; our $ftpdconf = '/etc/pure-ftpd.conf'; my $php_default = 5.6; my @accepted_logger_levels = ( 'info', 'info', 'raw', 'fatal', 'debug', 'panic', 'info', 'invalid', 'die', "warn", "error" ); our $ea3_version_ns = "2_4"; # sane default sub msglog { my ( $lvl, @msg ) = @_; if ( !@msg ) { return 0; } if ( !grep /$lvl/, @accepted_logger_levels ) { print "Invalid Logger handler : $lvl\n"; return 0; } foreach my $line (@msg) { if ($line) { chomp $line; if ( $lvl eq 'error' ) { # because our logger doesn't have an error level <facepalm> $logger->logger( { message => $line, level => "error", output => -t STDIN ? 2 : 0, backtrace => 0 } ); next; } $logger->$lvl($line); } } return 1; } # Provide a link to the log file regardless of where script dies/exits sub END { if ( $show_log_link == 1 ) { print "A record of this process may be found at : $logfile_path \n"; } return; } sub exit_testable { my ($val) = @_; exit $val if defined $val; exit; } sub get_current_php_version { return if !-x '/usr/bin/php'; # can’t do this since cgi-fcgi does not have -r: # my ($ea3_php_vers) = `/usr/bin/php -r "echo(PHP_VERSION);"` =~ m/^(\d\.\d)\.\d\d$/; my ($first_line) = `/usr/bin/php -v`; my ($php_vers) = $first_line =~ m/^PHP (\d+\.\d+)\.\d+ /; return $php_vers; } sub php_version_check { my ($parms_hr) = @_; my $current_php_version = Cpanel::EA4::Util::get_current_php_version(); my $dragons; if ($current_php_version) { my $target_php_version = Cpanel::EA4::Util::get_target_php_version($current_php_version); if ( $current_php_version ne $target_php_version ) { $dragons = "\e[31m"; $dragons .= "\t - " . locale->maketext( '[asis,EasyApache 4] does not support your current version of [asis,PHP] ([_1]). Exit now to maintain this version.', $current_php_version ) . "\n"; $dragons .= "\t - " . locale->maketext( 'The migration process will change your “[_1]”[comment,application name] version to “[_2]”[comment,application version number] and you may not be able to downgrade.', 'PHP', $target_php_version ) . "\n"; $dragons .= "\t - " . locale->maketext('Do you wish to continue?'); $dragons .= "\e[0m"; } } else { $dragons = "\e[31m"; $dragons .= "\t - " . locale->maketext( '“[_1]” is not currently configured on your system.', 'PHP' ) . "\n"; $dragons .= "\t - " . locale->maketext( 'If you convert to [asis,EasyApache 4], the system will not provide a “[_1]” version.', 'PHP' ) . "\n"; $dragons .= "\t - " . locale->maketext('Do you wish to continue?'); $dragons .= "\e[0m"; } if ($dragons) { local @ARGV; # otherwise it tries to read from *ARGV, weird if ( !exists $parms_hr->{'yes'} && !IO::Prompt::prompt( $dragons . " [y/n]", -yes_no ) ) { msglog( 'info', locale->maketext("Exiting with no changes.") ); exit_testable(0); } } return; } sub migration_warning { my ($parms_hr) = @_; my $php_current = Cpanel::EA4::Util::get_current_php_version(); my $php_target = $php_current ? set_ea4_default_php_version($php_current) : ''; my $dragons = "\e[31m"; if ( -f '/usr/local/cpanel/whostmgr/docroot/templates/ea4motd.tmpl' ) { my $out = ${ Cpanel::Template::process_template( "whostmgr", { template_file => "ea4motd.tmpl" } ) }; # process_template() returns a scalar ref, yeah I know I know … $dragons = "\e[33m⚠ $out\e[0m\e[31m"; } $dragons .= " " . locale->maketext('By continuing, you agree that you understand the following information:') . "\n"; $dragons .= "\t - " . locale->maketext("While the system runs the migration script, some of [asis,cPanel amp() WHM]’s features may not operate.") . "\n"; $dragons .= "\t - " . locale->maketext('Wait for the system to complete the script before you attempt any other actions on your server.') . "\n"; $dragons .= "\t - " . locale->maketext('If you revert to [asis,EasyApache3], the system automatically rebuilds [asis,PHP]. This may take 15 - 30 minutes or longer to complete.') . "\n"; if ($php_current) { $dragons .= "\t - " . locale->maketext( 'You are currently running [asis,PHP] v[_1]. Converting to EA4 will attempt to set your default [asis,PHP] version to v[_2]. Use the MultiPHP Manager in WHM to change this after the conversion.', $php_current, $php_target ) . "\n" if $php_current ne $php_target; } else { $dragons .= "\t - " . locale->maketext( '“[_1]” is not currently configured on your system.', 'PHP' ) . "\n"; $dragons .= "\t - " . locale->maketext( 'If you convert to [asis,EasyApache 4], the system will not provide a “[_1]” version.', 'PHP' ) . "\n"; } $dragons .= "\t - " . locale->maketext('Do you wish to continue?'); $dragons .= "\e[0m"; { local @ARGV = (); # otherwise it tries to read from *ARGV, weird if ( !exists $parms_hr->{'yes'} && !IO::Prompt::prompt( $dragons . " [y/n]", -yes_no ) ) { msglog( 'info', locale->maketext("Exiting with no changes.") ); exit_testable(0); } } return; } sub remove_noteable_packages { my ($parms_hr) = @_; my @packages = Cpanel::EA4::MigrateBlocker::check_for_packages_to_be_removed(); if (@packages) { msglog( 'info', locale->maketext('Removing package(s) that conflict with EA4.') ); my @args = ( '-C', '-y', 'erase', @packages ); my $yum = Cpanel::FindBin::findbin('yum'); die locale->maketext('Unable to find yum.') if ( !defined $yum ); my $result = Cpanel::SafeRun::Full::run( 'program' => $yum, 'args' => \@args ); if ( $result->{'exit_value'} == 0 ) { msglog( 'info', $result->{'stdout'} ) if exists $result->{'stdout'}; } else { msglog( 'info', locale->maketext('There was an error removing the package(s).') ); msglog( 'info', $result->{'stdout'} ) if exists $result->{'stdout'}; msglog( 'info', $result->{'stderr'} ) if exists $result->{'stderr'}; } } return; } sub migrate_blockers { my ($parms_hr) = @_; my $blockers = Cpanel::EA4::MigrateBlockers::evaluate_blockers(); if ( defined $blockers && ref $blockers eq "ARRAY" && @{$blockers} > 0 ) { my $is_blocked = 0; my $blocker_text = ""; foreach my $vendor ( @{$blockers} ) { $blocker_text .= "\e[0m"; $blocker_text .= locale->maketext("Warnings/Blockers from Vendor:") . " " . $vendor->{'vendor_id'} . "\n"; $blocker_text .= $vendor->{'name'} . " " . "\n"; $blocker_text .= $vendor->{'desc'} . " " . "\n"; foreach my $item ( @{ $vendor->{'items'} } ) { my $status = $item->{'status'}; my $color; my $type; if ( $status >= 2 ) { $is_blocked = 1; $color = "\e[31m"; # RED $type = locale->maketext("BLOCKED"); } elsif ( $status == 1 ) { $color = "\e[33m"; # YELLOW $type = locale->maketext("WARNING"); } else { $color = "\e[32m"; # GREEN $type = locale->maketext("INFO"); } $blocker_text .= $color . " " . $type . " " . $item->{'msg'} . "\n"; } $blocker_text .= "\n"; } $blocker_text .= "\e[0m\n"; if ($is_blocked) { my $blocked = $blocker_text; $blocked .= "\n\e[31m" . locale->maketext('The issues above prevent you from migrating, please correct these issues and try again.') . "\n"; msglog( 'info', $blocked ); msglog( 'info', "\e[0m\n" ); exit_testable(0); } else { $blocker_text .= "\t - " . locale->maketext('Do you wish to continue?'); { local @ARGV = (); # otherwise it tries to read from *ARGV, weird if ( !exists $parms_hr->{'yes'} && !IO::Prompt::prompt( $blocker_text . " [y/n]", -yes_no ) ) { msglog( 'info', locale->maketext("Exiting with no changes.") ); msglog( 'info', "\e[0m\n" ); exit_testable(0); } } } msglog( 'info', "\e[0m\n" ); } return; } sub _prompt_for_revert { my ($parms_hr) = @_; # 11.58 systems will, by default, never have had EA3 on them; thus, reverting should be forbidden. die "This system never had EasyApache 3. Reverting is not possible.\n" unless -d '/usr/local/apache.ea3'; my $dragons = "\e[31m"; $dragons .= locale->maketext('This action will revert your existing [asis,EasyApache4] install back to [asis,EasyApache3].') . "\n\n"; $dragons .= "\t - " . locale->maketext("While the system runs the migration script, some of [asis,cPanel amp() WHM]’s features may not operate completely.") . "\n"; $dragons .= "\t - " . locale->maketext('Please wait for the system to complete the script before you attempt anything else on your server.') . "\n"; $dragons .= "\t - " . locale->maketext('We apologize for the inconvenience.') . "\n\n"; $dragons .= "\t - " . locale->maketext('Do you wish to continue?'); $dragons .= "\e[0m"; { local @ARGV = (); # otherwise it tries to read from *ARGV, weird if ( !exists $parms_hr->{'yes'} && !IO::Prompt::prompt( $dragons . " [y/n]", -yes_no ) ) { msglog( 'info', locale->maketext("Exiting with no changes.") ); exit_testable(0); } } return; } sub _convert_ea3_proceed { my ( $is_error, $parms_hr, @messages ) = @_; if ( !$is_error ) { # just warnings, no errors msglog( 'info', "\n" . locale->maketext("[asis,EasyApache 4] converted your [asis,EasyApache 3] profile, with warnings.") . "\n\n" ); foreach (@messages) { msglog( 'info', $_ ); } my $question = locale->maketext('Do you wish to continue?'); { local @ARGV = (); # otherwise it tries to read from *ARGV, weird if ( !exists( $parms_hr->{'yes'} ) && !IO::Prompt::prompt( $question . " [y/n]", -yes_no ) ) { msglog( 'info', locale->maketext("Exiting with no changes.") ); exit_testable(0); return; # for testability } msglog( 'info', "\n" ); } } else { msglog( 'info', locale->maketext("[asis,EasyApache 4] was unable to convert your [asis,EasyApache 3] profile, with these fatal errors.") ); foreach (@messages) { msglog( 'info', $_ ); } exit_testable(0); } return; } sub convert_ea3_profile { my ($parms_hr) = @_; $parms_hr->{'convert_fail'} = 0 if !exists $parms_hr->{'convert_fail'}; if ( exists $parms_hr->{'skip_convert'} && $parms_hr->{'skip_convert'} ) { msglog( 'info', locale->maketext("Skipping conversion of [asis,EasyApache 3] profile.") ); return; } if ( -e $ea3_profile_fname ) { my $time = time(); my $migrate_fname = $ea4_custom_profile_dir . "/ea3_state_at_migration-$time.json"; my $ea3_hr; my $ea4_hr; try { $ea3_hr = Cpanel::YAML::LoadFile($ea3_profile_fname); if ( $ea3_version_ns ne $ea3_hr->{Apache}{version} ) { msglog( "info", "The ea3 Apache version is $ea3_version_ns; the profile being converted is $ea3_hr->{Apache}{version}" ); } $ea4_hr = Cpanel::EA4::EA3Map::convert_ea3_config_to_ea4( $ea3_hr, $parms_hr->{'convert_fail'} ); # filter out warnings we do not want to display to the user my @revised_warnings = grep { !/Cpanel::Easy::Apache::Fileprotect/ && !/Cpanel::Easy::Apache::SlowRestartPatch/ && !/Cpanel::Easy::PHP5::MagicQuotes/ && !/Cpanel::Easy::PHP5::MailHeaders/ } @{ $ea4_hr->{'warnings'} }; _convert_ea3_proceed( 0, $parms_hr, @revised_warnings ) if ( @revised_warnings > 0 ); File::Path::Tiny::mk_parent($migrate_fname) or die locale->maketext( "Can not create parent directory for “[_1]”: [_2]", $migrate_fname, "$!\n" ); Cpanel::JSON::DumpFile( $migrate_fname, $ea4_hr->{'profile'} ); msglog( 'info', locale->maketext( "[asis,EasyApache 3] profile was converted to “[_1]”.", $migrate_fname ) ); # profile was converted, set it as the profile to install $parms_hr->{'install_profile'} = $migrate_fname; } catch { my $error = $_; _convert_ea3_proceed( 1, $parms_hr, ($error) ); # we have no provision for a fatal error here, # they can always use the command line option to skip the # conversion exit_testable(0); }; } return; } sub preliminary_work { my ($parms_hr) = @_; msglog( 'info', "\n" . locale->maketext("[asis,EasyApache 4] Installing preliminary tools.") ); install_ea4_repo() unless $parms_hr->{'skip-cloudlinux'}; return; } sub install_ea4_repo { # Import public key and download .repo file system( "rpm", "--import", $Cpanel::EA4::Util::public_key_url ) && msglog( "warn", "Could not import the public key" ); if ( -e $Cpanel::EA4::Util::repo_file_path ) { $Cpanel::EA4::Util::repo_file_path = $Cpanel::EA4::Util::repo_file_path . "-" . time; } my $res = Cpanel::HTTP::Client->new->mirror( $Cpanel::EA4::Util::repo_file_url, $Cpanel::EA4::Util::repo_file_path ); if ( !$res->success() ) { msglog( 'info', locale->maketext( "Could not download “[_1]” to “[_2]”: [_3]", $Cpanel::EA4::Util::repo_file_url, $Cpanel::EA4::Util::repo_file_path, "$res->{status} $res->{reason}" ) ); exit_testable(1); return; # testability } # Modify repo file in case this is an Amazon Linux server, hardcode to use CentOS 6 packages my $distro = Cpanel::GenSysInfo::get_rpm_distro(); if ( $distro eq 'amazon' ) { # Edit the repo file and hardcode EA4.repo to use "6" as the releasever if ( open( my $repo_read_fh, '<', $Cpanel::EA4::Util::repo_file_path ) ) { my @file_contents = (<$repo_read_fh>); close($repo_read_fh); if ( open( my $repo_write_fh, '>', $Cpanel::EA4::Util::repo_file_path ) ) { foreach my $line (@file_contents) { $line =~ s/ea4-c\$releasever-/ea4-c6-/; print {$repo_write_fh} $line; } close($repo_write_fh); } # Make sure we have scl-utils from epel installed my $output = Cpanel::SafeRun::Errors::saferunallerrors( 'yum', '--enablerepo=epel', '-y', 'install', 'scl-utils' ); msglog( 'info', $output ); } } return; } sub _ula_looks_like_ea4 { my $ula_is_ea4 = 0; if ( -d $base_apache ) { my $domlogs = 0; my $htdocs = 0; my $logs = 0; my $modules = 0; $domlogs = 1 if -l "$base_apache/domlogs"; $htdocs = 1 if -l "$base_apache/htdocs"; $logs = 1 if -l "$base_apache/logs"; $modules = 1 if -l "$base_apache/modules"; $ula_is_ea4 = 1 if ( $domlogs && $htdocs && $logs && $modules ); } return $ula_is_ea4; } sub _ensure_dir { # First create /etc/cpanel with right permissions if it doesn't exist. # Using a separate function to create this directory # since it's being modified (created, edited) by other features as well (i.e. IPv6). my ( $ret, $reason ) = Cpanel::EtcCpanel::make_etc_cpanel_dir(); if ($ret) { # Change the umask for creating the /etc/cpanel/ea4 directory explicitly with 755 permissions. my $umask = Umask::Local->new(022); system("mkdir -p $Cpanel::EtcCpanel::ETC_CPANEL_DIR/ea4"); # to avoid needless “mkdir: cannot create directory `/etc/cpanel/ea4': File exists” } else { msglog( 'info', locale->maketext( "Could not create “[_1]”: [_2]", $Cpanel::EtcCpanel::ETC_CPANEL_DIR, $reason ) ); # cannot create the critical directory, we are out of here exit_testable(1); return; # testability } return; } sub _determine_ea3_apache_version { my $ea3_dash_v = Cpanel::SafeRun::Errors::saferunallerrors( '/usr/local/apache/bin/httpd', '-v' ); if ( $ea3_dash_v =~ m/Server version:\s+Apache\/([\d.]+)/ ) { my $ver = $1; my ( $maj, $min ) = split( /\./, $ver ); $ea3_version_ns = $min == 0 ? $maj : $maj . "_" . $min; msglog( "info", "Determined ea3 Apache version from -v: $ea3_version_ns" ); } else { msglog( "info", "Could not determine ea3 Apache version from /usr/local/apache/bin/httpd -v" ); } return; } sub move_to_ea4 { my ($parms_hr) = @_; msglog( 'info', "\n" . locale->maketext("[asis,EasyApache 4] Starting migrate.") ); # 4. mark system as ea4 unlink "/var/cpanel/httpd_was_built_by_ea3"; # if they revert, scripts/easyapache will re-create it _ensure_dir(); system("touch $Cpanel::EtcCpanel::ETC_CPANEL_DIR/ea4/is_ea4"); _determine_ea3_apache_version(); if ( -d $base_apache ) { # copy /usr/local/apache off, if and only if it is not EA4. # This protects them if they hit ctrl-C during a migrate if ( -d "$base_apache.ea3" && !_ula_looks_like_ea4() ) { msglog( 'info', "Preserving previous EA3 conversion backup." ); if ( -d "$base_apache.ea3.1" ) { _remove_flagfile(); msglog( 'die', "$base_apache.ea3.1 already exists, that could indicate a problem since this typically only needs to be run once. If you are sure its OK please move it out of the way and re-run this script." ); } system("mv $base_apache.ea3 $base_apache.ea3.1"); } msglog( 'info', "Stopping Apache" ); my $output = Cpanel::SafeRun::Errors::saferunallerrors( "/usr/local/cpanel/scripts/restartsrv_httpd", "stop" ); msglog( 'info', $output ); if ( !_ula_looks_like_ea4() ) { # only preserve ea3 if it was not partially migrated msglog( 'info', "Preserving last EA3 build" ); system("mv $base_apache $base_apache.ea3"); } _migrate_non_apache_ea3_items(); } my $output = Cpanel::SafeRun::Errors::saferunallerrors( "yum", "-y", "install", "yum-plugin-universal-hooks", "ea-profiles-cpanel", "ea-cpanel-tools" ); msglog( 'info', $output ); $output = Cpanel::SafeRun::Errors::saferunallerrors( "rm", "-rf", "/var/cpanel/cache/Cpanel-PackMan*" ); # force fresh cache msglog( 'info', $output ); return; } sub _do_you_want_to_abort { my ($parms_hr) = @_; return 1 if exists $parms_hr->{'yes'}; my $msg = locale->maketext("If you abort, you will leave [asis,EasyApache] in a non-usable state, do this at your own risk."); $msg .= "\n" . locale->maketext('Do you wish to continue?'); { local @ARGV = (); # otherwise it tries to read from *ARGV, weird return 1 if exists $parms_hr->{'yes'} || IO::Prompt::prompt( $msg . " [y/n]", -yes_no ); } return; } sub _provision_error { my ( $err_msg, $parms_hr ) = @_; msglog( 'info', locale->maketext( "An error occurred while installing profile “[_1]”.", $err_msg ) . "\n\n" ); if ( $install_profile > 1 ) { exit(31); # migrate failed, default profile failed } my $menu = locale->maketext("Please select from the following options:") . "\n\n"; $menu .= locale->maketext("[comment,user would enter the digit to choose this menu item]1) Revert to [asis,EasyApache 3]") . "\n"; $menu .= locale->maketext("[comment,user would enter the digit to choose this menu item]2) Abort.") . "\n"; my $max_choice = 2; if ( $parms_hr->{'install_profile'} ne $parms_hr->{'default_profile'} ) { $menu .= locale->maketext("[comment,user would enter the digit to choose this menu item]3) Install the cPanel Default profile.") . "\n"; $max_choice++; } my $count = 0; while (1) { # If we do not have a terminal we will just revert my $ret = -t STDIN ? IO::Prompt::prompt( $menu, -i ) : 1; if ( $ret < 1 || $ret > $max_choice ) { msglog( 'info', locale->maketext( "Please enter a digit from 1 to “[_1]”.", $max_choice ) . "\n" ); if ( $count++ > 100 ) { die "Prompted too many times, aborting …\n"; } next; } if ( $ret == 1 ) { eval { _revert() }; exit(11) if $@; # migrate failed, revert failed exit(10); # migrate failed, reverted } if ( $ret == 2 ) { if ( $count++ > 100 ) { die "Prompted too many times, aborting …\n"; } next if !_do_you_want_to_abort($parms_hr); # if we get here, they chose not to abort. msglog( 'info', locale->maketext("Exiting.") . "\n" ); exit(20); # migrate failed, aborted } if ( $ret == 3 ) { $parms_hr->{'install_profile'} = $parms_hr->{'default_profile'}; install_profile($parms_hr); # before you add anything here: should it go in _post_install_profile_tasks() instead? exit(30); # migrate failed, installed default profile OK } } return; } # for mocking purposes because mocking JSON::LoadFile seems like a very bad idea. sub _load_install_profile { my $file = shift; return Cpanel::JSON::LoadFile($file); } sub install_profile { my ($parms_hr) = @_; $install_profile++; if ( !exists $parms_hr->{'install_profile'} ) { $parms_hr->{'install_profile'} = $parms_hr->{'default_profile'}; } my $res; try { msglog( 'info', locale->maketext( "Installing profile “[_1]”.", $parms_hr->{'install_profile'} ) . "\n" ); my $data = _load_install_profile( $parms_hr->{'install_profile'} ); my $pkm = Cpanel::PackMan->instance; $pkm->{'logger'} = $logger; $res = $pkm->resolve_multi_op_ns( $data->{'pkgs'}, 'ea' ); # if there is a problem, the resolution will die and be caught otherwise we are fine $pkm->multi_op($res); } catch { _provision_error( $_, $parms_hr ); }; _post_install_profile_tasks($parms_hr); return; } sub _post_install_profile_tasks { my ($parms_hr) = @_; post_migrate_data($parms_hr); convert_user_php_ini($parms_hr); # converts /usr/local/lib/php.ini to # /opt/ea-php??/root/etc/php.ini Cpanel::EA4::ConvertPHPINI::convert_ea3_php_ini_to_ea4(); pureftpd_conf( $parms_hr->{revert} ); Cpanel::ProgLang::Supported::php::Ini::setup_session_save_path(); run_hooks("post_migration"); # TODO: apache health check migrate_apxs(); msglog( 'info', "EasyApache 4 updated the system mime types. You must manually update any custom user mime types." ); msglog( 'info', locale->maketext("[asis,EasyApache 4] has successfully been installed.") ); return; } sub migrate_apxs { if ( -x $apxs ) { my $debug_apxs = `rpm -fq $apxs`; if ($?) { msglog( 'info', "Moving ea3's $apxs to $apxs.ea3. If you want $apxs for ea4 you must manually install ea-apache24-devel." ); system("mv -f $apxs $apxs.ea3"); msglog( 'info', "Attempting to install ea-apache24-devel to get $apxs for ea4 …" ); my $devel_apxs = `yum install -y ea-apache24-devel`; if ($?) { msglog( 'warn', " … ea-apache24-devel installation failed you will need to do this manually if you want $apxs." ); } else { msglog( 'info', " … done!" ); } } } return; } # migrate_logs() recreates any 0640 logfiles from the old domlogs directory so that they will not be world-readable # until the next log processing cycle. sub migrate_logs { my $parms = shift; my $old_domlog_dir = "${base_apache}.ea3/domlogs"; my $new_domlog_dir = "${base_apache}/domlogs"; # Skip unless we had old domlogs return if ( -l $old_domlog_dir || !-d _ ); msglog( 'info', "Migrating the EasyApache 3 domlogs permissions and ownership to EasyApache 4." ); my $old_umask = umask(037); if ( opendir my $dh, $old_domlog_dir ) { while ( my $filename = readdir($dh) ) { next if ( $filename =~ /^\./ ); my $full_path = "${old_domlog_dir}/${filename}"; my ( $mode, $uid, $gid ) = ( lstat($full_path) )[ 2, 4, 5 ]; next unless ( -f _ ); $mode &= 07777; next unless ( $mode == 0640 ); if ( open my $fh, ">>", "${new_domlog_dir}/${filename}" ) { chmod $mode, $fh; chown $uid, $gid, $fh; close $fh; } } } umask($old_umask); return; } sub _pre_migrate_modsec2 { my $base = shift; for my $file (qw( modsec2.user.conf modsec2.cpanel.net )) { system("/bin/cp -a $base/modsec/$file $base/modsec/$file.PREVIOUS &>/dev/null"); } return 1; } sub _cleanup_migrate_modsec2 { my ( $base, $msg ) = @_; for my $file (qw( modsec2.user.conf modsec2.cpanel.conf )) { system("/bin/mv -f $base/modsec/$file.PREVIOUS $base/modsec/$file &>/dev/null"); } system("/bin/rm -rf $base/modsec_vendor_configs &>/dev/null"); $msg .= "\nThe ModSecurity2 migration failed.\n"; $msg .= "You will need to manually migrate your settings.\n"; logger->warn($msg); return 1; } # There are 3 parts to the cpanel implementation of modsecurity # 1. modsec_vendor_configs -- directory containing installed rule sets # 2. modsec2.user.conf -- user controlled configuration file # 3. modsec2.cpanel.conf -- cpanel controlled configuration file # # If at any point a migration fails, all migrated modsec configs are # removed and it's up to the user to manually migrate them. sub migrate_modsec2 { my $parms = shift; return 1 unless Whostmgr::ModSecurity::has_modsecurity_installed(); msglog( 'info', locale->maketext("Migrating the [asis,EasyApache 3] [asis,ModSecurity2] configuration settings to [asis,EasyApache 4].") ); my $oldap = "$base_apache.ea3/conf"; my $newap = "$new_base_apache/conf.d"; _pre_migrate_modsec2($newap); # 1. copy the old vendor configs to the new location (if any) if ( -d "$oldap/modsec_vendor_configs" ) { logger->info("Migrating the EasyApache 3 ModSecurity2 vendor rules to EasyApache 4."); system("/bin/cp -a $oldap/modsec_vendor_configs/ $newap/ &>/dev/null"); unless ( $? == 0 && -d "$newap/modsec_vendor_configs" ) { _cleanup_migrate_modsec2( $newap, "The ModSecurity2 migration was unable to copy your vendor rules." ); return 0; } } else { msglog( 'info', "The ModSecurity2 migration didn't find any vendor rules." ); } # 2. copy the modsec2.user.conf to the new location, making sure to # comment out the Include directives. These directives could be # pointing to some place that doesn't exist anymore, which would # cause Apache to croak. # # TODO: Perhaps in the future we can verify that the included file(s) # exists, and comment out if it doesn't. But this would need to # be a recursive (aka expensive) check. my $old_cfg = "$oldap/modsec2.user.conf"; my $found_include; if ( open( my $old_fh, '<', $old_cfg ) ) { my $tmp = Cpanel::TempFile->new(); # we use this because it creates a filesystem perm of 0600 my ( $temp_cfg, $temp_fh ) = $tmp->file(); unless ($temp_cfg) { close $old_fh; _cleanup_migrate_modsec2( $newap, "The ModSecurity2 migration was unable to create a temporary file." ); return 0; } while ( ( my $line = <$old_fh> ) ) { if ( $line =~ /\A(\s*include\s+.*)/i ) { $found_include = 1; $line = "# $line"; } print $temp_fh $line; } close $_ for $old_fh, $temp_fh; logger->info("Migrating the EasyApache 3 ModSecurity2 user configuration to EasyApache 4."); system("/bin/mv -f $temp_cfg $newap/modsec/modsec2.user.conf"); # this depends on the temp file being chmod 0600 unless ( $? == 0 ) { _cleanup_migrate_modsec2( $newap, "The ModSecurity2 migration was unable to create $newap/modsec/modsec2.user.conf." ); return 0; } } else { _cleanup_migrate_modsec2( $newap, "The ModSecurity2 migration was unable to open $old_cfg." ); return 0; } # 3. now regenerate modsec2.cpanel.conf logger->info("Regenerating the ModSecurity2 cPanel configuration for EasyApache 4."); my $modsec = Whostmgr::ModSecurity::ModsecCpanelConf->new( skip_restart => 1 ); eval { $modsec->manipulate( sub { } ); }; if ( $@ || !-s "$newap/modsec/modsec2.cpanel.conf" ) { _cleanup_migrate_modsec2( $newap, "The ModSecurity2 migration was unable to regenerate $newap/modsec/modsec2.cpanel.conf." ); return 0; } if ($found_include) { my %contact = ( class => q{EasyApache::EA4_MigrationModSec}, application => q{migrate_ea3_to_ea4}, constructor_args => [], ); # No point in catching the failure since we can't do anything # about here anyways. try { my $class = Cpanel::Notify::notification_class(%contact); waitpid( $class->{'_icontact_pid'}, WNOHANG ); }; } # hopefully everything's ok! return 1; } sub post_migrate_data { my $parms = shift; migrate_logs($parms); migrate_modsec2($parms); my $output = Cpanel::SafeRun::Errors::saferunallerrors("/usr/local/cpanel/scripts/findphpversion"); msglog( 'info', $output ); $output = Cpanel::SafeRun::Errors::saferunallerrors( "/usr/local/cpanel/scripts/generate_account_suspension_include", "--update" ); msglog( 'info', $output ); $output = Cpanel::SafeRun::Errors::saferunallerrors("/usr/local/cpanel/scripts/rebuildhttpdconf"); msglog( 'info', $output ); msglog( 'info', "Migrating userdata …" ); migrate_userdata_includes(); $output = Cpanel::SafeRun::Errors::saferunallerrors("/usr/local/cpanel/scripts/rebuildhttpdconf"); # yes again now that the userdata is in place, silently this time msglog( 'info', " … done!" ); msglog( 'info', "Migrating global include files …" ); migrate_global_includes(); $output = Cpanel::SafeRun::Errors::saferunallerrors("/usr/local/cpanel/scripts/rebuildhttpdconf"); # yes again now that the includes are in place, silently this time msglog( 'info', " … done!" ); $output = Cpanel::SafeRun::Errors::saferunallerrors( "/usr/local/cpanel/scripts/restartsrv_httpd", "--restart" ); msglog( 'info', $output ); return 1; } sub _get_dirs { my ($dir) = @_; my @sorted = sort( Cpanel::SafeDir::Read::read_dir( $dir, sub { ( !-l "$dir/$_[0]" && -d _ ) ? 1 : 0 } ) ); return @sorted; } sub _get_files { my ($dir) = @_; my @sorted = sort( Cpanel::SafeDir::Read::read_dir( $dir, sub { ( -l "$dir/$_[0]" || -f _ ) ? 1 : 0 } ) ); return @sorted; } my %includes_skip = ( 'cp_php_magic_include_path.conf' => 1, 'post_virtualhost_1.conf' => 1, 'pre_main_1.conf' => 1, 'pre_virtualhost_1.conf' => 1, 'post_virtualhost_2.conf' => 1, 'pre_main_2.conf' => 1, 'pre_virtualhost_2.conf' => 1, 'account_suspensions.conf' => 1, 'errordocument.conf' => 1, ); sub migrate_global_includes { local $base_apache = -d "$base_apache.ea3" ? "$base_apache.ea3" : $base_apache; _migrate_include_files( "$base_apache/conf/includes", "$new_base_apache/conf.d/includes" ); return; } sub migrate_userdata_includes { # by this time $base_apache has been moved to $base_apache.ea3, unless we're testing local $base_apache = -d "$base_apache.ea3" ? "$base_apache.ea3" : $base_apache; my $orig_userdata = "$base_apache/conf/userdata"; my $ea4_userdata = "$new_base_apache/conf.d/userdata"; my $confd_exists_already = -d "$new_base_apache/conf.d" ? 1 : 0; if ( !_migrate_include_files( $orig_userdata, $ea4_userdata ) ) { if ( !$confd_exists_already ) { File::Path::Tiny::rm("$new_base_apache/conf.d") or msglog( "warn", "Could not remove $new_base_apache/conf.d" ); } return; } TYPE: for my $type (qw(std ssl)) { _migrate_include_files( "$orig_userdata/$type", "$ea4_userdata/$type" ) or next TYPE; # this is how ea3 would determine what Include to add: 2_x or 2 or none: my $ver_str = -d "$orig_userdata/$type/$ea3_version_ns" ? $ea3_version_ns : -d "$orig_userdata/$type/2" ? "2" : ""; next TYPE if !$ver_str; _migrate_include_files( "$orig_userdata/$type/$ver_str", "$ea4_userdata/$type/$ver_str" ) or next TYPE; USER: for my $user ( _get_dirs("$orig_userdata/$type/$ver_str") ) { _migrate_include_files( "$orig_userdata/$type/$ver_str/$user", "$ea4_userdata/$type/$ver_str/$user" ) or next USER; for my $domain ( _get_dirs("$orig_userdata/$type/$ver_str/$user") ) { _migrate_include_files( "$orig_userdata/$type/$ver_str/$user/$domain", "$ea4_userdata/$type/$ver_str/$user/$domain" ); } } # prior versions of setbwlimit only put this in /2/ so grab it if we havn't already: if ( $ver_str ne "2" && -d "$orig_userdata/$type/2" ) { for my $user ( _get_dirs("$orig_userdata/$type/2") ) { for my $domain ( _get_dirs("$orig_userdata/$type/2/$user") ) { if ( -f "$orig_userdata/$type/2/$user/$domain/cp_bw_all_limit.conf" ) { my $src_file = "$orig_userdata/$type/2/$user/$domain/cp_bw_all_limit.conf"; my $trg_file = "$ea4_userdata/$type/$user/$domain/cp_bw_all_limit.conf"; File::Path::Tiny::mk_parent($trg_file); _copy_file( $src_file, $trg_file ); _conf_test() or unlink($trg_file); } } } } } return; } sub _copy_file { my ( $src_file, $trg_file ) = @_; msglog( "info", "Copying $src_file to $trg_file." ); if ( system( "/bin/cp", "-p", $src_file, $trg_file ) != 0 ) { msglog( "error", "Could not copy $src_file to $trg_file. That will need done manually." ); } return; } sub _conf_test { # essentially the preflight sytax check that bin/build_apache_conf does my ( $test_httpd_conf, undef ) = Cpanel::TempFile->new()->file(); Cpanel::Config::Httpd::EA4::reset_cache(); my ( $returnval, $message ) = Cpanel::AdvConfig::generate_config_file( { 'service' => 'apache', 'force' => 1, '_target_conf_file' => $test_httpd_conf, 'skip_local' => 1, } ); # issafe if ($returnval) { ( $returnval, $message ) = Cpanel::AdvConfig::apache::check_syntax($test_httpd_conf); } if ( !$returnval ) { msglog( "warn", "Syntax test failed" ); msglog( "info", $message ); return; } return 1; } sub _migrate_include_files { my ( $src_dir, $trg_dir, $opts ) = @_; $opts ||= {}; msglog( "info", "Migrating $src_dir files to $trg_dir" ); if ( -l $src_dir ) { msglog( "warn", "$src_dir is a symlink (not a directory). You will need to recreate it manualy with an appropriate target for ea4." ); return; } elsif ( !-d _ ) { msglog( "info", "Nothing to do (src dir does not exist)." ); return; # signal that there is no need to go deeper } my @files = grep { !exists $includes_skip{$_} } _get_files("$src_dir"); if ( !@files ) { msglog( "info", "Nothing to do (no unskipped files in src dir)." ); return 1; # signal that we are good to go deeper } my $target_already_exists = -d $trg_dir ? 1 : 0; if ( !$target_already_exists ) { if ( !File::Path::Tiny::mk($trg_dir) ) { msglog( "error", "Could not create $trg_dir. You will need to manually migrate $src_dir to ea4." ); return; # signal that there is no need to go deeper; } } my @trg_files; for my $file (@files) { my $src_file = "$src_dir/$file"; if ( -l $src_file ) { msglog( "warn", "$src_file is a symlink (not a file). You will need to recreate it manualy with an appropriate target for ea4." ); next; } my $trg_file = defined $opts->{prefix} && length( $opts->{prefix} ) ? "$trg_dir/$opts->{prefix}-$file" : "$trg_dir/$file"; if ( -e $trg_file && -s $trg_file ) { msglog( "warn", "$trg_file already exists and has content: you will need to manually migrate $src_file to ea4." ); next; } _copy_file( $src_file, $trg_file ); push @trg_files, $trg_file; } if ( !_conf_test() ) { unlink(@trg_files); msglog( "error", "You will need to manually migrate $src_dir to ea4" ); if ( !$target_already_exists ) { msglog( "info", "Cleaning up $trg_dir" ); File::Path::Tiny::rm($trg_dir) or msglog( "warn", "Could not remove $trg_dir. That will need done manually." ); } } return 1; # signal that we are good to go deeper } sub convert_user_php_ini { my $parms = shift; my $pid; if ( $parms->{'skip-php-convert'} ) { msglog( 'info', 'Skipping PHP ini conversion: User request' ); return 1; } unless ( -x "$tools_dir/ea_convert_php_ini" ) { msglog( 'info', 'Skipping PHP ini conversion: Missing conversion script' ); return 1; } try { msglog( 'info', "Starting EA3 to EA4 php.ini conversion in the background (EXPECT HEAVY CPU and I/O LOADS)" ); $pid = Cpanel::Daemonizer::Tiny::run_as_daemon( sub { $0 = 'ea_convert_php_ini - Converting PHP Ini Files'; # so it doesn't appear that the migrate script is still running system("touch $tmp_dir/you_take_full_responsibility_do_not_do_this_manually.ea_convert_php_ini"); # create crazy file to reduce manual exec Cpanel::SafeRun::Simple::saferunnoerror( "$tools_dir/ea_convert_php_ini", '--action', 'sys' ); # provided by ea-cpanel-tools ea4 package } ); } catch { msglog( 'info', "A failure occurred while converting php.ini files: " . $_->to_string() ); $pid = 0; }; # returns pid for testing purposes, nothing actually uses these return values return $pid; } sub _invalid_parms { _help(); die "Invalid Command Line Parameters\n"; } sub _yum_can_assumeno { # From autofixer2’s ensure_yum_has_assumeno my $output = `yum --help | grep assumeno`; if ( $? != 0 ) { return; } return 1; } sub script { ## no critic qw(Subroutines::ProhibitExcessComplexity) -- needs scrum my (@argv) = @_; if ( Cpanel::DNSONLY::enabled() ) { die "Error: EasyApache4 is not available on DNSOnly Servers.\n"; } if ( !_yum_can_assumeno() ) { die "Error: yum is too old for ea4 (it does not support the --assumeno flag), please update yum (yum update -y yum) and try again.\n"; } $install_profile = 0; $| = 1; my ( $run, $help, $revert, $skip_convert, $install_profile, $convert_fail, $yes, $force, $reinstall, $skip_cloudlinux, $skip_blockers, $skip_php_convert, $custom_logfile_path ); my $opts = Getopt::Long::GetOptionsFromArray( \@argv, 'run' => \$run, 'help' => \$help, 'revert' => \$revert, 'skip_convert' => \$skip_convert, 'convert_fail' => \$convert_fail, 'y' => \$yes, 'yes' => \$yes, 'force' => \$force, 'reinstall' => \$reinstall, 'install_profile=s' => \$install_profile, 'skip-cloudlinux' => \$skip_cloudlinux, # DO NOT DOCUMENT THIS FLAG, IT IS PRIVATE FOR CLOUDLINUX SCRIPTS TO USE 'skip-blockers', => \$skip_blockers, 'skip-php-convert' => \$skip_php_convert, 'logfile_path=s' => \$custom_logfile_path, ) or _invalid_parms(); if ($custom_logfile_path) { $logfile_path = $custom_logfile_path; $logger = Cpanel::Logger->new( { 'alternate_logfile' => $logfile_path } ); } # this has to be done early, in case we jump to CloudLinux where this # needs to be in place. my $syspkg = Cpanel::SysPkgs::YUM->new( {} ); if ( defined $run && !$skip_cloudlinux && Cpanel::CloudLinux::Installed::installed() ) { $syspkg->ensure_plugins_turned_on(); my $cl_url = "https://repo.cloudlinux.com/cloudlinux/sources/cloudlinux_ea3_to_ea4"; my $cl_script = "$ENV{HOME}/cloudlinux_ea3_to_ea4"; my $res = Cpanel::HTTP::Client->new->mirror( $cl_url, $cl_script ); if ( !$res->success() ) { msglog( 'info', locale->maketext( "Could not download “[_1]” to “[_2]”: [_3]", $cl_url, $cl_script, "$res->{status} $res->{reason}" ) . "\n" ); exit(1); # please use something like Test::Exit if you want to test this, no need for exit_testable();return which his kind of gross } chmod( 0755, $cl_script ) or die "Failed to make $cl_script executable\n"; my $flag = defined $revert ? '--revert' : '--convert'; exec("$cl_script $flag"); # please use something like Test::Exec if you want to test this (instead of inventing a mechanism similar to exit_testable();return) } if ( defined $help ) { _help(); exit_testable(0); return 1; # testability } $syspkg->ensure_plugins_turned_on(); if ( !defined $run ) { _help("You must pass --run to indicate you really want to run this script."); exit_testable(1); return; # testability } if ( defined $install_profile ) { $skip_convert = 1; # I cannot check for the profile file as it is not likely on the # system yet. } $show_log_link = 1; if ( defined $revert ) { if ( !Cpanel::Config::Httpd::EA4::is_ea4() ) { if ($force) { msglog( 'info', locale->maketext("Attempting revert by force.") ); } else { die "--revert only applies to systems running EasyApache4\n"; } } } else { if ( Cpanel::Config::Httpd::EA4::is_ea4() ) { if ($reinstall) { msglog( 'info', locale->maketext("Attempting reinstallation of [asis,EasyApache4].") ); } elsif ($force) { msglog( 'info', locale->maketext("Attempting migrate by force.") ); } else { msglog( 'die', "System is already converted to EasyApache4" ); exit; } } } # We need to wait for the current EA3 process to finish before migrating if ( Cpanel::Config::Httpd::get_message_if_apache_is_building() ) { die "This system is currently building an EasyApache 3 stack. Please wait for that process to finish before migrating to EasyApache 4\n"; } # Check if the current system is compatible with EA4 die "EasyApache 4 is not supported on this Operating System\n" if !Cpanel::EA4::Util::is_os_ea4_compatible(); my $parms = {}; $parms->{'run'} = 1 if defined $run; $parms->{'revert'} = 1 if defined $revert; $parms->{'skip_convert'} = 1 if defined $skip_convert || defined $reinstall; $parms->{'skip-cloudlinux'} = 1 if defined $skip_cloudlinux; $parms->{'skip-blockers'} = 1 if defined $skip_blockers; $parms->{'skip-blockers'} = 1 if defined $force && $force; $parms->{'convert_fail'} = 1 if defined $convert_fail; $parms->{'install_profile'} = $install_profile if defined $install_profile; $parms->{'yes'} = $yes if defined $yes; $parms->{'force'} = $force if defined $force; $parms->{'reinstall'} = $reinstall if defined $reinstall; $parms->{'default_profile'} = "$base_ea4_cfgdir/profiles/cpanel/default.json"; $parms->{'skip-php-convert'} = 1 if defined $skip_php_convert; # prevent cache problems, this needs to happen for both revert and normal migrations unlink "/var/cpanel/globalcache/cpanel.cache" if -e "/var/cpanel/globalcache/cpanel.cache"; if ($revert) { _prompt_for_revert($parms) unless $force; run_hooks("pre_revert"); eval { _revert() }; pureftpd_conf($revert); # TODO: Convert php ini files back to EA3 if we're coming from EA4? exit(1) if $@; run_hooks("post_revert"); exit(0); } if ($reinstall) { run_hooks("pre_reinstall"); _reinstall_ea4(); run_hooks("post_reinstall"); exit_testable(0); return; # testability } php_version_check($parms) unless $force; migration_warning($parms) unless $force; migrate_blockers($parms) unless $skip_blockers; run_hooks("pre_migration"); remove_noteable_packages($parms); preliminary_work($parms); convert_ea3_profile($parms); move_to_ea4($parms); install_profile($parms); # before you add anything here: should it go in _post_install_profile_tasks() instead? return 1; } exit( script(@ARGV) ? 0 : 1 ) unless caller(); sub _remove_flagfile { unlink '/var/cpanel/conf/is_ea4', '/etc/cpanel/ea4/is_ea4'; # the var one is a possible stale one, we realized the need to change paths in HB-403 if ( -e '/etc/cpanel/ea4/is_ea4' ) { msglog( 'info', "/etc/cpanel/ea4/is_ea4 could not be removed, please do so manually" ); } return; } sub _help { my ($msg) = @_; chomp($msg) if $msg; msglog( 'info', $msg ) if $msg; msglog( 'info', "Usage: $0 --run Migrates a system from ea3 to ea4 --run Indicates you want to run this script --help This screen. No arguments will have the same effect. --revert Migrate from ea4 back to ea3 --skip_convert Do not convert and install the ea3 profile this will install the ea4 cPanel Default profile unless you set --install_profile command. --install_profile profile_path This assumes you mean --skip_convert and will install this profile instead. You must give it a full path. --convert_fail When you convert an ea3 profile to an ea4 profile, this prevents the tool's error correction code from executing. This may cause a failure during provisioning, which is useful for testing. Use at your own risk. -y | --y | -yes | --yes Answer y (yes) to all yes/no questions. --force Ignore the protection checks and do the migrate or revert commands. --reinstall Yum will attempt to reinstall currently installed ea4 packages. --skip-blockers On migrate the system looks for problems that could prevent the migration. If you want to skip these blockers set this flag. --logfile_path logfile_path Override the default location of the logfile that is made from the script run. You must give it a full path. EXIT CODES If the overall operation works it will exit 0, if there is a problem it exits non-zero. Some special non-zero exit codes: 10 migrate failed and it reverted to ea3 11 migrate failed and the revert to ea3 failed 20 migrate failed and you chose to abort and leave it in a broken state 30 migrate failed and then it installed default profile OK 31 migrate failed and the attempt to install the default prompt also failed EXAMPLE USAGE Convert an EA3 system to EA4: /scripts/migrate_ea3_to_ea4 --run Revert an EA4 system back to EA3 (must have had EA3 already from a previous update): /scripts/migrate_ea3_to_ea4 --run --revert Convert an EA3 system to EA4 using a specific profile without prompts or sanity checking: /scripts/migrate_ea3_to_ea4 --run --yes --force --skip-blockers --install_profile /full/path/to/an_ea4_profile.json " ); return; } sub is_owned_by_ea_rpm { my ($file) = @_; my $response = `rpm -qf $file 2>&1`; chomp $response; return 1 if ( $response =~ m/^ea-/ ); # error, or not owned by an rpm that belongs to ea return 0; } sub _migrate_non_apache_ea3_items { my $file_hr = _get_ea3_files_hr(); for my $file ( keys %{$file_hr} ) { if ( -e $file ) { if ( is_owned_by_ea_rpm($file) ) { msglog( 'info', locale->maketext( "Skipping delete of “[_1]”.", $file ) ); } else { unlink $file; } } } return; } sub _revert { if ( !-d "$base_apache.ea3" ) { if ( _ula_looks_like_ea4() ) { msglog( 'die', "$base_apache.ea3 does not exist" ); exit; } else { # looking for cases where they hit ctrl-c before we finished reverting. msglog( 'info', locale->maketext( "Skipping move of the “[_1]” directory because it looks like [asis,EasyApache 3].", "$base_apache.ea3" ) . "\n" ); } } # uninstall the packages my $output = Cpanel::SafeRun::Errors::saferunallerrors( "yum", "-y", "remove", "ea-*" ); # ? TODO ?: something akin to: yum remove $(package-cleanup --orphans) msglog( 'info', $output ); # remove repo file unlink $Cpanel::EA4::Util::repo_file_path; # force fresh cache system("rm -rf /var/cpanel/cache/Cpanel-PackMan*"); # remove flag file _remove_flagfile(); # move the ea4 060-symlink script’s /usr/local/apache to /usr/local/apache.ea4 if ( -d $base_apache && _ula_looks_like_ea4() ) { system("rm -rf $base_apache.ea4"); system("mv $base_apache $base_apache.ea4"); } if ( !-d $base_apache ) { # restore previous apache msglog( 'info', "Restoring $base_apache.ea3 to $base_apache" ); system("mv $base_apache.ea3 $base_apache"); } if ( -x "$base_apache/bin/apxs" ) { msglog( 'info', "Restoring ea3's $apxs" ); system("cp -p $base_apache/bin/apxs $apxs"); } if ( Cpanel::Sys::OS::getreleaseversion() >= 7.0 && -d '/etc/systemd/system/' ) { if ( !-e '/etc/systemd/system/httpd.service' ) { if ( open( my $fh, '>', '/etc/systemd/system/httpd.service' ) ) { print {$fh} <<"END_CONT"; [Unit] Description=Apache webserver managed by cPanel EasyApache ConditionPathExists=!/etc/httpddisable ConditionPathExists=!/etc/apachedisable ConditionPathExists=!/etc/httpdisable [Service] Type=forking ExecStart=/usr/local/cpanel/scripts/restartsrv_httpd --no-verbose ExecStop=/usr/local/cpanel/scripts/restartsrv_httpd stop --no-verbose [Install] WantedBy=multi-user.target END_CONT close($fh); } else { msglog( 'info', "Could not open “/etc/systemd/system/httpd.service” for writing: $!" ); } } } _restore_non_apache_ea3_items(); Cpanel::ProgLang::Supported::php::Ini::setup_session_save_path(); msglog( 'info', "Rebuilding configuration files and restarting Apache." ); msglog( 'info', Cpanel::SafeRun::Errors::saferunallerrors("/usr/local/cpanel/scripts/rebuildhttpdconf") ); msglog( 'info', Cpanel::SafeRun::Errors::saferunallerrors( "/usr/local/cpanel/scripts/restartsrv_httpd", "--restart" ) ); rename $base_ea4_cfgdir, $base_ea4_cfgdir . '.' . time(); msglog( 'info', locale->maketext("[asis,EasyApache 4] has successfully been reverted.") . "\n" ); # All done return; } sub _restore_non_apache_ea3_items { msglog( 'info', 'Restoring and rebuilding EA3 components that may have been overwritten by an EA4 package. Please be patient as this may take a few minutes.' ); my $output = Cpanel::SafeRun::Errors::saferunallerrors( "/usr/local/cpanel/scripts/easyapache", "--build", "--skip=Apache" ); msglog( 'info', $output ); # rebuild any symlinks the build did not do my $file_hr = _get_ea3_files_hr(); for my $file ( keys %{$file_hr} ) { next if !defined $file_hr->{$file}; # if it is not a symlink next if -l $file || -e _; # if it already a symlink symlink( $file_hr->{$file}, $file ) || msglog( 'info', "Could not symlink($file_hr->{$file}, $file): $!" ); } return; } sub _get_ea3_files_hr { # See HB-313 for info on where this info comes from return { '/usr/sbin/httpd' => "$base_apache/bin/apachectl", '/etc/httpd' => $base_apache, '/usr/bin/php' => undef, '/usr/bin/php5-cgi' => '/usr/bin/php', '/usr/bin/php-cgi' => '/usr/bin/php', '/usr/local/bin/php' => undef, '/usr/bin/php5-cli' => '/usr/local/bin/php', '/usr/bin/php5' => '/usr/local/bin/php', '/usr/bin/php-cli' => '/usr/local/bin/php', }; } sub _reinstall_ea4 { my $output = Cpanel::SafeRun::Errors::saferunallerrors( "yum", "-y", "install", "yum-plugin-tsflags", "yum-plugin-universal-hooks", "ea-profiles-cpanel", "ea-cpanel-tools" ); msglog( 'info', $output ); $output = Cpanel::SafeRun::Errors::saferunallerrors( "yum", "-y", "reinstall", "ea-*" ); msglog( 'info', $output ); return; } sub set_ea4_default_php_version { my ($current_php_version) = @_; my $default_version = Cpanel::EA4::Util::get_target_php_version($current_php_version); my $handler; my $ea3_php_conf; eval { $ea3_php_conf = Cpanel::YAML::LoadFile("$base_apache/conf/php.conf.yaml") }; if ($ea3_php_conf) { if ( $ea3_php_conf->{php5} ) { $handler = $ea3_php_conf->{php5}; } else { msglog( 'info', "Could not determine the Apache handler for PHP." ); $handler = Cpanel::EA4::Util::get_default_php_handler(); } } else { msglog( 'info', "Apache was not configured to use PHP or the configuration file is invalid." ); $handler = Cpanel::EA4::Util::get_default_php_handler(); } msglog( 'info', "Setting the system default PHP version to v$default_version using the Apache '$handler' handler." ); # Manually create php.conf for EA4 # NOTE: Don't use the Cpanel::ProgLang namespace since those APIs expect it to already be set up _ensure_dir(); Cpanel::DataStore::edit_datastore( "$base_ea4_cfgdir/php.conf", sub { my ($hr) = @_; my $version = $default_version =~ s/\.//gr; # e.g. 5.6 -> 56 my $package = "ea-php$version"; $hr->{default} = $package; $hr->{$package} = $handler; } ); return $default_version; } sub run_hooks { my ($hook) = @_; my @hooks = glob("$hook_dir/$hook/*"); for my $hook (@hooks) { msglog( 'info', "Hook starting: $hook" ); if ( -x $hook ) { my $output = Cpanel::SafeRun::Errors::saferunallerrors($hook); my $status = $? >> 8; msglog( 'info', $output ); msglog( 'info', "Hook [$hook] finished, exited: $status" ); } else { msglog( 'info', "Hook [$hook] is not executable so it was skipped." ); } } return; } # Case EA-5817: pure-ftpd xferlog file location needs to be changed depending on # if moving to or from EA4. sub pureftpd_conf { my ($is_revert) = @_; my $log_location = $new_base_apache . '/logs/domlogs/ftpxferlog'; if ($is_revert) { $log_location = $base_apache . '/domlogs/ftpxferlog'; } if ( -e $ftpdconf ) { if ( open( my $ftpdconf_read_fh, '<', $ftpdconf ) ) { my @contents = (<$ftpdconf_read_fh>); close($ftpdconf_read_fh); if ( open( my $ftpdconf_write_fh, '>', $ftpdconf ) ) { foreach my $line (@contents) { $line =~ s/AltLog xferlog.*/AltLog xferlog:$log_location/; print {$ftpdconf_write_fh} $line; } close($ftpdconf_write_fh); } } } return 1; } 1;