#!/usr/local/cpanel/3rdparty/bin/perl ## ---------------------------------------------------------------------------- ## ## DO NOT EDIT THIS FILE ## ## This file is automatically generated from script/elevate-cpanel.PL ## ## view https://github.com/cpanel/elevate for more details ## ## ---------------------------------------------------------------------------- BEGIN { # Suppress load of all of these at earliest point. $INC{'Elevate/Constants.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Base.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/AbsoluteSymlinks.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/AutoSSL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/BootKernel.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/CCS.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/CloudLinux.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/cPanelPlugins.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/cPanelPrep.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/DatabaseUpgrade.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/DiskSpace.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Distros.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/DNS.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/EA4.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/ElevateScript.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/ELS.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Grub2.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Imunify.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/InfluxDB.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/IsContainer.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/JetBackup.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/KernelCare.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Kernel.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Leapp.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Lists.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/LiteSpeed.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/MountPoints.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/MySQL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/NICs.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/NixStats.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/OVH.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PackageRestore.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PackageDupes.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Panopta.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PECL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PerlXS.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/PostgreSQL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/R1Soft.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Repositories.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/RmMod.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/RpmDB.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/SSH.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Softaculous.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/UnconvertedModules.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/UpdateReleaseUpgrades.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/UpdateSystem.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/WHM.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/WPToolkit.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Acronis.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CentOS7.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CloudLinux7.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/RHEL.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/Ubuntu.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/Ubuntu20.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/PkgMgr.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/PkgMgr/APT.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/PkgMgr/Base.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/PkgMgr/YUM.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Database.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/EA4.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Fetch.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Leapp.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Logger.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Marker.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Motd.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/NICs.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Notify.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Roles/Run.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Script.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Service.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/StageFile.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Stages.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/SystemctlService.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Usage.pm'} = 'script/elevate-cpanel.PL.static'; } { # --- BEGIN lib/Elevate/Constants.pm package Elevate::Constants; use cPstrict; use constant SERVICE_DIR => '/etc/systemd/system/'; use constant SERVICE_NAME => 'elevate-cpanel.service'; use constant LOG_FILE => q[/var/log/elevate-cpanel.log]; use constant PID_FILE => q[/var/run/elevate-cpanel.pid]; use constant DEFAULT_GRUB_FILE => '/etc/default/grub'; use constant YUM_REPOS_D => q[/etc/yum.repos.d]; use constant ELEVATE_BACKUP_DIR => "/root/.elevate.backup"; use constant RPMDB_DIR => q[/var/lib/rpm]; use constant RPMDB_BACKUP_DIR => q[/var/lib/rpm-elevate-backup]; use constant IMUNIFY_AGENT => '/usr/bin/imunify360-agent'; use constant CHKSRVD_SUSPEND_FILE => q[/var/run/chkservd.suspend]; use constant IGNORE_OUTDATED_SERVICES_FILE => q[/etc/cpanel/local/ignore_outdated_services]; use constant SBIN_IP => q[/sbin/ip]; use constant ETH_FILE_PREFIX => q[/etc/sysconfig/network-scripts/ifcfg-]; use constant R1SOFT_REPO => 'r1soft'; use constant R1SOFT_REPO_FILE => '/etc/yum.repos.d/r1soft.repo'; use constant R1SOFT_MAIN_AGENT_PACKAGE => 'serverbackup-agent'; use constant R1SOFT_AGENT_PACKAGES => qw{ r1soft-getmodule serverbackup-agent serverbackup-async-agent-2-6 serverbackup-enterprise-agent serverbackup-setup }; use constant ACRONIS_BACKUP_PACKAGE => 'acronis-backup-cpanel'; use constant ACRONIS_OTHER_PACKAGES => qw{ BackupAndRecoveryAgent BackupAndRecoveryBootableComponents dkms file_protector snapapi26_modules }; use constant POSTGRESQL_SYSTEM_DATADIR => '/var/lib/pgsql/data'; use constant OVH_MONITORING_TOUCH_FILE => '/var/cpanel/acknowledge_ovh_monitoring_for_elevate'; 1; } # --- END lib/Elevate/Constants.pm { # --- BEGIN lib/Elevate/Components/Base.pm package Elevate::Components::Base; use cPstrict; use Carp (); use Cpanel::JSON (); use Simple::Accessor qw( components ); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } BEGIN { my @_DELEGATE_TO_CPEV = qw{ getopt upgrade_distro_manually ssystem ssystem_and_die ssystem_capture_output ssystem_hide_and_capture_output }; foreach my $subname (@_DELEGATE_TO_CPEV) { no strict 'refs'; *$subname = sub ( $self, @args ) { my $cpev = $self->cpev or Carp::confess(qq[Cannot find cpev to call $subname]); my $sub = $cpev->can($subname) or Carp::confess(qq[cpev does not support $subname]); return $sub->( $cpev, @args ); } } } sub _build_components { if ( $0 =~ qr{\bt/} ) { return Elevate::Components->new; } Carp::confess(q[Missing components]); } sub cpev ($self) { return $self->components->cpev; } sub run_once ( $self, $subname ) { my $cpev = $self->cpev; my $run_once = $cpev->can('run_once') or Carp::confess(qq[cpev does not support 'run_once']); my $label = ref($self) . "::$subname"; my $sub = $self->can($subname) or Carp::confess(qq[$self does not support '$subname']); my $code = sub { return $sub->($self); }; return $run_once->( $cpev, $label, $code ); } sub is_check_mode ( $self, @args ) { return $self->components->is_check_mode(@args); } sub has_blocker ( $self, $msg, %others ) { my $caller_id; if ( $others{'blocker_id'} ) { $caller_id = $others{'blocker_id'}; } else { ( undef, undef, undef, $caller_id ) = caller(1); $caller_id ||= ref $self; } my $analytics_data; if ( $others{info} ) { my $info = delete $others{info}; if ( ref $info eq 'HASH' ) { $analytics_data = Cpanel::JSON::canonical_dump( [$info] ); } else { my ( $latest_version, $self_version ) = ( $self->cpev->script->latest_version(), cpev::VERSION() ); if ( $self_version > $latest_version ) { die "Invalid data analytics given to blocker. 'info' must be a hash reference.\n"; } } } my $blocker = cpev::Blocker->new( id => $caller_id, msg => $msg, %others, info => $analytics_data ); $self->components->add_blocker($blocker); die $blocker if $self->components->abort_on_first_blocker(); if ( !$others{'quiet'} ) { WARN( <<~"EOS"); *** Elevation Blocker detected: *** $msg EOS } return $blocker; } sub check ($self) { return; } sub pre_distro_upgrade ($self) { return; } sub post_distro_upgrade ($self) { return; } { package cpev::Blocker; use Simple::Accessor qw{ id msg info }; sub TO_JSON ($self) { my %hash = $self->%*; return \%hash; } } 1; } # --- END lib/Elevate/Components/Base.pm { # --- BEGIN lib/Elevate/Components.pm package Elevate::Components; use cPstrict; use Elevate::Components::Base (); use Elevate::Components::AbsoluteSymlinks (); use Elevate::Components::AutoSSL (); use Elevate::Components::BootKernel (); use Elevate::Components::CCS (); use Elevate::Components::CloudLinux (); use Elevate::Components::cPanelPlugins (); use Elevate::Components::cPanelPrep (); use Elevate::Components::DatabaseUpgrade (); use Elevate::Components::DiskSpace (); use Elevate::Components::Distros (); use Elevate::Components::DNS (); use Elevate::Components::EA4 (); use Elevate::Components::ElevateScript (); use Elevate::Components::ELS (); use Elevate::Components::Grub2 (); use Elevate::Components::Imunify (); use Elevate::Components::InfluxDB (); use Elevate::Components::IsContainer (); use Elevate::Components::JetBackup (); use Elevate::Components::KernelCare (); use Elevate::Components::Kernel (); use Elevate::Components::Leapp (); use Elevate::Components::Lists (); use Elevate::Components::LiteSpeed (); use Elevate::Components::MountPoints (); use Elevate::Components::MySQL (); use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::OVH (); use Elevate::Components::PackageRestore (); use Elevate::Components::PackageDupes (); use Elevate::Components::Panopta (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); use Elevate::Components::PostgreSQL (); use Elevate::Components::R1Soft (); use Elevate::Components::Repositories (); use Elevate::Components::RmMod (); use Elevate::Components::RpmDB (); use Elevate::Components::SSH (); use Elevate::Components::Softaculous (); use Elevate::Components::UnconvertedModules (); use Elevate::Components::UpdateReleaseUpgrades (); use Elevate::Components::UpdateSystem (); use Elevate::Components::WHM (); use Elevate::Components::WPToolkit (); use Elevate::Components::Acronis (); use Simple::Accessor qw( cpev check_mode blockers abort_on_first_blocker ); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Cpanel::JSON (); our @CHECKS = qw{ IsContainer ElevateScript MountPoints SSH DiskSpace WHM Distros CloudLinux Imunify DNS MySQL Repositories Lists JetBackup KernelCare NICs EA4 BootKernel Grub2 OVH AbsoluteSymlinks AutoSSL }; our @NOOP_CHECKS = qw{ CCS DatabaseUpgrade ELS InfluxDB Kernel LiteSpeed NixStats PECL PackageRestore PackageDupes Panopta PerlXS PostgreSQL R1Soft RmMod RpmDB Softaculous UnconvertedModules UpdateReleaseUpgrades UpdateSystem WPToolkit cPanelPlugins cPanelPrep Acronis }; push @CHECKS, @NOOP_CHECKS; push @CHECKS, 'Leapp'; # This blocker has to run last! use constant ELEVATE_BLOCKER_FILE => '/var/cpanel/elevate-blockers'; our $_CHECK_MODE; # for now global so we can use the helper (move it later to the object) sub _build_blockers { return []; } sub check ($self) { # do_check - main entry point if ( $self->cpev->service->is_active ) { WARN("An elevation process is already in progress."); return 1; } my $stage = Elevate::Stages::get_stage(); if ( $stage != 0 && $stage <= cpev::VALID_STAGES() ) { die <<~"EOS"; An elevation process is currently in progress: running stage $stage You can check the log by running: /scripts/elevate-cpanel --log or check the elevation status: /scripts/elevate-cpanel --status EOS } Elevate::Components::Distros::bail_out_on_inappropriate_distro(); my $blocker_file = $self->cpev->getopt('check') || ELEVATE_BLOCKER_FILE; my $has_blockers = $self->_has_blockers( $self->cpev->getopt('start') ? 0 : 1 ); $self->save( $blocker_file, { 'blockers' => $self->{'blockers'} } ); if ($has_blockers) { WARN( <<~'EOS' ); Please fix the detected issues before performing the elevation process. Read More: https://cpanel.github.io/elevate/blockers/ EOS } else { my $cmd = q[/scripts/elevate-cpanel --start]; INFO( <<~"EOS" ); There are no known blockers to start the elevation process. You can consider running: $cmd EOS } return $has_blockers; } sub _has_blockers ( $self, $check_mode = 0 ) { unless ( $< == 0 ) { ERROR("This script can only be run by root"); return 666; } $_CHECK_MODE = !!$check_mode; # running with --check $self->abort_on_first_blocker(0); my $ok = eval { $self->_check_all_blockers; 1; }; if ( !$ok ) { my $error = $@; if ( ref $error eq 'cpev::Blocker' ) { ERROR( $error->{msg} ); return 401; } WARN("Unknown error while checking blockers: $error"); return 127; # unknown error } return scalar $self->blockers->@*; } sub num_blockers_found ($self) { return scalar $self->blockers->@*; } sub add_blocker ( $self, $blocker ) { push $self->blockers->@*, $blocker; return; } sub is_check_mode ($) { return $_CHECK_MODE; } sub save ( $self, $path, $stash ) { open( my $fh, '>', $path ) or LOGDIE( "Failed to open " . $path . ": $!" ); print {$fh} Cpanel::JSON::pretty_canonical_dump($stash); close $fh; return 1; } sub _check_all_blockers ($self) { # sub _blockers_check ($self) { foreach my $blocker (@CHECKS) { # preserve order $self->_check_single_blocker($blocker); } return 0; } sub _check_single_blocker ( $self, $name ) { my $blocker = $self->_get_blocker_for($name); my $check = $blocker->can('check') or die qq[Missing check function from ] . ref($blocker); return $check->($blocker); } sub _get_blocker_for ( $self, $name ) { # useful for tests my $pkg = "Elevate::Components::$name"; # need to be loaded return $pkg->new( components => $self ); } 1; } # --- END lib/Elevate/Components.pm { # --- BEGIN lib/Elevate/Components/AbsoluteSymlinks.pm package Elevate::Components::AbsoluteSymlinks; use cPstrict; use Cpanel::Chdir (); use Cpanel::UUID (); use File::Copy (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub get_abs_symlinks ($self) { my %links; foreach my $entry ( glob "/*" ) { my $path = readlink($entry); # don't bother with stat, this is fast next unless $path && substr( $path, 0, 1 ) eq '/'; $links{$entry} = $path; } return %links; } sub pre_distro_upgrade ($self) { $self->ssystem(qw{/usr/bin/ln -snf usr/local/cpanel/scripts /scripts}); $self->_absolute_symlinks; return; } sub _absolute_symlinks ($self) { my %links = $self->get_abs_symlinks(); return unless %links; my $chdir = Cpanel::Chdir->new("/"); foreach my $link ( keys(%links) ) { my $updated = substr( $links{$link}, 1 ); my $rand_uid = Cpanel::UUID::random_uuid(); my $tries = 0; while ( -e "$link-$rand_uid" && $tries++ < 10000 ) { $rand_uid = Cpanel::UUID::random_uuid(); } symlink( $updated, "$link-$rand_uid" ) or die "Can't create symlink $link-$rand_uid to $updated: $!"; File::Copy::mv( "$link-$rand_uid", $link ) or die "Can't overwite $link: $!"; } return; } sub check ($self) { my %links = $self->get_abs_symlinks(); WARN( "Symlinks with absolute paths have been found in /:\n\t" . join( ", ", sort keys(%links) ) . "\n" . "This can cause problems during the leapp run, so\n" . 'these will be corrected to be relative symlinks before elevation.' ) if %links; return; } 1; } # --- END lib/Elevate/Components/AbsoluteSymlinks.pm { # --- BEGIN lib/Elevate/Components/AutoSSL.pm package Elevate::Components::AutoSSL; use cPstrict; use Cpanel::SSL::Auto (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { return $self->_check_autossl_provider(); } sub _check_autossl_provider ($self) { if ( $self->is_using_sectigo() ) { WARN( <<~"EOS" ); Elevating with Sectigo as the provider for AutoSSL is not supported. If you proceed with this upgrade, we will switch your system to use the Let's Encrypt™ provider. EOS } return 0; } sub pre_distro_upgrade ($self) { if ( $self->is_using_sectigo() ) { $self->ssystem_and_die(qw{/usr/local/cpanel/scripts/autorepair set_autossl_to_lets_encrypt}); } return; } sub is_using_sectigo ($self) { my @providers = Cpanel::SSL::Auto::get_all_provider_info(); foreach my $provider (@providers) { next unless ( ref $provider eq 'HASH' && $provider->{enabled} ); if ( defined $provider->{display_name} && $provider->{display_name} =~ /sectigo/i ) { return 1; } } return 0; } 1; } # --- END lib/Elevate/Components/AutoSSL.pm { # --- BEGIN lib/Elevate/Components/BootKernel.pm package Elevate::Components::BootKernel; use cPstrict; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Elevate::Constants (); use Cpanel::Kernel::Status (); use Cpanel::Exception (); use Cpanel::YAML (); use Cpanel::JSON (); use Try::Tiny; sub check ($self) { return 1 unless $self->upgrade_distro_manually; # skip when --upgrade-distro-manually is provided my $ok = 0; try { my ( $running_version, $boot_version ) = Cpanel::Kernel::Status::reboot_status()->@{ 'running_version', 'boot_version' }; $ok = $running_version eq $boot_version; $self->has_blocker( <<~EOS ) if !$ok; The running kernel version ($running_version) does not match that of the default boot entry ($boot_version). This could be due to the kernel being changed by an update, meaning that a reboot should resolve this. However, this also could indicate that the system does not have control over which kernel and early boot environment (initrd) is used upon reboot, which is required to upgrade the operating system with this script. If this message remains after a reboot, your server may have been configured to boot into a particular kernel directly rather than to an instance of the GRUB2 boot loader. This often happens to virtualized servers, but physical servers also can have this problem under certain configurations. Your provider may have a solution to allow booting into GRUB2; contact them for further information. EOS } catch { my $ex = $_; $self->has_blocker( "Unable to determine running and boot kernels due to the following error:\n" # . _to_str($ex) ); }; return $ok ? 1 : 0; } sub _to_str ($e) { $e //= ''; my $str = Cpanel::Exception::get_string($e); if ( length $str ) { my $hash = eval { Cpanel::YAML::Load($str) } # parse yaml // eval { Cpanel::JSON::Load($str) } # or json output... we cannot predict // {}; if ( ref $hash eq 'HASH' && $hash->{msg} ) { $str = $hash->{msg}; } } return $str; } 1; } # --- END lib/Elevate/Components/BootKernel.pm { # --- BEGIN lib/Elevate/Components/CCS.pm package Elevate::Components::CCS; use cPstrict; use Try::Tiny; use File::Path (); use File::Copy (); use Cpanel::Autodie (); use Cpanel::Config::Users (); use Cpanel::JSON (); use Cpanel::Pkgr (); use Elevate::Notify (); use Elevate::PkgMgr (); use Elevate::StageFile (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant CCS_PACKAGE => 'cpanel-ccs-calendarserver'; use constant ZPUSH_PACKAGE => 'cpanel-z-push'; use constant EXPORT_DIR => '/var/cpanel/elevate_ccs_export'; use constant CCS_RESTART_SCRIPT => '/usr/local/cpanel/scripts/restartsrv_cpanel_ccs'; use constant TASK_QUEUE_SCRIPT => '/usr/local/cpanel/bin/servers_queue'; use constant DUMP_TYPES => ( calendars => 'ics', contacts => 'vcard', ); sub pre_distro_upgrade ($self) { my $ccs_installed = Cpanel::Pkgr::is_installed(CCS_PACKAGE); Elevate::StageFile::update_stage_file( { ccs_installed => $ccs_installed } ); return unless $ccs_installed; $self->_load_ccs_modules(); $self->run_once('export_ccs_data'); $self->remove_ccs_and_dependencies(); $self->clean_up_pkg_cruft(); return; } sub clean_up_pkg_cruft ($self) { $self->remove_cpanel_ccs_home_directory(); return; } sub remove_cpanel_ccs_home_directory ($self) { File::Path::remove_tree('/opt/cpanel-ccs') if -d '/opt/cpanel-ccs'; return; } sub remove_ccs_and_dependencies ($self) { my $zpush_installed = Cpanel::Pkgr::is_installed(ZPUSH_PACKAGE); Elevate::StageFile::update_stage_file( { zpush_installed => $zpush_installed } ); my @ccs_dependencies; push @ccs_dependencies, ZPUSH_PACKAGE(); Elevate::PkgMgr::remove( CCS_PACKAGE(), @ccs_dependencies ); return; } sub _load_ccs_modules ($self) { require Cpanel::LoadModule::Custom; Cpanel::LoadModule::Custom::load_perl_module('Cpanel::CCS::Delegates'); Cpanel::LoadModule::Custom::load_perl_module('Cpanel::CCS::DBUtils'); Cpanel::LoadModule::Custom::load_perl_module('Cpanel::CCS::Userdata'); return; } sub export_ccs_data ($self) { my $export_dir = EXPORT_DIR(); INFO("Exporting CCS data to '$export_dir'. A backup of this data will be left in place after elevate completes."); $self->_ensure_export_directory(); my @users = Cpanel::Config::Users::getcpusers(); foreach my $user (@users) { INFO(" Exporting data for $user"); $self->_export_data_for_single_user($user); } INFO('Completed exporting CCS data for all users'); return; } sub _export_data_for_single_user ( $self, $user ) { my $users_ccs_info = $self->_get_ccs_info_for_user($user); my @webmail_users = keys %{ $users_ccs_info->{users} }; next if ( !@webmail_users ); $self->_make_backup_paths_for_user($user); $self->_dump_persistence_data_for_user($user); $self->_dump_delegation_data_for_user($user); foreach my $webmail_user (@webmail_users) { $self->_process_calendar_and_contacts_for_webmail_user( $user, $webmail_user ); } return; } sub _process_calendar_and_contacts_for_webmail_user ( $self, $user, $webmail_user ) { my $path = $self->_get_export_path_for_user($user); my $users_ccs_info = $self->_get_ccs_info_for_user($user); my $uuid = $users_ccs_info->{users}{$webmail_user}; my %dump_types = DUMP_TYPES(); my $dbh = $self->_get_dbh(); foreach my $type ( keys %dump_types ) { my ( $query_string, $query_args ) = $self->_get_query_for_type( $type, $uuid ); my $sth = $dbh->prepare($query_string); $sth->execute(@$query_args); my $num_rows = $sth->rows; next if !$num_rows; my $dump_file = "$path/$type/${uuid}_${type}.$dump_types{$type}"; Cpanel::Autodie::open( my $dh, ">", $dump_file ); binmode( $dh, ":encoding(UTF-8)" ) or die "Can't set binmode to UTF-8 on $dump_file: $!"; while ( my $text = $sth->fetch ) { for (@$text) { my $txt = $_; $txt =~ tr/'//d; print $dh $txt; } } } return; } sub _dump_delegation_data_for_user ( $self, $user ) { my $path = $self->_get_export_path_for_user($user); my $dbh = $self->_get_dbh(); my @webmail_users_info = Cpanel::CCS::Userdata::get_users($user); my $delegates_ar = Cpanel::CCS::Delegates::get( @webmail_users_info, $dbh ); my $delegate_file = $path . '/' . 'delegates.json'; Cpanel::JSON::DumpFile( $delegate_file, $delegates_ar ); return; } sub _dump_persistence_data_for_user ( $self, $user ) { my $path = $self->_get_export_path_for_user($user); my $persistence_file = $path . '/' . 'persistence.json'; my $users_ccs_info = $self->_get_ccs_info_for_user($user); Cpanel::JSON::DumpFile( $persistence_file, $users_ccs_info ); return; } sub _make_backup_paths_for_user ( $self, $user ) { my $path = $self->_get_export_path_for_user($user); File::Path::make_path($path); my %dump_types = DUMP_TYPES(); for ( keys(%dump_types) ) { File::Path::make_path("$path/$_"); } return; } sub _get_query_for_type ( $self, $type, $uuid ) { my %querydata = ( 'calendars' => { 'args' => [ $uuid, '1', 'f' ], 'query' => "SELECT icalendar_text FROM calendar_object INNER JOIN calendar_bind ON calendar_bind.calendar_resource_id = calendar_object.calendar_resource_id INNER JOIN calendar_metadata ON calendar_metadata.resource_id = calendar_bind.calendar_resource_id INNER JOIN calendar_home ON calendar_home.resource_id = calendar_bind.calendar_home_resource_id WHERE calendar_home.owner_uid = ? AND calendar_bind.bind_status = ? AND calendar_metadata.is_in_trash = ?;", }, 'contacts' => { 'args' => [ $uuid, 'f' ], 'query' => "SELECT vcard_text FROM addressbook_object INNER JOIN addressbook_home ON addressbook_home.resource_id = addressbook_object.addressbook_home_resource_id WHERE addressbook_home.owner_uid = ? AND addressbook_object.is_in_trash = ?;", }, ); return ( $querydata{$type}{'query'}, $querydata{$type}{'args'} ); } sub _get_dbh ($self) { $self->{dbh} ||= Cpanel::CCS::DBUtils::get_dbh(); return $self->{dbh}; } sub _get_export_path_for_user ( $self, $user ) { $self->{$user}{export_path} ||= EXPORT_DIR() . '/' . $user . '/calendar_and_contacts'; return $self->{$user}{export_path}; } sub _get_ccs_info_for_user ( $self, $user ) { $self->{$user}{info} ||= Cpanel::CCS::Userdata::get_cpanel_account_users_uuids($user); return $self->{$user}{info}; } sub _ensure_export_directory ($self) { File::Path::make_path(EXPORT_DIR); chmod 0700, EXPORT_DIR; return; } sub post_distro_upgrade ($self) { return unless Elevate::StageFile::read_stage_file('ccs_installed'); $self->run_once('move_pgsql_directory'); $self->_install_ccs_and_dependencies(); $self->_clear_task_queue(); $self->_ensure_ccs_service_is_up(); $self->run_once('import_ccs_data'); $self->move_pgsql_directory_back(); return; } sub _install_ccs_and_dependencies ($self) { my @packages_to_install = ( CCS_PACKAGE() ); push @packages_to_install, ZPUSH_PACKAGE() if Elevate::StageFile::read_stage_file('zpush_installed'); Elevate::PkgMgr::install(@packages_to_install); return; } sub _clear_task_queue ($self) { $self->ssystem( TASK_QUEUE_SCRIPT, 'run' ); return; } sub _ensure_ccs_service_is_up ($self) { INFO('Attempting to ensure that the CCS service is running'); my $attempts = 1; my $max_attempts = 5; while ( $attempts <= $max_attempts ) { DEBUG("Attempt $attempts of $max_attempts to verify that the CCS service is up"); if ( $self->_ccs_service_is_up() ) { INFO('Verified that the CCS service is up'); return; } $self->remove_ccs_and_dependencies(); $self->remove_cpanel_ccs_home_directory(); $self->_clear_task_queue(); $self->_install_ccs_and_dependencies(); $self->_clear_task_queue(); sleep 5; $attempts++; } WARN("Failed to start CCS service. Importing CCS data may fail."); return; } sub _attempt_to_start_service ($self) { $self->ssystem(CCS_RESTART_SCRIPT); return; } sub _ccs_service_is_up ($self) { my $out = $self->ssystem_capture_output( CCS_RESTART_SCRIPT, '--status' ); return grep { $_ =~ m/is running as cpanel-ccs with PID/ } @{ $out->{stdout} }; } sub import_ccs_data ($self) { INFO("Importing CCS data"); my @failed_users; my @users = Cpanel::Config::Users::getcpusers(); foreach my $user (@users) { try { INFO(" Importing data for $user"); $self->_import_data_for_single_user($user); } catch { push @failed_users, $user; }; } INFO('Completed importing CCS data for all users'); if (@failed_users) { my $export_dir = EXPORT_DIR(); my $message = "The CCS data failed to import for the following users:\n\n"; $message .= join "\n", sort(@failed_users); $message .= <<~"EOS"; A backup of this data is located at $export_dir If this data is crucial, you may want to consider reaching out to cPanel Support for further assistance: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS Elevate::Notify::add_final_notification($message); } return; } sub _import_data_for_single_user ( $self, $user ) { require '/var/cpanel/perl5/lib/CCSHooks.pm'; ##no critic qw(RequireBarewordIncludes) my $extract_dir = EXPORT_DIR() . '/' . $user; my $import_data = { user => $user, extract_dir => $extract_dir, }; try { CCSHooks::pkgacct_restore( undef, $import_data ); } catch { my $err = $_; WARN("Failed to restore CCS data for '$user'"); DEBUG($err); die "CCS import failed for $user\n"; }; return; } sub move_pgsql_directory ($self) { my $pg_dir = '/var/lib/pgsql'; my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; return unless -d $pg_dir; File::Path::remove_tree($pg_backup_dir) if -e $pg_backup_dir && -d $pg_backup_dir; INFO( <<~"EOS" ); Temporarily moving the PostgreSQL data dir located at $pg_dir to $pg_backup_dir due to issues with the CCS upgrade process. This script will attempt to move the directory back to $pg_dir after CCS is upgraded. EOS my $success = rename( $pg_dir, $pg_backup_dir ); LOGDIE(qq[The system failed to move $pg_dir to $pg_backup_dir (reason: $!)!]) unless $success; return; } sub move_pgsql_directory_back ($self) { my $pg_dir = '/var/lib/pgsql'; my $pg_backup_dir = '/var/lib/pgsql_pre_elevate'; return unless -e $pg_backup_dir; INFO(qq[Restoring system PostgreSQL instance...]); Elevate::SystemctlService->new( name => 'postgresql' )->stop(); # just in case File::Path::remove_tree($pg_dir) if -e $pg_dir; my $result = rename( $pg_backup_dir, $pg_dir ); if ( !$result ) { my $msg = <<~"EOS"; The system could not fully restore $pg_backup_dir to $pg_dir (reason: $!). Restore this manually, and perform the update as recommended. EOS LOGDIE($msg); return; } INFO(qq[The system returned the PostgreSQL data directory to $pg_dir.]); return; } 1; } # --- END lib/Elevate/Components/CCS.pm { # --- BEGIN lib/Elevate/Components/CloudLinux.pm package Elevate::Components::CloudLinux; use cPstrict; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant CLDETECT => '/usr/bin/cldetect'; use constant RHN_CHECK => '/usr/sbin/rhn_check'; sub check ($self) { return $self->_check_cloudlinux_license(); } sub _check_cloudlinux_license ($self) { return 0 unless Elevate::OS::should_check_cloudlinux_license(); my $out = $self->ssystem_capture_output( CLDETECT, '--check-license' ); if ( $self->ssystem(RHN_CHECK) != 0 || $out->{status} != 0 || grep { $_ !~ m/^ok/i } @{ $out->{stdout} } ) { $self->components->abort_on_first_blocker(1); return $self->has_blocker( <<~'EOS'); The CloudLinux license is reporting that it is not currently valid. A valid CloudLinux license is required to ELevate from CloudLinux 7 to CloudLinux 8. EOS } return 0; } 1; } # --- END lib/Elevate/Components/CloudLinux.pm { # --- BEGIN lib/Elevate/Components/cPanelPlugins.pm package Elevate::Components::cPanelPlugins; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { return if Elevate::OS::is_apt_based(); my @installed_arch_cpanel_plugins; my $installed = Elevate::PkgMgr::pkg_list(); my @cpanel_repos = grep { m/^cpanel-/ } keys %$installed; foreach my $repo (@cpanel_repos) { push @installed_arch_cpanel_plugins, map { $_->{'package'} } $installed->{$repo}->@*; } return unless @installed_arch_cpanel_plugins; Elevate::StageFile::update_stage_file( { restore => { yum => \@installed_arch_cpanel_plugins } } ); return; } sub post_distro_upgrade ($self) { return if Elevate::OS::is_apt_based(); my $stash = Elevate::StageFile::read_stage_file(); my $yum_arch_plugins = $stash->{'restore'}->{'yum'} // []; return unless scalar @$yum_arch_plugins; INFO('Restoring cPanel yum-based-plugins'); Elevate::PkgMgr::reinstall(@$yum_arch_plugins); return; } 1; } # --- END lib/Elevate/Components/cPanelPlugins.pm { # --- BEGIN lib/Elevate/Components/cPanelPrep.pm package Elevate::Components::cPanelPrep; use cPstrict; use File::Slurper (); use File::Basename (); use Elevate::Constants (); use Elevate::StageFile (); use Elevate::SystemctlService (); use Cpanel::FileUtils::TouchFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { unlink('/usr/local/cpanel/scripts/upcp'); unlink('/usr/local/cpanel/bin/backup'); $self->_flush_task_queue(); $self->_disable_all_cpanel_services(); $self->_setup_outdated_services(); $self->_suspend_chkservd(); return; } sub _flush_task_queue ($self) { INFO('Running all queued cPanel tasks...'); $self->ssystem(qw{/usr/local/cpanel/bin/servers_queue run}); return; } sub _suspend_chkservd ($self) { INFO('Suspending cPanel service monitoring...'); Cpanel::FileUtils::TouchFile::touchfile(Elevate::Constants::CHKSRVD_SUSPEND_FILE); return; } sub _setup_outdated_services ($self) { INFO('Verifying elevate service is up to date...'); my $content = ''; my $outdated_services_file = Elevate::Constants::IGNORE_OUTDATED_SERVICES_FILE; if ( -e $outdated_services_file ) { $content = File::Slurper::read_binary($outdated_services_file) // ''; } my $service = Elevate::Service->new( cpev => $self->cpev ); my $service_name = $service->short_name; return if $content =~ qr{^${service_name}$}m; chomp($content); $content .= "\n" if length $content; $content .= $service_name . "\n"; my $dirname = File::Basename::dirname($outdated_services_file); if ( !-d $dirname ) { mkdir($dirname) or die qq[Failed to create directory $dirname - $!]; } File::Slurper::write_binary( $outdated_services_file, $content ); return 1; } sub _disable_all_cpanel_services ($self) { INFO('Disabling cPanel services...'); my @cpanel_services = qw/ cpanel cpdavd cpgreylistd cphulkd cpipv6 cpcleartaskqueue dnsadmin dovecot exim ipaliases mailman mysqld pdns proftpd queueprocd spamd crond tailwatchd lsws /; my @disabled_services; foreach my $name (@cpanel_services) { my $service = Elevate::SystemctlService->new( name => $name ); next unless $service->is_enabled; $service->disable; push @disabled_services, $name; } Elevate::StageFile::update_stage_file( { 'disabled_cpanel_services' => [ sort @disabled_services ] } ); return; } 1; } # --- END lib/Elevate/Components/cPanelPrep.pm { # --- BEGIN lib/Elevate/Components/DatabaseUpgrade.pm package Elevate::Components::DatabaseUpgrade; use cPstrict; use Elevate::Database (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); use File::Slurper; use Try::Tiny; use Cpanel::MysqlUtils::MyCnf::Basic (); use Cpanel::MysqlUtils::RemoteMySQL::ProfileManager (); use Cpanel::PasswdStrength::Generate (); use Cpanel::JSON (); use Cpanel::SafeRun::Simple (); use Cpanel::Encoder::URI (); use constant MYSQL_PROFILE_FILE => '/var/cpanel/elevate-mysql-profile'; # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub pre_distro_upgrade ($self) { return if Elevate::Database::is_database_provided_by_cloudlinux(); $self->_ensure_localhost_mysql_profile_is_active(1); return if Elevate::Database::is_database_version_supported( Elevate::Database::get_local_database_version() ); Elevate::Database::upgrade_database_server(); return; } sub post_distro_upgrade ($self) { return unless -e MYSQL_PROFILE_FILE; my $original_profile = File::Slurper::read_text(MYSQL_PROFILE_FILE) // 'localhost'; INFO(qq{Reactivating "$original_profile" MySQL profile}); my $output = $self->ssystem_capture_output( '/usr/local/cpanel/scripts/manage_mysql_profiles', '--activate', "$original_profile" ); my $stdout = join qq{\n}, @{ $output->{'stdout'} }; unless ( $stdout =~ m{MySQL profile activation done} ) { die <<~"EOS"; Unable to reactivate the original remote MySQL profile "$original_profile": $stdout Please resolve the reported problems then run this script again with: /scripts/elevate-cpanel --continue EOS } unlink MYSQL_PROFILE_FILE or WARN( "Could not delete " . MYSQL_PROFILE_FILE . ": $!" ); return; } sub _ensure_localhost_mysql_profile_is_active ( $self, $should_create_localhost_profile ) { return if Cpanel::MysqlUtils::MyCnf::Basic::is_local_mysql(); my $profile_manager = Cpanel::MysqlUtils::RemoteMySQL::ProfileManager->new(); my $profile = $profile_manager->get_active_profile('dont_die') || 'localhost'; if ($should_create_localhost_profile) { INFO( "Saving the currently active MySQL Profile ($profile) to " . MYSQL_PROFILE_FILE ); File::Slurper::write_text( MYSQL_PROFILE_FILE, $profile ); } try { $profile_manager->validate_profile('localhost'); $self->_activate_localhost_profile($profile_manager); } catch { die "Unable to generate/enable localhost MySQL profile: $_\n" unless $should_create_localhost_profile; INFO("Attempting to create new localhost MySQL profile..."); $self->_create_new_localhost_profile($profile_manager); $self->_ensure_localhost_mysql_profile_is_active(0); }; return; } sub _create_new_localhost_profile ( $self, $profile_manager ) { my $password = Cpanel::PasswdStrength::Generate::generate_password( 16, no_othersymbols => 1 ); try { $profile_manager->create_profile( { 'name' => 'localhost', 'mysql_user' => 'root', 'mysql_pass' => $password, 'mysql_host' => 'localhost', 'mysql_port' => Cpanel::MysqlUtils::MyCnf::Basic::getmydbport('root') || 3306, 'setup_via' => 'Auto-generated localhost profile during elevate.', }, { 'overwrite' => 1 }, ); } catch { die <<~"EOS"; Unable to generate a functioning MySQL DB profile for the local MySQL server. The following error was encountered: $@ EOS }; $self->_set_local_mysql_root_password($password); $profile_manager->save_changes_to_disk(); $self->_activate_localhost_profile($profile_manager); return; } sub _set_local_mysql_root_password ( $self, $password ) { INFO("Resetting password for local root database user..."); my $encoded_password = Cpanel::Encoder::URI::uri_encode_str($password); my $output = Cpanel::SafeRun::Simple::saferunnoerror( q{/bin/sh}, q{-c}, qq{/usr/local/cpanel/bin/whmapi1 --output=json set_local_mysql_root_password password='$encoded_password'} ); my $result = eval { Cpanel::JSON::Load($output); } // {}; unless ( $result->{metadata}{result} ) { my $errors = join qq{\n\n}, @{ $result->{'metadata'}{'errors'} }; die <<~"EOS"; Unable to set root password for the localhost database server. The following errors occurred: $errors Please resolve the reported problems then run this script again with: /scripts/elevate-cpanel --continue EOS } return; } sub _activate_localhost_profile { my ( $self, $profile_manager ) = @_; if ($profile_manager) { $profile_manager->{'_transaction_obj'}->close_or_die(); } INFO("Activating “localhost” MySQL profile"); my $output = $self->ssystem_capture_output(qw{/usr/local/cpanel/scripts/manage_mysql_profiles --activate localhost}); my $stdout = join qq{\n}, @{ $output->{'stdout'} }; if ( $stdout !~ m{MySQL profile activation done} ) { die <<~"EOS"; Unable to activate a MySQL profile for "localhost": $stdout Please resolve the reported problems then run this script again with: /scripts/elevate-cpanel --continue EOS } return; } 1; } # --- END lib/Elevate/Components/DatabaseUpgrade.pm { # --- BEGIN lib/Elevate/Components/DiskSpace.pm package Elevate::Components::DiskSpace; use cPstrict; use Cpanel::SafeRun::Simple (); use Elevate::OS (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant K => 1; use constant MEG => 1_024 * K; use constant GIG => 1_024 * MEG; sub check ($self) { # $self is a cpev object here my $ok = _disk_space_check($self); $self->has_blocker(q[disk space issue]) unless $ok; return $ok; } sub _disk_space_check ($self) { my $need_space = { '/boot' => 200 * MEG, '/usr/local/cpanel' => 1.5 * GIG, # '/var/lib' => 5 * GIG, '/tmp' => 5 * MEG, '/' => 5 * GIG, }; my @keys = ( sort keys %$need_space ); my @df_cmd = ( qw{/usr/bin/df -k}, @keys ); my $cmd = join( ' ', @df_cmd ); my $result = Cpanel::SafeRun::Simple::saferunnoerror(@df_cmd) // ''; die qq[Failed to check disk space using: $cmd\n] if $?; my ( $header, @out ) = split( "\n", $result ); if ( scalar @out != scalar @keys ) { my $count_keys = scalar @keys; my $count_out = scalar @out; die qq[Fail: Cannot parse df output from: $cmd\n] . "# expected $count_keys lines ; got $count_out lines\n" . join( "\n", @out ) . "\n"; } my @errors; my $ix = 0; foreach my $line (@out) { my $key = $keys[ $ix++ ]; my ( undef, undef, undef, $available ) = split( /\s+/, $line ); my $need = $need_space->{$key}; next if $available > $need; my $str; if ( $need / GIG > 1 ) { $str = sprintf( "- $key needs %2.2f G => available %2.2f G", $need / GIG, $available / GIG ); } else { $str = sprintf( "- $key needs %d M => available %d M", $need / MEG, $available / MEG ); } push @errors, $str; } return 1 unless @errors; my $details = join( "\n", @errors ); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $error = <<"EOS"; ** Warning **: your system does not have enough disk space available to update to $pretty_distro_name $details EOS warn $error . "\n"; return 0; # error } 1; } # --- END lib/Elevate/Components/DiskSpace.pm { # --- BEGIN lib/Elevate/Components/Distros.pm package Elevate::Components::Distros; use cPstrict; use Cpanel::OS (); use constant MINIMUM_CENTOS_7_SUPPORTED => 9; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { my @checks = qw{ _blocker_os_is_not_supported _blocker_is_old_centos7 _blocker_is_experimental_os }; foreach my $name (@checks) { my $blocker = $self->can($name)->($self); return $blocker if $blocker; } return 0; } sub _blocker_os_is_not_supported ($self) { Elevate::OS::is_supported(); # dies return 0; } sub _blocker_is_old_centos7 ($self) { return if Elevate::OS::skip_minor_version_check(); if ( Cpanel::OS::minor() < MINIMUM_CENTOS_7_SUPPORTED ) { ## no critic(Cpanel::CpanelOS) my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); return $self->has_blocker( sprintf( 'You need to run CentOS 7.%s and later to upgrade %s. You are currently using %s', # MINIMUM_CENTOS_7_SUPPORTED, $pretty_distro_name, Cpanel::OS::display_name() # ) ); } return 0; } sub _blocker_is_experimental_os ($self) { if ( -e '/var/cpanel/caches/Cpanel-OS.custom' ) { return $self->has_blocker('Experimental OS detected. This script only supports CentOS 7 upgrades'); } return 0; } sub bail_out_on_inappropriate_distro () { Elevate::OS::clear_cache(); Elevate::OS::is_supported(); # dies return; } 1; } # --- END lib/Elevate/Components/Distros.pm { # --- BEGIN lib/Elevate/Components/DNS.pm package Elevate::Components::DNS; use cPstrict; use Elevate::Constants (); use Elevate::OS (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Cpanel::Config::LoadCpConf (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { return $self->_blocker_nameserver_not_supported( _get_nameserver_type() ); } sub _get_nameserver_type () { my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf(); return $cpconf->{'local_nameserver_type'} // ''; } sub _blocker_nameserver_not_supported ( $self, $nameserver = '' ) { return 0 unless length $nameserver; my @supported_nameserver_types = Elevate::OS::supported_cpanel_nameserver_types(); return 0 if grep { $_ eq $nameserver } @supported_nameserver_types; my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $supported_nameservers = join( "\n", @supported_nameserver_types ); return $self->has_blocker( <<~"EOS"); $pretty_distro_name only supports the following nameservers: $supported_nameservers We suggest you switch to powerdns. Before upgrading, we suggest you run: /usr/local/cpanel/scripts/setupnameserver powerdns EOS } 1; } # --- END lib/Elevate/Components/DNS.pm { # --- BEGIN lib/Elevate/Components/EA4.pm package Elevate::Components::EA4; use cPstrict; use Elevate::Constants (); use Elevate::EA4 (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::JSON (); use Cpanel::Pkgr (); use Cpanel::SafeRun::Simple (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_imunify ($self) { $self->run_once('_gather_php_usage'); $self->run_once('_backup_ea4_profile'); $self->run_once('_backup_config_files'); return; } sub pre_distro_upgrade ($self) { $self->run_once('_cleanup_rpm_db'); return; } sub post_distro_upgrade ($self) { $self->run_once('_restore_ea4_profile'); $self->run_once('_restore_ea_addons'); $self->run_once('_restore_config_files'); $self->run_once('_ensure_sites_use_correct_php_version'); return; } sub _backup_ea4_profile ($self) { Elevate::EA4::backup(); return; } sub _cleanup_rpm_db ($self) { Elevate::PkgMgr::remove('ea-*'); return; } sub _restore_ea_addons ($self) { return unless Elevate::StageFile::read_stage_file('ea4')->{'nginx'}; INFO("Restoring ea-nginx"); Elevate::PkgMgr::remove_no_dependencies('ea-nginx'); Elevate::PkgMgr::install('ea-nginx'); return; } sub _restore_ea4_profile ($self) { my $stash = Elevate::StageFile::read_stage_file(); my $is_enabled = $stash->{'ea4'} && $stash->{'ea4'}->{'enable'}; unless ($is_enabled) { WARN('Skipping EA4 restore. EA4 does not appear to be enabled on this system.'); return; } my $json = $stash->{'ea4'}->{'profile'}; unless ( length $json && -f $json && -s _ ) { WARN('Unable to restore EA4 profile. Is EA4 enabled?'); INFO("Profile was backed up as: $json") if length $json; return; } $self->ssystem( '/usr/local/bin/ea_install_profile', '--install', $json ); if ( my $dropped_pkgs = $stash->{'ea4'}->{'dropped_pkgs'} ) { if ( scalar keys $dropped_pkgs->%* ) { my $msg = qq[One or more EasyApache 4 package(s) cannot be restored from your previous profile:\n]; foreach my $pkg ( sort keys $dropped_pkgs->%* ) { my $type = $dropped_pkgs->{$pkg} // ''; $msg .= sprintf( "- '%s'%s\n", $pkg, $type eq 'exp' ? ' ( package was Experimental in CentOS 7 )' : '' ); } chomp $msg; Elevate::Notify::add_final_notification( $msg, 1 ); } } return 1; } sub _backup_config_files ($self) { Elevate::StageFile::remove_from_stage_file('ea4_config_files'); my $ea4_config_files = Elevate::PkgMgr::get_config_files_for_pkg_prefix('ea-*'); Elevate::StageFile::update_stage_file( { ea4_config_files => $ea4_config_files } ); return; } our %config_files_to_ignore = ( 'ea-nginx' => { '/etc/nginx/conf.d/ea-nginx.conf' => 1, '/etc/nginx/ea-nginx/settings.json' => 1, }, 'ea-apache24' => { '/etc/apache2/conf/httpd.conf' => 1, }, ); sub _restore_config_files ($self) { my $config_files = Elevate::StageFile::read_stage_file('ea4_config_files'); foreach my $key ( sort keys %$config_files ) { INFO("Restoring config files for package: '$key'"); my @config_files_to_restore = @{ $config_files->{$key} }; if ( exists $config_files_to_ignore{$key} ) { @config_files_to_restore = grep { !$config_files_to_ignore{$key}{$_} } @config_files_to_restore; } Elevate::PkgMgr::restore_config_files(@config_files_to_restore); } return; } sub _ensure_sites_use_correct_php_version ($self) { my $vhost_versions = Elevate::StageFile::read_stage_file('php_get_vhost_versions'); return unless ref $vhost_versions eq 'ARRAY'; return unless scalar $vhost_versions->@*; foreach my $vhost_entry (@$vhost_versions) { my $version = $vhost_entry->{version}; my $vhost = $vhost_entry->{vhost}; my $fpm = $vhost_entry->{php_fpm}; my @api_cmd = ( '/usr/local/cpanel/bin/whmapi1', '--output=json', 'php_set_vhost_versions', "version=$version", "vhost=$vhost", "php_fpm=$fpm", ); my $out = Cpanel::SafeRun::Simple::saferunnoerror(@api_cmd); my $result = eval { Cpanel::JSON::Load($out); } // {}; my $api_string = join( ' ', @api_cmd ); unless ( $result->{metadata}{result} ) { WARN( <<~"EOS" ); Unable to set $vhost back to its desired PHP version. This site may be using the incorrect version of PHP. To set it back to its original PHP version, execute the following command: $api_string EOS } } return; } sub _gather_php_usage ($self) { my $php_get_vhost_versions = Elevate::EA4::php_get_vhost_versions(); Elevate::StageFile::remove_from_stage_file('php_get_vhost_versions'); Elevate::StageFile::update_stage_file( { php_get_vhost_versions => $php_get_vhost_versions } ); return; } sub check ($self) { return $self->_blocker_ea4_profile; } sub _blocker_ea4_profile ($self) { my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); INFO("Checking EasyApache profile compatibility with $pretty_distro_name."); my $check_mode = $self->is_check_mode(); Elevate::EA4::backup($check_mode); my @incompatible_packages = $self->_get_incompatible_packages(); return unless @incompatible_packages; my $list = join( "\n", map { "- $_" } @incompatible_packages ); return $self->has_blocker( <<~"EOS" ); One or more EasyApache 4 package(s) are not compatible with $pretty_distro_name. Please remove these packages before continuing the update. $list EOS } sub _get_incompatible_packages ($self) { my $stash = Elevate::StageFile::read_stage_file(); my $dropped_pkgs = $stash->{'ea4'}->{'dropped_pkgs'} // {}; return unless scalar keys $dropped_pkgs->%*; my @incompatible; foreach my $pkg ( sort keys %$dropped_pkgs ) { my $type = $dropped_pkgs->{$pkg} // ''; next if $type eq 'exp'; # use of experimental packages is a non blocker next if $pkg =~ m/^ea-openssl(?:11)?-devel$/; # ignore these packages, as they can be orphans next if $pkg =~ m/^ea-noop-u20$/; # ignore this package since it is specifically for ubuntu 20 if ( $pkg =~ m/^(ea-php[0-9]+)/ ) { my $php_pkg = $1; next unless $self->_php_version_is_in_use($php_pkg); } push @incompatible, $pkg; } return @incompatible; } sub _php_version_is_in_use ( $self, $php ) { my $current_php_usage = $self->_get_php_usage(); return 1 if $current_php_usage->{api_fail}; return $current_php_usage->{$php} ? 1 : 0; } our $php_usage; sub _get_php_usage ($self) { return $php_usage if defined $php_usage && ref $php_usage eq 'HASH'; my $php_get_vhost_versions = Elevate::EA4::php_get_vhost_versions(); if ( !defined $php_get_vhost_versions ) { $php_usage->{api_fail} = 1; return $php_usage; } foreach my $domain_info (@$php_get_vhost_versions) { my $php_version = $domain_info->{version}; $php_usage->{$php_version} = 1; } return $php_usage; } 1; } # --- END lib/Elevate/Components/EA4.pm { # --- BEGIN lib/Elevate/Components/ElevateScript.pm package Elevate::Components::ElevateScript; use cPstrict; use Elevate::Constants (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { $self->_blocker_wrong_location; $self->_is_up_to_date; return; } sub _blocker_wrong_location ($self) { my $running_from = Cwd::abs_path($0) // ''; return 0 if $running_from eq '/scripts/elevate-cpanel' || $running_from eq '/usr/local/cpanel/scripts/elevate-cpanel'; return $self->has_blocker( <<~'EOS'); The script is not installed to the correct directory. Please install it to /scripts/elevate-cpanel and run it again. EOS } sub _is_up_to_date ($self) { # $self is a cpev object here return if $self->getopt('skip-elevate-version-check'); my ( $should_block, $message ) = $self->cpev->script->is_out_of_date(); $message //= ''; if ( !$should_block ) { WARN($message) if length $message; return; } return $self->has_blocker( <<~"EOS"); $message Pass the --skip-elevate-version-check flag to skip this check. EOS } 1; } # --- END lib/Elevate/Components/ElevateScript.pm { # --- BEGIN lib/Elevate/Components/ELS.pm package Elevate::Components::ELS; use cPstrict; use Elevate::OS (); use Elevate::PkgMgr (); use Cpanel::Pkgr (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant ELS_PACKAGE => 'els-define'; sub pre_distro_upgrade ($self) { return unless Elevate::OS::remove_els(); my @files_to_remove = qw{ /etc/yum.repos.d/centos7-els.repo /etc/yum.repos.d/centos7-els-rollout.repo }; foreach my $file (@files_to_remove) { if ( -e $file ) { unlink $file or WARN("Could not remove file $file: $!"); } } Elevate::PkgMgr::remove(ELS_PACKAGE) if Cpanel::Pkgr::is_installed(ELS_PACKAGE); return; } 1; } # --- END lib/Elevate/Components/ELS.pm { # --- BEGIN lib/Elevate/Components/Grub2.pm package Elevate::Components::Grub2; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Cpanel::JSON (); use Cpanel::Pkgr (); use Cpanel::SafeRun::Simple (); use Cpanel::SafeRun::Object (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } our $GRUB2_PREFIX_DEBIAN = '/boot/grub'; our $GRUB2_PREFIX_RHEL = '/boot/grub2'; use constant GRUB2_WORKAROUND_NONE => 0; use constant GRUB2_WORKAROUND_OLD => 1; use constant GRUB2_WORKAROUND_NEW => 2; use constant GRUB2_WORKAROUND_UNCERTAIN => -1; use constant GRUB_EDITENV => '/usr/bin/grub2-editenv'; use constant GRUB_ENV_FILE => '/boot/grub2/grubenv'; use constant GRUBBY_PATH => '/usr/sbin/grubby'; use constant CMDLINE_PATH => '/proc/cmdline'; use constant EX_UNAVAILABLE => 69; sub _call_grubby ( $self, @args ) { my %opts = ( should_capture_output => 0, should_hide_output => 0, die_on_error => 0, ); if ( ref $args[0] eq 'HASH' ) { my %opt_args = %{ shift @args }; foreach my $key ( keys %opt_args ) { $opts{$key} = $opt_args{$key}; } } unshift @args, GRUBBY_PATH; return $opts{die_on_error} ? $self->ssystem_and_die(@args) : $self->ssystem( \%opts, @args ); } sub _default_kernel ($self) { return $self->_call_grubby( { should_capture_output => 1, should_hide_output => 1 }, '--default-kernel' )->{'stdout'}->[0] // ''; } sub _persistent_id { my $id = Elevate::StageFile::read_stage_file( 'bootloader_random_tag', '' ); return $id if $id; $id = int( rand(100000) ); Elevate::StageFile::update_stage_file( { 'bootloader_random_tag', $id } ); return $id; } sub mark_cmdline ($self) { return unless -x GRUBBY_PATH; my $arg = "elevate-" . _persistent_id; INFO("Marking default boot entry with additional parameter \"$arg\"."); my $kernel_path = $self->_default_kernel; $self->_call_grubby( { die_on_error => 1 }, "--update-kernel=$kernel_path", "--args=$arg" ); return; } sub _remove_but_dont_stop_service ($self) { $self->cpev->service->disable(); $self->ssystem( '/usr/bin/systemctl', 'daemon-reload' ); return; } sub verify_cmdline ($self) { return unless -x GRUBBY_PATH; if ( $self->cpev->upgrade_distro_manually() ) { my $arg = "elevate-" . _persistent_id; INFO("Checking for \"$arg\" in booted kernel's command line..."); my $kernel_cmdline = eval { File::Slurper::read_binary(CMDLINE_PATH) } // ''; DEBUG( CMDLINE_PATH . " contains: $kernel_cmdline" ); my $detected = scalar grep { $_ eq $arg } split( ' ', $kernel_cmdline ); if ($detected) { INFO("Parameter detected; restoring entry to original state."); } else { ERROR("Parameter not detected. Attempt to upgrade is being aborted."); } my $kernel_path = $self->_default_kernel; my $result = $self->_call_grubby( "--update-kernel=$kernel_path", "--remove-args=$arg" ); WARN("Unable to restore original command line. This should not cause problems but is unusual.") if $result != 0; if ( !$detected ) { my $stage = Elevate::Stages::get_stage(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $msg = <<"EOS"; The elevation process failed during stage $stage. Specifically, the script could not prove that the system has control over its own boot process using the utilities the operating system provides. For this reason, the elevation process has terminated before making any irreversible changes. You can check the error log by running: $0 Before you can run the elevation process, you must provide for the ability for the system to manipulate its own boot process. Then you can start the elevation process anew: $0 --start EOS Elevate::Notify::send_notification( qq[Failed to update to $pretty_distro_name] => $msg ); $self->cpev->do_cleanup(1); $self->_remove_but_dont_stop_service(); exit EX_UNAVAILABLE; ## no critic(Cpanel::NoExitsFromSubroutines) } } return; } sub pre_distro_upgrade ($self) { $self->run_once('_update_grub2_workaround_if_needed'); # required part $self->run_once('_merge_grub_directories_if_needed'); # best-effort part return; } sub _update_grub2_workaround_if_needed ($self) { my $grub2_info = Elevate::StageFile::read_stage_file('grub2_workaround'); return unless $grub2_info->{'needs_workaround_update'}; my $grub_dir = $GRUB2_PREFIX_DEBIAN; my $grub2_dir = $GRUB2_PREFIX_RHEL; my $grub_bak; if ( $grub2_info->{'backup_dir'} ) { $grub_bak = $grub2_info->{'backup_dir'}; } else { $grub_bak = $grub2_info->{'backup_dir'} = $grub_dir . '-' . time; Elevate::StageFile::update_stage_file( { 'grub2_workaround' => $grub2_info } ); } rename $grub_dir, $grub_bak or LOGDIE("Unable to rename $grub_dir to $grub_bak: $!"); # failure on cross-device move is a feature symlink $grub2_dir, $grub_dir or do { rename $grub_bak, $grub_dir; # undo previous change on failure LOGDIE("Unable to create symlink $grub_dir to point to $grub2_dir"); # symlink() doesn't set $! }; return; } sub _merge_grub_directories_if_needed ($self) { my $grub2_info = Elevate::StageFile::read_stage_file('grub2_workaround'); return unless $grub2_info->{'needs_workaround_update'}; my $grub_dir = $GRUB2_PREFIX_DEBIAN; my $grub2_dir = $GRUB2_PREFIX_RHEL; my $grub_bak = $grub2_info->{'backup_dir'}; my ( $skipped_copy, $failed_copy ) = ( 0, 0 ); eval { my %grub2_entries; opendir my $grub2_dir_fh, $grub2_dir or die "Unable to open directory $grub2_dir: $!"; while ( my $entry = readdir $grub2_dir_fh ) { $grub2_entries{$entry} = 1; } closedir $grub2_dir_fh; opendir my $grub_bak_fh, $grub_bak or die "Unable to open directory $grub_bak: $!"; while ( my $entry = readdir $grub_bak_fh ) { next if $entry eq '.'; next if $entry eq '..'; next if $entry eq "grub.cfg"; if ( exists $grub2_entries{$entry} ) { $skipped_copy++; WARN("\"$grub_bak/$entry\" is not being copied to \"$grub2_dir/$entry\" because the destination already exists."); next; } if ( !File::Copy::Recursive::rcopy( "$grub_bak/$entry", "$grub2_dir/$entry" ) ) { $failed_copy++; WARN("Copying \"$grub_bak/$entry\" into \"$grub2_dir\" failed."); } } closedir $grub_bak_fh; }; WARN("Unable to copy the contents of \"$grub_bak\" into \"$grub2_dir\": $@") if $@; my $log_file = Elevate::Constants::LOG_FILE; Elevate::Notify::add_final_notification( <<~EOS ) if ( $skipped_copy > 0 || $failed_copy > 0 ); After converting "$grub_dir" from a directory to a symlink to "$grub2_dir", the upgrade process chose not to copy $skipped_copy entries from the old directory due to name conflicts, and it failed to copy $failed_copy entries due to system errors. The previous contents of "$grub_dir" are now located at "$grub_bak". See $log_file for further information. If you did not add these files or otherwise customize the boot loader configuration, you may ignore this message. EOS return; } sub post_distro_upgrade ($self) { my $proc_cmd_line = eval { File::Slurper::read_binary('/proc/cmdline') } // ''; return unless $proc_cmd_line =~ m{net.ifnames=0}; my $grub_conf = eval { File::Slurper::read_binary(Elevate::Constants::DEFAULT_GRUB_FILE) } // ''; return unless length $grub_conf; return if $grub_conf =~ m/net.ifnames/; return unless $grub_conf =~ s/GRUB_CMDLINE_LINUX="(.+?)"/GRUB_CMDLINE_LINUX="$1 net.ifnames=0"/m; File::Slurper::write_binary( Elevate::Constants::DEFAULT_GRUB_FILE, $grub_conf ); my $grubenv = Cpanel::SafeRun::Simple::saferunnoerror( GRUB_EDITENV, GRUB_ENV_FILE, 'list' ); foreach my $line ( split "\n", $grubenv ) { my ( $name, $value ) = split "=", $line, 2; next unless $name eq "kernelopts"; if ( $value !~ m/\bnet\.ifnames=/a ) { $line .= ( $line && $line !~ m/ $/ ? " " : "" ) . 'net.ifnames=0'; Cpanel::SafeRun::Object->new_or_die( program => GRUB_EDITENV, args => [ GRUB_ENV_FILE, "set", $line, ], ); last; } } return 1; } sub check ($self) { return 1 unless Elevate::OS::needs_leapp(); return 1 unless $self->upgrade_distro_manually; # skip when --upgrade-distro-manually is provided my $ok = 1; $ok = 0 unless $self->_blocker_grub2_workaround; $ok = 0 unless $self->_blocker_blscfg; $ok = 0 unless $self->_blocker_grub_not_installed; $ok = 0 unless $self->_blocker_grub_config_missing; return $ok; } sub _blocker_grub2_workaround ($self) { my $state = _grub2_workaround_state(); if ( $state == GRUB2_WORKAROUND_OLD ) { my ( $deb, $rhel ) = ( $GRUB2_PREFIX_DEBIAN, $GRUB2_PREFIX_RHEL ); WARN( <<~EOS ); $deb/grub.cfg is currently a symlink to $rhel/grub.cfg. Your provider may have added this to support booting your server using their own instance of the GRUB2 bootloader, one which looks for its configuration, boot entries, and modules in a different location from where your operating system stores this data. In order to allow the process to complete successfully, the upgrade process will rename the current $deb directory, re-create $deb as a symlink to $rhel, and then copy as much of the old $deb into $rhel as possible. EOS Elevate::StageFile::update_stage_file( { 'grub2_workaround' => { 'needs_workaround_update' => 1 } } ) if !$self->is_check_mode(); # don't update stage file if this is just a check } elsif ( $state == GRUB2_WORKAROUND_UNCERTAIN ) { return $self->has_blocker( <<~EOS ); The configuration of the GRUB2 bootloader does not match the expectations of this script. For more information, see the output of the script when run at the console: /scripts/elevate-cpanel --check If your GRUB2 configuration has not been customized, you may want to consider reaching out to cPanel Support for assistance: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS } return 0; } sub _blocker_blscfg ($self) { my $grub_enable_blscfg = _parse_shell_variable( Elevate::Constants::DEFAULT_GRUB_FILE, 'GRUB_ENABLE_BLSCFG' ); return $self->has_blocker( <<~EOS ) if defined $grub_enable_blscfg && $grub_enable_blscfg ne 'true'; Disabling the BLS boot entry format prevents the resulting system from adding kernel updates to any boot loader configuration, because the old utility responsible for maintaining native GRUB2 boot loader entries was removed and replaced with a wrapper around the new utility, which only understands BLS format. This means that the old kernel will be used on reboot, unless the GRUB2 configuration file is manually edited to load the new kernel. Furthermore, after a few kernel updates, the DNF package manager may begin to remove old kernels, including the one still used in the configuration file. If that happens, the system will fail to come back after a subsequent reboot. The safe option is to remove the following line in /etc/default/grub: GRUB_ENABLE_BLSCFG=false or to change it so that it is set to "true" instead. EOS return 0; } sub _blocker_grub_not_installed ($self) { return 0 if Cpanel::Pkgr::is_installed('grub2-pc'); return $self->has_blocker( <<~EOS ); The grub2-pc package is not installed. The GRUB2 boot loader is required to upgrade via leapp. If you need assistance, open a ticket with cPanel Support, as outlined here https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS } sub _blocker_grub_config_missing ($self) { if ( ( !-f '/boot/grub2/grub.cfg' || !-s '/boot/grub2/grub.cfg' ) && ( !-f '/boot/grub/grub.cfg' || !-s '/boot/grub/grub.cfg' ) ) { return $self->has_blocker( <<~EOS ); The GRUB2 config file is missing. If you need assistance, open a ticket with cPanel Support, as outlined here https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS } return 0; } sub _parse_shell_variable ( $path, $varname ) { my ( undef, $dir, $file ) = File::Spec->splitpath($path); $dir = File::Spec->canonpath($dir); my $bash_sr = Cpanel::SafeRun::Object->new( program => Cpanel::Binaries::path('bash'), args => [ '--restricted', '-c', qq(set -ux ; [ "x\$PWD" = "x$dir" ] || exit 72 ; source $file ; echo "\$$varname"), ], before_exec => sub { chdir $dir; Cpanel::AccessIds::SetUids::setuids('nobody'); }, ); return undef if $bash_sr->CHILD_ERROR && $bash_sr->error_code == 127; $bash_sr->die_if_error(); # bail out if something else went wrong my $value = $bash_sr->stdout; chomp $value; return $value; } sub _grub2_workaround_state () { return GRUB2_WORKAROUND_NONE if !-e $GRUB2_PREFIX_DEBIAN; if ( -l $GRUB2_PREFIX_DEBIAN ) { my $dest = Cwd::realpath($GRUB2_PREFIX_DEBIAN); if ( !defined($dest) ) { ERROR( $GRUB2_PREFIX_DEBIAN . " is a symlink but realpath() failed: $!" ) unless defined($dest); return GRUB2_WORKAROUND_UNCERTAIN; } if ( $dest eq $GRUB2_PREFIX_RHEL ) { return GRUB2_WORKAROUND_NEW if -d $dest; ERROR( $GRUB2_PREFIX_DEBIAN . " does not ultimately link to /boot/grub2." ); return GRUB2_WORKAROUND_UNCERTAIN; # ...unless /boot/grub2 isn't a directory. } } elsif ( !-d $GRUB2_PREFIX_DEBIAN ) { ERROR( $GRUB2_PREFIX_DEBIAN . " is neither symlink nor directory." ); return GRUB2_WORKAROUND_UNCERTAIN; } my ( $grub_cfg, $grub2_cfg ) = map { $_ . "/grub.cfg" } ( $GRUB2_PREFIX_DEBIAN, $GRUB2_PREFIX_RHEL ); return GRUB2_WORKAROUND_NONE if !-e $grub_cfg; if ( -l $grub_cfg ) { my $dest_cfg = Cwd::realpath($grub_cfg); if ( !defined($dest_cfg) ) { ERROR("$grub_cfg is a symlink but realpath() failed: $!"); return GRUB2_WORKAROUND_UNCERTAIN; } if ( $dest_cfg eq $grub2_cfg ) { return GRUB2_WORKAROUND_OLD if -f $dest_cfg; ERROR("$dest_cfg is not a regular file."); return GRUB2_WORKAROUND_UNCERTAIN; # ...unless /boot/grub2/grub.cfg isn't a regular file. } } ERROR("$grub_cfg exists but is not a symlink which ultimately links to $grub2_cfg."); return GRUB2_WORKAROUND_UNCERTAIN; } 1; } # --- END lib/Elevate/Components/Grub2.pm { # --- BEGIN lib/Elevate/Components/Imunify.pm package Elevate::Components::Imunify; use cPstrict; use Elevate::Constants (); use Elevate::Fetch (); use Elevate::Notify (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Cpanel::JSON (); use Cpanel::Pkgr (); use Cpanel::SafeRun::Simple (); use File::Copy (); use Cwd (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant IMUNIFY_AGENT => Elevate::Constants::IMUNIFY_AGENT; # ya alias use constant IMUNIFY_LICENSE_FILE => '/var/imunify360/license.json'; use constant IMUNIFY_LICENSE_BACKUP => Elevate::Constants::ELEVATE_BACKUP_DIR . '/imunify-backup-license.json'; sub pre_distro_upgrade ($self) { if ( Elevate::OS::needs_leapp() ) { return if Elevate::OS::leapp_can_handle_imunify(); } return unless $self->is_installed; $self->run_once("_capture_imunify_features"); $self->run_once("_capture_imunify_packages"); $self->run_once('_remove_imunify_360'); return; } sub post_distro_upgrade ($self) { if ( Elevate::OS::needs_leapp() ) { return if Elevate::OS::leapp_can_handle_imunify(); } $self->run_once('_reinstall_imunify_360'); $self->run_once('_restore_imunify_features'); $self->run_once("_restore_imunify_packages"); return; } sub _capture_imunify_packages ($self) { my $pkg_info = Elevate::PkgMgr::get_installed_pkgs('imunify-*'); my @packages = keys %$pkg_info; return unless scalar @packages; Elevate::StageFile::update_stage_file( { 'reinstall' => { 'imunify_packages' => \@packages } } ); return; } sub _restore_imunify_packages ($self) { return unless my $packages = Elevate::StageFile::read_stage_file('reinstall')->{'imunify_packages'}; foreach my $pkg (@$packages) { next unless Cpanel::Pkgr::is_installed($pkg); INFO("Try to reinstall Imunify package: $pkg"); Elevate::PkgMgr::install($pkg); } return; } sub _capture_imunify_features { return unless -x IMUNIFY_AGENT; my $output = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features list} ); my @features = map { my $trim_spaces = $_; $trim_spaces =~ s/\s+//g; $trim_spaces; } grep { m/\S/ } split( "\n", $output ); if ( -f IMUNIFY_LICENSE_FILE ) { File::Copy::mv( IMUNIFY_LICENSE_FILE, IMUNIFY_LICENSE_BACKUP ); } Elevate::StageFile::update_stage_file( { 'reinstall' => { 'imunify_features' => \@features } } ); return; } sub _restore_imunify_features { return unless my $features = Elevate::StageFile::read_stage_file('reinstall')->{'imunify_features'}; File::Copy::mv( IMUNIFY_LICENSE_BACKUP, IMUNIFY_LICENSE_FILE ) if -f IMUNIFY_LICENSE_BACKUP; return unless ref $features eq 'ARRAY'; return unless @$features; INFO("Restoring imunify 360 features."); foreach my $feature (@$features) { INFO("Restoring imunify360 $feature"); my $log_file = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features install }, $feature ); $log_file or next; chomp $log_file; next unless $log_file =~ m/\S/; __monitor_imunify_feature_install( $feature, $log_file ); } return; } sub __imunify_feature_install_status ($feature) { my $install_status = eval { my $json = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features status}, $feature, '--json' ) // '{}'; Cpanel::JSON::Load($json); } // {}; my $status = $install_status->{'items'}->{'status'} // ''; return $status if $status =~ m/^(installed|installing|not_installed)$/i; return $install_status->{'items'}->{'message'} || "$feature is unknown"; } sub __monitor_imunify_feature_install ( $feature, $log_file ) { my $start = time; while ( time - $start < 30 ) { my $status = __imunify_feature_install_status($feature); last if ( $status eq 'installed' || $status eq 'installing' && -e $log_file ); } open( my $fh, '<', $log_file ) or do { my $status = __imunify_feature_install_status($feature); WARN("Could not open $log_file for monitoring ($!). The install of $feature is in state: $status"); return; }; DEBUG("Monitoring $log_file for completion"); $start = time; my $partial_line = ''; while ( time - $start < 60 * 20 ) { # abort after 20 minutes. while ( my $read = <$fh> ) { $partial_line .= $read; if ( length $read && substr( $partial_line, -1, 1 ) eq "\n" ) { INFO($partial_line); $partial_line = ''; } } my $status = __imunify_feature_install_status($feature); if ( $status eq 'installed' ) { INFO("Restore of $feature complete."); return 1; } if ( $status ne 'installing' ) { FATAL("Failed to install imunify 360 feature $feature ($status)"); FATAL("See $log_file for more information"); return 0; } sleep 5; } Elevate::Notify::add_final_notification( "Imunify failed to install feature $feature", 1 ); return 0; } sub is_installed ($self) { return unless -x Elevate::Constants::IMUNIFY_AGENT; return 1; } sub has_360_installed ($self) { return Cpanel::Pkgr::is_installed('imunify360-firewall') || Cpanel::Pkgr::is_installed('imunify-antivirus'); } sub _remove_imunify_360 ($self) { return unless $self->has_360_installed; my $agent_bin = Elevate::Constants::IMUNIFY_AGENT; my $out = $self->ssystem_capture_output( $agent_bin, 'version', '--json' ); my $raw_data = join "\n", @{ $out->{stdout} }; my $license_data = eval { Cpanel::JSON::Load($raw_data) } // {}; if ( !ref $license_data->{'license'} || !$license_data->{'license'}->{'status'} ) { WARN("Imunify360: Cannot detect license. Skipping upgrade."); return; } my $product_type = $license_data->{'license'}->{'license_type'} or do { WARN("Imunify360: No license type detected. Skipping upgrade."); return; }; my $installer_script = _fetch_imunify_installer($product_type) or do { WARN("Imunify360: Failed to fetch script for $product_type. Skipping upgrade."); return; }; my $imunify_pid = Cpanel::LoadFile::load_if_exists('/var/lock/i360deploy.lck') || Cpanel::LoadFile::load_if_exists('/var/lock/imav-deploy.lck'); if ($imunify_pid) { INFO("Waiting for Imunify update to complete before uninstalling it"); while ( kill 0, $imunify_pid ) { sleep 1; } INFO("Imunify update has completed"); } INFO("Imunify360: Removing $product_type prior to upgrade."); INFO("Imunify360: Product $product_type detected. Uninstalling before upgrade for later restore."); if ( $self->ssystem( '/usr/bin/bash', $installer_script, '-y', '--uninstall' ) != 0 ) { LOGDIE("Imunify360: Failed to uninstall $product_type."); return; } unlink $installer_script; Elevate::StageFile::update_stage_file( { 'reinstall' => { 'imunify360' => $product_type } } ); my $pkg_info = Elevate::PkgMgr::get_installed_pkgs('imunify-*'); my @packages = keys %$pkg_info; Elevate::PkgMgr::remove(@packages); return; } sub _reinstall_imunify_360 ($self) { my $product_type = Elevate::StageFile::read_stage_file('reinstall')->{'imunify360'} or return; INFO("Reinstalling $product_type"); my $installer_script = _fetch_imunify_installer($product_type) or return; if ( $self->ssystem( '/usr/bin/bash', $installer_script, '-y' ) == 0 ) { INFO("Successfully reinstalled $product_type."); } else { my $installer_url = _script_url_for_product($product_type); my $msg = "Failed to reinstall $product_type. Please reinstall it manually using $installer_url."; ERROR($msg); Elevate::Notify::add_final_notification($msg); } unlink $installer_script; return; } sub _script_url_for_product ($product) { $product =~ s/Plus/+/i; my %installer_scripts = ( 'imunifyAV' => 'https://repo.imunify360.cloudlinux.com/defence360/imav-deploy.sh', 'imunifyAV+' => 'https://repo.imunify360.cloudlinux.com/defence360/imav-deploy.sh', 'imunify360' => 'https://www.repo.imunify360.cloudlinux.com/defence360/i360deploy.sh', ); my $installer_url = $installer_scripts{$product} or do { ERROR( "_fetch_imunify_installer: Unknown product type '$product'. Known products are: " . join( ', ', sort keys %installer_scripts ) ); return; }; return $installer_url; } sub _fetch_imunify_installer ($product) { my $installer_url = _script_url_for_product($product) or return; return Elevate::Fetch::script( $installer_url, 'imunify_installer' ); } sub check ($self) { return $self->_check_imunify_license(); } sub _check_imunify_license ($self) { return unless -x Elevate::Constants::IMUNIFY_AGENT; my $agent_bin = Elevate::Constants::IMUNIFY_AGENT; my $out = $self->ssystem_hide_and_capture_output( $agent_bin, 'version', '--json' ); my $raw_data = join "\n", @{ $out->{stdout} }; my $license_data = eval { Cpanel::JSON::Load($raw_data) } // {}; if ( !ref $license_data->{license} || !$license_data->{license}->{status} ) { my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); return $self->has_blocker( <<~"EOS"); The Imunify license is reporting that it is not currently valid. Since Imunify is installed on this system, a valid Imunify license is required to ELevate to $pretty_distro_name. EOS } return; } 1; } # --- END lib/Elevate/Components/Imunify.pm { # --- BEGIN lib/Elevate/Components/InfluxDB.pm package Elevate::Components::InfluxDB; use cPstrict; use Elevate::Constants (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::Pkgr (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { Elevate::StageFile::remove_from_stage_file('reinstall.influxdb'); return unless Cpanel::Pkgr::is_installed('telegraf'); INFO("Not removing influxdb. Will re-install it after elevate."); Elevate::StageFile::update_stage_file( { 'reinstall' => { 'influxdb' => 1 } } ); return; } sub post_distro_upgrade ($self) { return unless Elevate::StageFile::read_stage_file('reinstall')->{'influxdb'}; INFO("Re-installing telegraf for influxdb"); Elevate::PkgMgr::reinstall('telegraf'); return; } 1; } # --- END lib/Elevate/Components/InfluxDB.pm { # --- BEGIN lib/Elevate/Components/IsContainer.pm package Elevate::Components::IsContainer; use cPstrict; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { # $self is a cpev object here return 0 unless $self->upgrade_distro_manually; if ( _is_container_envtype() ) { return $self->has_blocker( <<~'EOS'); cPanel thinks that this is a container-like environment. This cannot be upgraded by the native leapp tool. Consider contacting your hypervisor provider for alternative solutions. EOS } return 0; } sub _is_container_envtype () { require Cpanel::OSSys::Env; my $envtype = Cpanel::OSSys::Env::get_envtype(); return scalar grep { $envtype eq $_ } qw( virtuozzo vzcontainer lxc virtualiron vserver ); } 1; } # --- END lib/Elevate/Components/IsContainer.pm { # --- BEGIN lib/Elevate/Components/JetBackup.pm package Elevate::Components::JetBackup; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::Pkgr (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { Elevate::StageFile::remove_from_stage_file('reinstall.jetbackup'); return unless Cpanel::Pkgr::is_installed('jetbackup5-cpanel'); my $repos = Elevate::PkgMgr::pkg_list(); my $jetbackup_tier = $repos->{'jetapps-stable'} ? 'jetapps-stable' : $repos->{'jetapps-edge'} ? 'jetapps-edge' : $repos->{'jetapps-beta'} ? 'jetapps-beta' : 'jetapps-stable'; # Just give up and choose stable if you can't guess. INFO("Jetbackup tier '$jetbackup_tier' detected. Not removing jetbackup. Will re-install it after elevate."); if ( ref Elevate::PkgMgr::instance() eq 'Elevate::PkgMgr::APT' ) { if ( -f "/etc/apt/sources.list.d/$jetbackup_tier.list.disabled" ) { rename( "/etc/apt/sources.list.d/$jetbackup_tier.list.disabled", "/etc/apt/sources.list.d/$jetbackup_tier.list" ) || WARN("Couldn't enable repository for $jetbackup_tier: $!"); } } my @reinstall = Elevate::PkgMgr::get_installed_pkgs_in_repo(qw/jetapps jetapps-stable jetapps-beta jetapps-edge/); push @reinstall, 'jetbackup5-cpanel' if !grep { $_ eq 'jetbackup5-cpanel' } @reinstall; unshift @reinstall, $jetbackup_tier; if ( Elevate::OS::needs_leapp() && Cpanel::Pkgr::is_installed('jetphp81-zip') ) { Elevate::PkgMgr::remove_no_dependencies('jetphp81-zip'); push @reinstall, 'jetphp81-zip' if !grep { $_ eq 'jetphp81-zip' } @reinstall; } my $data = { tier => $jetbackup_tier, packages => \@reinstall, }; Elevate::StageFile::update_stage_file( { 'reinstall' => { 'jetbackup' => $data } } ); return; } sub post_distro_upgrade ($self) { my $data = Elevate::StageFile::read_stage_file('reinstall')->{'jetbackup'}; return unless ref $data && ref $data->{packages}; INFO("Re-installing jetbackup."); my $tier = $data->{tier}; my @packages = $data->{packages}->@*; my $pkgmgr_options = [ '--enablerepo=jetapps', "--enablerepo=$tier" ]; Elevate::PkgMgr::update_with_options( $pkgmgr_options, \@packages ); return; } sub check ($self) { $self->_blocker_old_jetbackup; return; } sub _blocker_jetbackup_is_supported { return undef } sub _blocker_old_jetbackup ($self) { return 0 unless $self->_use_jetbackup4_or_earlier(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); return $self->has_blocker( <<~"END" ); $pretty_distro_name does not support JetBackup prior to version 5. Please upgrade JetBackup before elevate. END } sub _use_jetbackup4_or_earlier ($self) { return unless Cpanel::Pkgr::is_installed('jetbackup'); my $v = Cpanel::Pkgr::get_package_version("jetbackup"); if ( defined $v && $v =~ qr{^[1-4]\b} ) { WARN("JetBackup version $v currently installed."); return 1; } return; } 1; } # --- END lib/Elevate/Components/JetBackup.pm { # --- BEGIN lib/Elevate/Components/KernelCare.pm package Elevate::Components::KernelCare; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cwd (); use File::Copy (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Elevate::Fetch (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { return unless Elevate::OS::supports_kernelcare(); return if Elevate::OS::leapp_can_handle_kernelcare(); $self->run_once("_remove_kernelcare_if_needed"); return; } sub post_distro_upgrade ($self) { return unless Elevate::OS::supports_kernelcare(); return if Elevate::OS::leapp_can_handle_kernelcare(); $self->run_once('_restore_kernelcare'); return; } sub _remove_kernelcare_if_needed ($self) { return unless -x q[/usr/bin/kcarectl]; local $ENV{KCARE_KEEP_REGISTRATION} = '1'; Elevate::PkgMgr::remove_pkgs_from_repos('kernelcare'); Elevate::StageFile::update_stage_file( { 'reinstall' => { 'kernelcare' => 1 } } ); return 1; } sub _restore_kernelcare ($self) { return unless Elevate::StageFile::read_stage_file('reinstall')->{'kernelcare'}; INFO("Restoring kernelcare"); INFO("Retrieving kernelcare installer"); my $installer_script = Elevate::Fetch::script( 'https://kernelcare.com/installer', 'kernelcare_installer' ); my $conf_file = q[/etc/sysconfig/kcare/kcare.conf]; if ( -e $conf_file . q[.rpmsave] ) { INFO("Restoring Configuration file: $conf_file"); File::Copy::cp( $conf_file . q[.rpmsave], $conf_file ); } INFO("Running kernelcare installer"); $self->ssystem_and_die( '/usr/bin/bash' => $installer_script ); unlink $installer_script; INFO("Updating kernelcare"); $self->ssystem(qw{ /usr/bin/kcarectl --update }); return; } sub check ($self) { return unless -x q[/usr/bin/kcarectl]; return if Elevate::OS::supports_kernelcare(); my $name = Elevate::OS::default_upgrade_to(); return $self->has_blocker( <<~"EOS" ); ELevate does not currently support KernelCare for upgrades of $name. Support for KernelCare on $name will be added in a future version of ELevate. EOS } 1; } # --- END lib/Elevate/Components/KernelCare.pm { # --- BEGIN lib/Elevate/Components/Kernel.pm package Elevate::Components::Kernel; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub post_distro_upgrade ($self) { $self->run_once('_kernel_check'); return; } sub _kernel_check ($self) { my $kernel_pkgs = Elevate::PkgMgr::get_installed_pkgs('kernel-*'); my @el7_kernels; foreach my $kernel ( sort keys %$kernel_pkgs ) { if ( $kernel_pkgs->{$kernel} =~ m/\.el7\./ ) { push @el7_kernels, "$kernel-$kernel_pkgs->{$kernel}"; } } return unless @el7_kernels; my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $msg = "The following kernels should probably be removed as they will not function on $pretty_distro_name:\n\n"; foreach my $kernel (@el7_kernels) { $msg .= " $kernel\n"; } $msg .= "\nYou can remove these by running: /usr/bin/rpm -e " . join( " ", @el7_kernels ) . "\n"; Elevate::Notify::add_final_notification($msg); return; } 1; } # --- END lib/Elevate/Components/Kernel.pm { # --- BEGIN lib/Elevate/Components/Leapp.pm package Elevate::Components::Leapp; use cPstrict; use Elevate::Constants (); use Elevate::OS (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { return if $self->is_check_mode(); # skip for --check return unless Elevate::OS::needs_leapp(); return unless $self->upgrade_distro_manually; # skip when --upgrade-distro-manually is provided return if ( $self->components->num_blockers_found() > 0 ); # skip if any blockers have already been found $self->cpev->leapp->install(); my $out = $self->cpev->leapp->preupgrade(); return if ( $out->{status} == 0 ); $self->_check_for_inhibitors(); $self->_check_for_fatal_errors($out); if ( $self->components->num_blockers_found() > 0 ) { INFO('Leapp found issues which would prevent the upgrade, more information can be obtained in the files under /var/log/leapp'); } return; } sub _check_for_inhibitors ($self) { my $inhibitors = $self->cpev->leapp->search_report_file_for_inhibitors( qw( check_detected_devices_and_drivers check_installed_devel_kernels cl_mysql_repository_setup persistentnetnamesdisable verify_check_results ) ); foreach my $inhibitor (@$inhibitors) { my $message = $inhibitor->{title} . "\n"; $message .= $inhibitor->{summary} . "\n"; if ( $inhibitor->{hint} ) { $message .= "Possible resolution: " . $inhibitor->{hint} . "\n"; } if ( $inhibitor->{command} ) { $message .= "Consider running:" . "\n" . $inhibitor->{command} . "\n"; } $self->has_blocker($message); } return; } sub _check_for_fatal_errors ( $self, $out ) { my $error_block = $self->cpev->leapp->extract_error_block_from_output( $out->{stdout} ); if ( length $error_block ) { $self->has_blocker( "Leapp encountered the following error(s):\n" . $error_block ); } return; } 1; } # --- END lib/Elevate/Components/Leapp.pm { # --- BEGIN lib/Elevate/Components/Lists.pm package Elevate::Components::Lists; use cPstrict; use Elevate::OS (); use Elevate::PkgMgr (); use File::Slurper (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant APT_LIST_D => q[/etc/apt/sources.list.d]; sub check ($self) { my $ok = 1; return $ok unless Elevate::OS::is_apt_based(); $ok = 0 if $self->_blocker_apt_can_update(); $ok = 0 if $self->_blocker_apt_has_held_packages(); $ok = 0 if $self->_blocker_invalid_apt_lists(); return $ok; } sub _blocker_apt_can_update ($self) { my $error_msg = <<~'EOS'; '/usr/bin/apt' failed to return cleanly. This could be due to a temporary mirror problem, or it could indicate a larger issue, such as a broken list. Since this script relies heavily on apt, you will need to address this issue before upgrading. If you need assistance, open a ticket with cPanel Support, as outlined here: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS my $ret = Elevate::PkgMgr::clean_all(); if ( $ret->{status} != 0 ) { WARN( "Errors encountered running 'apt-get clean': " . $ret->{stderr} ); } my $makecache = Elevate::PkgMgr::makecache(); if ( $makecache =~ m/\S/ms ) { ERROR($error_msg); ERROR($makecache); my $id = ref($self) . '::AptUpdateError'; return $self->has_blocker( $error_msg . $makecache, info => { name => $id, error => $makecache, }, blocker_id => $id, quiet => 1, ); } return; } sub _blocker_apt_has_held_packages ($self) { my $out = Elevate::PkgMgr::showhold(); if ( $out->{status} != 0 ) { my $stderr = join( "\n", @{ $out->{stderr} } ); return $self->has_blocker( <<~"EOS" ); '/usr/bin/apt-mark showhold' failed to return cleanly: $stderr Since we are unable to reliably determine if any packages are being held back, you will need to address this issue before upgrading. If you need assistance, open a ticket with cPanel support, as outlined here: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS } my @held_packages = @{ $out->{stdout} }; if (@held_packages) { my $held_pkgs = join( "\n", @held_packages ); my $pkgs = join( ' ', @held_packages ); return $self->has_blocker( <<~"EOS" ); The following packages are currently held back and could prevent the upgrade from succeeding: $held_pkgs To unhold the packages, execute /usr/bin/apt-mark unhold $pkgs EOS } return; } sub _blocker_invalid_apt_lists ($self) { my @unvetted_list_files; my $list_dir = APT_LIST_D; my $vetted_apt_lists = Elevate::OS::vetted_apt_lists(); opendir( my $dh, $list_dir ) or die "Unable to read directory $list_dir: $!\n"; foreach my $list_file ( readdir($dh) ) { next unless $list_file =~ m{\.list$}; next if exists $vetted_apt_lists->{$list_file}; push @unvetted_list_files, $list_file; } if (@unvetted_list_files) { my $list_files = join( "\n", @unvetted_list_files ); return $self->has_blocker( <<~"EOS" ); The following unsupported list files were found in $list_dir: $list_files You can temporarily disable these lists by renaming them. For example, to rename a list file titled sample.list, you would do the following: mv $list_dir/sample.list $list_dir/sample.list.disabled Then, to reenable this list, you would simply rename the file back to the original '.list' suffix. EOS } return; } sub post_distro_upgrade ($self) { return unless Elevate::OS::is_apt_based(); $self->run_once('update_list_files'); return; } sub update_list_files ($self) { my $list_dir = APT_LIST_D; opendir( my $dh, $list_dir ) or die "Unable to read directory $list_dir: $!\n"; foreach my $list_file ( readdir($dh) ) { next unless $list_file =~ m{\.list$}; $self->_update_list_file($list_file); } return; } sub _update_list_file ( $self, $list_file ) { my $list_dir = APT_LIST_D; my $vetted_apt_lists = Elevate::OS::vetted_apt_lists(); unless ( $vetted_apt_lists->{$list_file} ) { WARN("Unknown list file: $list_dir/$list_file\n"); return; } File::Slurper::write_binary( "$list_dir/$list_file", "$vetted_apt_lists->{$list_file}\n" ); return; } 1; } # --- END lib/Elevate/Components/Lists.pm { # --- BEGIN lib/Elevate/Components/LiteSpeed.pm package Elevate::Components::LiteSpeed; use cPstrict; use Elevate::Constants (); use Elevate::StageFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { Elevate::StageFile::remove_from_stage_file('reinstall.litespeed'); my $ls_cfg_dir = q[/usr/local/lsws/conf]; return unless -d $ls_cfg_dir; INFO("LiteSpeed is installed"); $self->ssystem(qw{/usr/local/lsws/bin/lshttpd -V}); my $has_valid_license = $? == 0 ? 1 : 0; my $data = { has_valid_license => $has_valid_license, }; Elevate::StageFile::update_stage_file( { 'reinstall' => { 'litespeed' => $data } } ); return; } sub post_distro_upgrade ($self) { my $data = Elevate::StageFile::read_stage_file('reinstall')->{'litespeed'}; return unless ref $data; INFO("Checking LiteSpeed"); if ( $data->{has_valid_license} ) { $self->ssystem(qw{/usr/local/lsws/bin/lshttpd -V}); ERROR("LiteSpeed license is not valid. Check /usr/local/lsws/conf/serial.no") if $? != 0; } $self->ssystem(qw{/usr/bin/systemctl restart lsws}); return; } 1; } # --- END lib/Elevate/Components/LiteSpeed.pm { # --- BEGIN lib/Elevate/Components/MountPoints.pm package Elevate::Components::MountPoints; use cPstrict; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant FINDMNT_BIN => '/usr/bin/findmnt'; use constant MOUNT_BIN => '/usr/bin/mount'; # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { $self->_ensure_mount_dash_a_succeeds(); return; } sub _ensure_mount_dash_a_succeeds ($self) { return if $self->is_check_mode(); my $ret = $self->ssystem_capture_output( MOUNT_BIN, '-a' ); my $stderr = join "\n", @{ $ret->{stderr} }; if ( $ret->{status} != 0 ) { $self->components->abort_on_first_blocker(1); my $bin = MOUNT_BIN(); return $self->has_blocker( <<~"EOS"); The following command failed to execute successfully on your server: $bin -a The following message was given as the reason for the failure: $stderr Since this script will need to reboot your server, we need to ensure a consistent file system in between in each reboot. Please review the entries in '/etc/fstab' and ensure that each entry is valid and that '$bin -a' returns exit code 0 before continuing. If your '/etc/fstab' file has not been customized, you may want to consider reaching out to cPanel Support for assistance: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS } return; } 1; } # --- END lib/Elevate/Components/MountPoints.pm { # --- BEGIN lib/Elevate/Components/MySQL.pm package Elevate::Components::MySQL; use cPstrict; use Try::Tiny; use File::Copy (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Cpanel::OS (); use Cpanel::Pkgr (); use Cpanel::Version::Tiny (); use Cpanel::JSON (); use Cpanel::SafeRun::Simple (); use Cpanel::DB::Map::Collection::Index (); use Cpanel::Exception (); use Cpanel::MysqlUtils::MyCnf::Basic (); use Cpanel::MysqlUtils::Running (); use Elevate::Database (); use Elevate::Notify (); use Elevate::PkgMgr (); use Elevate::StageFile (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } my $cnf_file = '/etc/my.cnf'; sub pre_distro_upgrade ($self) { return if Elevate::Database::is_database_provided_by_cloudlinux(); $self->run_once("_cleanup_mysql_packages"); return; } sub post_distro_upgrade ($self) { return if Elevate::Database::is_database_provided_by_cloudlinux(); $self->run_once('_reinstall_mysql_packages'); return; } sub _cleanup_mysql_packages ($self) { my $mysql_version = Elevate::StageFile::read_stage_file( 'mysql-version', '' ); return unless length $mysql_version; my $db_type = Elevate::Database::get_database_type_name_from_version($mysql_version); INFO("# Cleanup $db_type packages ; using version $mysql_version"); Elevate::StageFile::update_stage_file( { 'mysql-version' => $mysql_version } ); File::Copy::cp( $cnf_file, "$cnf_file.rpmsave_pre_elevate" ) or WARN("Couldn't backup $cnf_file to $cnf_file.rpmsave_pre_elevate: $!"); $self->_remove_cpanel_mysql_packages() unless Elevate::OS::is_apt_based(); return; } sub _remove_cpanel_mysql_packages ($self) { $self->_cleanup_mysql_57_packages(); $self->_cleanup_mysql_80_packages(); $self->_cleanup_mysql_102_packages(); $self->_cleanup_mysql_103_packages(); $self->_cleanup_mysql_105_packages(); $self->_cleanup_mysql_106_packages(); return; } sub _reinstall_mysql_packages { my $upgrade_version = Elevate::StageFile::read_stage_file( 'mysql-version', '' ) or return; my $upgrade_dbtype_name = Elevate::Database::get_database_type_name_from_version($upgrade_version); my $enabled = Elevate::StageFile::read_stage_file( 'mysql-enabled', '' ) or return; INFO("Restoring $upgrade_dbtype_name $upgrade_version"); if ( !$enabled ) { INFO("$upgrade_dbtype_name is not enabled. This will cause the $upgrade_dbtype_name upgrade tool to fail. Temporarily enabling it to ensure the upgrade succeeds."); Cpanel::SafeRun::Simple::saferunnoerror(qw{/usr/local/cpanel/bin/whmapi1 configureservice service=mysql enabled=1}); } try { Elevate::Database::upgrade_database_server(); } catch { LOGDIE( <<~"EOS" ); Unable to install $upgrade_dbtype_name $upgrade_version. To attempt to install the database server manually, execute: /usr/local/cpanel/bin/whmapi1 start_background_mysql_upgrade version=$upgrade_version To have this script attempt to install $upgrade_dbtype_name $upgrade_version for you, execute this script again with the continue flag /scripts/elevate-cpanel --continue Or once the issue has been resolved manually, execute /scripts/elevate-cpanel --continue to complete the ELevation process. EOS }; if ( !$enabled ) { Cpanel::SafeRun::Simple::saferunnoerror(qw{/usr/local/cpanel/bin/whmapi1 configureservice service=mysql enabled=0}); return; } File::Copy::cp( $cnf_file, "$cnf_file.elevate_post_distro_upgrade_orig" ); INFO("Restoring $cnf_file.rpmsave_pre_elevate to $cnf_file..."); File::Copy::cp( "$cnf_file.rpmsave_pre_elevate", $cnf_file ); my $restart_out = Cpanel::SafeRun::Simple::saferunnoerror(qw{/scripts/restartsrv_mysql}); my @restart_lines = split "\n", $restart_out; DEBUG('Restarting Database server with restored my.cnf in place'); DEBUG($restart_out); return if grep { $_ =~ m{mysql (?:re)?started successfully} } @restart_lines; INFO('The database server failed to start. Restoring my.cnf to default.'); File::Copy::cp( "$cnf_file.elevate_post_distro_upgrade_orig", $cnf_file ); $restart_out = Cpanel::SafeRun::Simple::saferunnoerror(qw{/scripts/restartsrv_mysql}); @restart_lines = split "\n", $restart_out; DEBUG('Restarting Database server with original my.cnf in place'); DEBUG($restart_out); return if grep { $_ =~ m{mysql (?:re)?started successfully} } @restart_lines; LOGDIE( <<~'EOS' ); The database server was unable to start after the attempted restoration. Check the elevate log located at '/var/log/elevate-cpanel.log' for further details. Additionally, you may wish to inspect the database error log for further details. This log is located at '/var/lib/mysql/$HOSTNAME.err' where $HOSTNAME is the hostname of your server by default. EOS return; } sub _cleanup_mysql_57_packages ($self) { my @repos = qw{ Mysql-connectors-community Mysql-tools-community Mysql57-community Mysql-tools-preview }; Elevate::PkgMgr::remove_pkgs_from_repos(@repos); return; } sub _cleanup_mysql_80_packages ($self) { my @repos = qw{ Mysql-connectors-community Mysql-tools-community Mysql80-community Mysql-tools-preview }; Elevate::PkgMgr::remove_pkgs_from_repos(@repos); return; } sub _cleanup_mysql_102_packages ($self) { Elevate::PkgMgr::remove_pkgs_from_repos('MariaDB102'); return; } sub _cleanup_mysql_103_packages ($self) { Elevate::PkgMgr::remove_pkgs_from_repos('MariaDB103'); return; } sub _cleanup_mysql_105_packages ($self) { Elevate::PkgMgr::remove_pkgs_from_repos('MariaDB105'); return; } sub _cleanup_mysql_106_packages ($self) { Elevate::PkgMgr::remove_pkgs_from_repos('MariaDB106'); return; } sub check ($self) { my $ok = 1; $ok = 0 unless $self->_blocker_old_mysql; $ok = 0 unless $self->_blocker_mysql_upgrade_in_progress; $ok = 0 unless $self->_blocker_mysql_database_corrupted; $self->_warning_mysql_not_enabled(); return $ok; } sub _blocker_old_mysql ($self) { my $mysql_is_provided_by_cloudlinux = Elevate::Database::is_database_provided_by_cloudlinux(0); return $mysql_is_provided_by_cloudlinux ? $self->_blocker_old_cloudlinux_mysql() : $self->_blocker_old_cpanel_mysql(); } sub _blocker_old_cloudlinux_mysql ($self) { my ( $db_type, $db_version ) = Elevate::Database::get_db_info_if_provided_by_cloudlinux(); return 0 if length $db_version && $db_version >= 55; my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); $db_type = Elevate::Database::pretty_type_name($db_type); $db_version =~ s/([0-9])$/\.$1/; return $self->has_blocker( <<~"EOS"); You are using $db_type $db_version server. This version is not available for $pretty_distro_name. You first need to update your database server software to version 5.5 or later. Please review the following documentation for instructions on how to update to a newer version with MySQL Governor: https://docs.cloudlinux.com/shared/cloudlinux_os_components/#upgrading-database-server Once the upgrade is finished, you can then retry to ELevate to $pretty_distro_name. EOS } sub _blocker_old_cpanel_mysql ($self) { my $db_version = Elevate::Database::get_local_database_version(); if ( Elevate::Database::is_database_version_supported($db_version) ) { Elevate::StageFile::update_stage_file( { 'mysql-version' => $db_version } ); return 0; } my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $db_type = Elevate::Database::get_database_type_name_from_version($db_version); my $upgrade_version = Elevate::Database::get_default_upgrade_version(); my $upgrade_dbtype_name = Elevate::Database::get_database_type_name_from_version($upgrade_version); WARN( <<~"EOS" ); You have $db_type $db_version installed. This version is not available for $pretty_distro_name. EOS if ( $self->is_check_mode() ) { INFO( <<~"EOS" ); You can manually upgrade your installation of $db_type using the following command: /usr/local/cpanel/bin/whmapi1 start_background_mysql_upgrade version=$upgrade_version Once the $db_type upgrade is finished, you can then retry to elevate to $pretty_distro_name. EOS return 0; } WARN( <<~"EOS" ); Prior to elevating this system to $pretty_distro_name, we will automatically upgrade your installation of $db_type to $upgrade_dbtype_name $upgrade_version. EOS if ( !$self->getopt('non-interactive') ) { if ( !IO::Prompt::prompt( '-one_char', '-yes_no', '-tty', -default => 'y', "Do you consent to upgrading to $upgrade_dbtype_name $upgrade_version [Y/n]: ", ) ) { return $self->has_blocker( <<~"EOS" ); The system cannot be elevated to $pretty_distro_name until $db_type has been upgraded. To upgrade manually: /usr/local/cpanel/bin/whmapi1 start_background_mysql_upgrade version=$upgrade_version To have have this script perform the upgrade, run this script again and consent to allow it to upgrade $upgrade_dbtype_name $upgrade_version. EOS } } Elevate::StageFile::update_stage_file( { 'mysql-version' => $upgrade_version } ); return 0; } sub _blocker_mysql_upgrade_in_progress ($self) { if ( -e q[/var/cpanel/mysql_upgrade_in_progress] ) { return $self->has_blocker(q[MySQL/MariaDB upgrade in progress. Please wait for the upgrade to finish.]); } return 0; } sub _blocker_mysql_database_corrupted ($self) { return 0 if $self->is_check_mode(); return 0 unless Cpanel::MysqlUtils::MyCnf::Basic::is_local_mysql(); if ( !Cpanel::MysqlUtils::Running::is_mysql_running() ) { return 0 unless Cpanel::Services::Enabled::is_enabled('mysql'); my $output = $self->ssystem_capture_output(qw{/scripts/restartsrv_mysql}); WARN('Database server was down, starting it to check database integrity'); unless ( Cpanel::MysqlUtils::Running::is_mysql_running() && grep { /mysql (?:re)?started successfully/ } @{ $output->{stdout} } ) { return $self->has_blocker( <<~"EOS" ); Unable to to start the database server to check database integrity. Additional information can be found in the error log located at /var/log/mysqld.log To attempt to start the database server, execute: /scripts/restartsrv_mysql EOS } } my $mysqlcheck_path = Cpanel::Binaries::path('mysqlcheck'); my $output = $self->ssystem_capture_output( $mysqlcheck_path, '-c', '-m', '-A', '--silent' ); if ( scalar grep { /^error/i } @{ $output->{stdout} } ) { my $issues_found = join( "\n", @{ $output->{stdout} } ); return $self->has_blocker( <<~"EOS" ); We have found the following problems with your database(s): $issues_found You should repair any corrupted databases before elevating the system. EOS } return 0; } sub _warning_mysql_not_enabled ($self) { require Cpanel::Services::Enabled; my $enabled = Cpanel::Services::Enabled::is_enabled('mysql'); Elevate::StageFile::update_stage_file( { 'mysql-enabled' => $enabled } ); if ( !$enabled ) { my $db_version = Elevate::Database::get_local_database_version(); my $db_type = Elevate::Database::get_database_type_name_from_version($db_version); WARN( "$db_type is disabled. This must be enabled for the upgrade to succeed.\n" . "We temporarily will enable it when it is needed to be enabled,\n" . "but we recommend starting the process with $db_type enabled." ); } return 0; } 1; } # --- END lib/Elevate/Components/MySQL.pm { # --- BEGIN lib/Elevate/Components/NICs.pm package Elevate::Components::NICs; use cPstrict; use File::Slurper (); use Elevate::Constants (); use Elevate::NICs (); use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant ETH_FILE_PREFIX => Elevate::Constants::ETH_FILE_PREFIX; use constant NIC_PREFIX => q[cp]; use constant PERSISTENT_NET_RULES_PATH => q[/etc/udev/rules.d/70-persistent-net.rules]; use constant SBIN_IP => Elevate::Constants::SBIN_IP; sub pre_distro_upgrade ($self) { return unless Elevate::OS::needs_leapp(); $self->_rename_nics(); return; } sub _rename_nics ($self) { my @nics = Elevate::NICs::get_nics(); return unless scalar @nics > 1; foreach my $nic (@nics) { my $nic_path = ETH_FILE_PREFIX . $nic; my $die_msg = <<~"EOS"; The file for the network interface card (NIC) using kernel-name ($nic) does not exist at the expected location ($nic_path). We are unable to change the name of this NIC due to this. You will need to resolve this issue manually before elevate can continue. Once the issue has been resolved, you can continue this script by executing: /scripts/elevate-cpanel --continue EOS die "$die_msg\n" unless -s $nic_path; my $new_nic = NIC_PREFIX . $nic; INFO("Renaming $nic to $new_nic"); my $device_line_found = 0; my $txt = File::Slurper::read_binary($nic_path); my @nic_lines = split( "\n", $txt ); foreach my $line (@nic_lines) { if ( $line =~ m{^\s*DEVICE\s*=\s*\Q$nic\E\s*$} ) { $device_line_found = 1; $line = "DEVICE=$new_nic"; last; } } die qq[Unable to rename $nic to $new_nic. The line beginning with 'DEVICE' in $nic_path was not found.\n] unless $device_line_found; my $new_nic_path = ETH_FILE_PREFIX . $new_nic; File::Slurper::write_binary( $new_nic_path, join( "\n", @nic_lines ) ); unlink $nic_path; next unless -s PERSISTENT_NET_RULES_PATH; my $rules_txt = File::Slurper::read_binary( PERSISTENT_NET_RULES_PATH() ); my @rules_lines = split( "\n", $rules_txt ); foreach my $line (@rules_lines) { $line =~ s/NAME="\Q$nic\E"/NAME="$new_nic"/; } my $new_rules_txt = join( "\n", @rules_lines ); $new_rules_txt .= "\n"; File::Slurper::write_binary( PERSISTENT_NET_RULES_PATH(), $new_rules_txt ); } return; } sub check ($self) { return 1 unless Elevate::OS::needs_leapp(); return 1 unless $self->upgrade_distro_manually; return $self->_blocker_bad_nics_naming; } sub _blocker_bad_nics_naming ($self) { return $self->has_blocker( q[Missing ] . SBIN_IP . ' binary' ) unless -x SBIN_IP; my @eths = Elevate::NICs::get_nics(); if ( @eths >= 2 ) { WARN( <<~'EOS' ); Your machine has multiple network interface cards (NICs) using kernel-names (ethX). EOS if ( $self->is_check_mode() ) { INFO( <<~'EOS' ); Since the upgrade process cannot guarantee their stability after upgrade, we will need to rename these interfaces before upgrading. EOS return 0; } return if $self->_nics_have_missing_ifcfg_files(@eths); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); WARN( <<~"EOS" ); Prior to elevating this system to $pretty_distro_name, we will automatically rename these interfaces. EOS if ( !$self->getopt('non-interactive') ) { if ( !IO::Prompt::prompt( '-one_char', '-yes_no', '-tty', -default => 'y', 'Do you consent to renaming your NICs to use non kernel-names [Y/n]: ', ) ) { return $self->has_blocker( <<~"EOS" ); The system cannot be elevated to $pretty_distro_name until the NICs using kernel-names (ethX) have been updated with new names. Please provide those interfaces new names before continuing the update. To have this script perform the upgrade, run this script again and consent to allow it to rename the NICs. EOS } } } return 0; } sub _nics_have_missing_ifcfg_files ( $self, @nics ) { my @nics_missing_nic_path; foreach my $nic (@nics) { my $nic_path = ETH_FILE_PREFIX . $nic; my $err_msg = <<~"EOS"; The file for the network interface card (NIC) using kernel-name ($nic) does not exist at the expected location ($nic_path). We are unable to change the name of this NIC due to this. You will need to resolve this issue manually before elevate can continue. EOS unless ( -s $nic_path ) { ERROR($err_msg); push @nics_missing_nic_path, $nic; } } if (@nics_missing_nic_path) { my $missing_nics = join "\n", @nics_missing_nic_path; return $self->has_blocker( <<~"EOS" ); This script is unable to rename the following network interface cards due to a missing ifcfg file: $missing_nics Please provide these interfaces new names before continuing the update. EOS } return; } 1; } # --- END lib/Elevate/Components/NICs.pm { # --- BEGIN lib/Elevate/Components/NixStats.pm package Elevate::Components::NixStats; use cPstrict; use File::Copy (); use Elevate::Constants (); use Elevate::SystemctlService (); use Elevate::Fetch (); use Elevate::Notify (); use Elevate::StageFile (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { $self->run_once("_remove_nixstats"); return; } sub post_distro_upgrade ($self) { $self->run_once('_restore_nixstats'); return; } sub has_nixstats { return -e q[/etc/systemd/system/nixstatsagent.service] || -e q[/usr/local/bin/nixstatsagent]; } sub _remove_nixstats ($self) { return unless has_nixstats(); INFO("Removing nixstats"); my $backup_dir = Elevate::Constants::ELEVATE_BACKUP_DIR . "/nixstats"; File::Path::make_path($backup_dir); die "Failed to create backup directory: $backup_dir" unless -d $backup_dir; my @to_backup = qw{ /etc/nixstats-token.ini /etc/nixstats.ini }; my $to_restore = {}; foreach my $f (@to_backup) { next unless -f $f; my $name = File::Basename::basename($f); my $backup = "$backup_dir/$name"; File::Copy::mv( $f, $backup ); $to_restore->{$backup} = $f; } my $service_name = q[nixstatsagent]; my $service = Elevate::SystemctlService->new( name => $service_name ); my $is_enabled = $service->is_enabled; $service->disable if $is_enabled; $service->stop; my $pip; if ( -x q[/usr/bin/pip3] ) { $pip = q[/usr/bin/pip3]; } elsif ( -x q[/usr/bin/pip] ) { $pip = q[/usr/bin/pip]; } if ($pip) { $self->ssystem( $pip, qw{uninstall -y nixstatsagent} ); } else { ERROR("Cannot remove nixstatsagent: cannot find pip binary"); } my $data = { service_enabled => $is_enabled, to_restore => $to_restore, }; Elevate::StageFile::update_stage_file( { 'reinstall' => { 'nixstats' => $data } } ); return; } sub _restore_nixstats ($self) { my $data = Elevate::StageFile::read_stage_file('reinstall')->{'nixstats'}; return unless ref $data; INFO("Restoring nixstats"); my $user = q[deadbeefdeadbeefdeadbeef]; my $installer_script = Elevate::Fetch::script( 'https://www.nixstats.com/nixstatsagent.sh', 'nixstatsagent' ); $self->ssystem( '/usr/bin/bash', $installer_script, $user ); unlink $installer_script; if ( !-x q[/usr/local/bin/nixstatsagent] ) { ERROR("Missing nixstatsagent binary: /usr/local/bin/nixstatsagent"); } my $service = q[nixstatsagent]; $self->ssystem( qw{/usr/bin/systemctl stop}, $service ); my $to_restore = $data->{to_restore}; foreach my $src ( sort keys %$to_restore ) { my $destination = $to_restore->{$src}; File::Copy::cp( $src, $destination ); } if ( $data->{service_enabled} ) { $self->ssystem( qw{/usr/bin/systemctl enable}, $service ); } else { # leave it disabled $self->ssystem( qw{/usr/bin/systemctl disable}, $service ); } $self->ssystem( qw{/usr/bin/systemctl start}, $service ); if ( $? == 0 ) { INFO("nixstatsagent restored"); } else { Elevate::Notify::add_final_notification( "Failed to start nixstatsagent.service", 1 ); } return; } 1; } # --- END lib/Elevate/Components/NixStats.pm { # --- BEGIN lib/Elevate/Components/OVH.pm package Elevate::Components::OVH; use cPstrict; use Elevate::Constants; use Cpanel::Version::Tiny (); use Cpanel::Update::Tiers (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub check ($self) { return 0 unless $self->__is_ovh(); my $touch_file = Elevate::Constants::OVH_MONITORING_TOUCH_FILE; return 0 if -e $touch_file; my $message = <<~"EOS"; We have detected that your server is hosted by "OVH SA" company. Before continuing the elevation process, you should disable the "proactive monitoring" provided by OVH. When using "proactive monitoring" your server could incorrectly boot in rescue mode during the elevate process. Once you have disabled the monitoring system (or confirm this message does not apply to you), please touch that file and continue the elevation pricess. > touch $touch_file You can read more about this issue: URL: https://github.com/cpanel/elevate/issues/176 OVH Monitoring Documentation: https://support.us.ovhcloud.com/hc/en-us/articles/115001821044-Overview-of-OVHcloud-Monitoring-on-Dedicated-Servers EOS return $self->has_blocker($message); } sub __is_ovh ($self) { return 1 if -e q[/root/.ovhrc]; my @ip_rules = qw{ 5.39.0.0/17 5.135.0.0/16 5.196.0.0/16 8.7.244.0/24 8.18.128.0/24 8.18.172.0/24 8.20.110.0/24 8.21.41.0/24 8.24.8.0/21 8.26.94.0/24 8.29.224.0/24 8.30.208.0/21 8.33.96.0/21 8.33.128.0/21 8.33.136.0/23 15.204.0.0/16 15.235.0.0/16 23.92.224.0/19 37.59.0.0/16 37.60.48.0/20 37.187.0.0/16 45.92.60.0/22 46.105.0.0/16 46.244.32.0/20 51.38.0.0/16 51.68.0.0/16 51.75.0.0/16 51.77.0.0/16 51.79.0.0/16 51.81.0.0/16 51.83.0.0/16 51.89.0.0/16 51.91.0.0/16 51.161.0.0/16 51.178.0.0/16 51.195.0.0/16 51.210.0.0/16 51.222.0.0/16 51.254.0.0/15 54.36.0.0/14 57.128.0.0/17 57.128.128.0/18 62.3.18.0/24 66.70.128.0/17 79.137.0.0/17 87.98.128.0/17 91.90.88.0/21 91.121.0.0/16 91.134.0.0/16 92.222.0.0/16 92.246.224.0/19 94.23.0.0/16 103.5.12.0/22 107.189.64.0/18 109.190.0.0/16 135.125.0.0/16 135.148.0.0/16 137.74.0.0/16 139.99.0.0/16 141.94.0.0/15 142.4.192.0/19 142.44.128.0/17 144.2.32.0/19 144.217.0.0/16 145.239.0.0/16 146.59.0.0/16 147.135.0.0/16 148.113.0.0/18 148.113.128.0/17 149.56.0.0/16 149.202.0.0/16 151.80.0.0/16 151.127.0.0/16 152.228.128.0/17 158.69.0.0/16 162.19.0.0/16 164.132.0.0/16 167.114.0.0/16 172.83.201.0/24 176.31.0.0/16 178.32.0.0/15 185.12.32.0/23 185.15.68.0/22 185.45.160.0/22 185.228.96.0/22 188.165.0.0/16 192.95.0.0/18 192.99.0.0/16 192.240.152.0/21 193.31.62.0/24 193.43.104.0/24 193.70.0.0/17 195.110.30.0/23 195.246.232.0/23 198.27.64.0/18 198.50.128.0/17 198.100.144.0/20 198.244.128.0/17 198.245.48.0/20 209.126.71.0/24 213.32.0.0/17 213.186.32.0/19 213.251.128.0/18 217.182.0.0/16 }; require Net::CIDR; my @cidr_list; foreach my $rule (@ip_rules) { if ( Net::CIDR::cidrvalidate($rule) ) { @cidr_list = Net::CIDR::cidradd( $rule, @cidr_list ); } else { WARN("Invalid CIDR rule '$rule'"); } } require Cpanel::DIp::MainIP; require Cpanel::NAT; my $public_ip = Cpanel::NAT::get_public_ip( Cpanel::DIp::MainIP::getmainip() ); return 1 if eval { Net::CIDR::cidrlookup( $public_ip, @cidr_list ) }; return 0; } 1; } # --- END lib/Elevate/Components/OVH.pm { # --- BEGIN lib/Elevate/Components/PackageRestore.pm package Elevate::Components::PackageRestore; use cPstrict; use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::Pkgr (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub _get_packages_to_check () { return qw{ net-snmp sys-snap }; } sub pre_distro_upgrade ($self) { my @package_list = _get_packages_to_check(); my @installed_packages; foreach my $package (@package_list) { if ( Cpanel::Pkgr::is_installed($package) ) { push @installed_packages, $package; } } Elevate::PkgMgr::remove(@installed_packages); my $config_files = Elevate::PkgMgr::get_config_files( \@installed_packages ); Elevate::StageFile::update_stage_file( { 'packages_to_restore' => $config_files, } ); return; } sub post_distro_upgrade ($self) { my $package_info = Elevate::StageFile::read_stage_file('packages_to_restore'); return unless defined $package_info and ref $package_info eq 'HASH'; foreach my $package ( keys %$package_info ) { Elevate::PkgMgr::install($package); Elevate::PkgMgr::restore_config_files( @{ $package_info->{$package} } ); } return; } 1; } # --- END lib/Elevate/Components/PackageRestore.pm { # --- BEGIN lib/Elevate/Components/PackageDupes.pm package Elevate::Components::PackageDupes; use cPstrict; use Digest::SHA (); use Elevate::Constants (); use Elevate::PkgMgr (); use Cpanel::SafeRun::Simple (); use Cpanel::Version::Compare::Package (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant { ALPHA => 0, DIGIT => 1 }; sub pre_distro_upgrade ($self) { INFO('Looking for duplicate system packages...'); my %dupes = $self->_find_dupes(); if ( scalar %dupes > 0 ) { INFO('Duplicates found.'); if ( !-d Elevate::Constants::RPMDB_BACKUP_DIR ) { INFO('Backing up system package database. If there are problems upgrading packages, consider restoring this backup and resolving the duplicate packages manually.'); if ( $self->_backup_rpmdb() ) { INFO( 'Active RPM database: ' . Elevate::Constants::RPMDB_DIR ); INFO( 'Backup RPM database: ' . Elevate::Constants::RPMDB_BACKUP_DIR ); } else { ERROR('The backup process did not produce a correct backup! ELevate will proceed with the next step in the upgrade process without attempting to correct the issue. If there are problems upgrading packages, resolve the duplicate packages manually.'); return; } } my @packages_to_remove = $self->_select_packages_for_removal(%dupes); DEBUG( "The following packages are being removed from the system package database:\n" . join( "\n", @packages_to_remove ) ); $self->_remove_packages(@packages_to_remove); } else { INFO('No duplicate packages found.'); } return; } sub _find_dupes ($self) { my %dupes; my $output = Cpanel::SafeRun::Simple::saferunnoerror(qw( /usr/bin/package-cleanup --dupes )); foreach my $line ( split /\n/, $output ) { my ( $name, $version, $release, $arch ) = _parse_package($line); push $dupes{$name}->@*, { version => $version, release => $release, arch => $arch } if $name; } return %dupes; } sub _parse_package ($pkg) { return ( $pkg =~ m/^(.+)-(.+)-(.+)\.(.+)$/ ); } sub _backup_rpmdb ($self) { my ( $orig_dir, $backup_dir ) = ( Elevate::Constants::RPMDB_DIR, Elevate::Constants::RPMDB_BACKUP_DIR ); rename $orig_dir, $backup_dir or LOGDIE("Failed to move $orig_dir to $backup_dir (reason: $!)"); File::Copy::Recursive::dircopy( $backup_dir, $orig_dir ); if ( !_rpmdb_backup_is_good( $orig_dir, $backup_dir ) ) { restore_rpmdb_from_backup(); return 0; } return 1; } sub _rpmdb_backup_is_good ( $orig_dir, $backup_dir ) { opendir( my $orig_dh, $orig_dir ) or return 0; opendir( my $backup_dh, $backup_dir ) or return 0; my @orig_files = sort grep { !/^\./ } readdir($orig_dh); my @backup_files = sort grep { !/^\./ } readdir($backup_dh); return 0 if scalar @orig_files != scalar @backup_files; while ( scalar @orig_files && scalar @backup_files ) { my ( $orig_file, $backup_file ) = ( shift(@orig_files), shift(@backup_files) ); return 0 if $orig_file ne $backup_file; my ( $orig_digest, $backup_digest ) = map { Digest::SHA->new(256)->addfile($_)->hexdigest } ( "$orig_dir/$orig_file", "$backup_dir/$backup_file" ); return 0 if !$orig_digest || !$backup_digest || $orig_digest ne $backup_digest; } return 1; } sub restore_rpmdb_from_backup () { local $SIG{'HUP'} = 'IGNORE'; local $SIG{'TERM'} = 'IGNORE'; local $SIG{'INT'} = 'IGNORE'; local $SIG{'QUIT'} = 'IGNORE'; local $SIG{'USR1'} = 'IGNORE'; local $SIG{'USR2'} = 'IGNORE'; my ( $orig_dir, $backup_dir ) = ( Elevate::Constants::RPMDB_DIR, Elevate::Constants::RPMDB_BACKUP_DIR ); File::Path::rmtree($orig_dir); rename $backup_dir, $orig_dir or LOGDIE("Failed to restore original RPM database to $orig_dir (reason: $!)! It is currently stored at $backup_dir."); return; } sub _select_packages_for_removal ( $self, %dupes ) { my @pkgs_for_removal; for my $pkg ( keys %dupes ) { my @sorted_versions = sort { Cpanel::Version::Compare::Package::version_cmp( $a->{version}, $b->{version} ) || Cpanel::Version::Compare::Package::version_cmp( $a->{release}, $b->{release} ) } $dupes{$pkg}->@*; splice @sorted_versions, -2, 1; push @pkgs_for_removal, sprintf( '%s-%s-%s.%s', $pkg, $_->@{qw( version release arch )} ) foreach @sorted_versions; } return @pkgs_for_removal; } sub _remove_packages ( $self, @packages ) { foreach my $pkg (@packages) { Elevate::PkgMgr::remove_no_dependencies_or_scripts_and_justdb($pkg); } return; } 1; } # --- END lib/Elevate/Components/PackageDupes.pm { # --- BEGIN lib/Elevate/Components/Panopta.pm package Elevate::Components::Panopta; use cPstrict; use Cpanel::Pkgr (); use Elevate::PkgMgr (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { if ( Cpanel::Pkgr::is_installed('panopta-agent') ) { Elevate::PkgMgr::remove('panopta-agent'); } return; } 1; } # --- END lib/Elevate/Components/Panopta.pm { # --- BEGIN lib/Elevate/Components/PECL.pm package Elevate::Components::PECL; use cPstrict; use Elevate::Constants (); use Elevate::StageFile (); use Cpanel::JSON (); use Cpanel::SafeRun::Simple (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { $self->run_once("_backup_pecl_packages"); return; } sub post_distro_upgrade ($self) { $self->run_once('_check_pecl_packages'); return; } sub _backup_pecl_packages ($self) { my $out = Cpanel::SafeRun::Simple::saferunnoerror(qw{/usr/local/cpanel/bin/whmapi1 --output=json php_get_installed_versions}); my $result = eval { Cpanel::JSON::Load($out); } // {}; unless ( $result->{metadata}{result} ) { WARN( <<~"EOS" ); Unable to determine the installed PHP versions. Assuming that backing up PECL packages is not relevant as such. EOS return; } foreach my $v ( @{ $result->{'data'}{'versions'} } ) { _store_pecl_for( qq[/opt/cpanel/$v/root/usr/bin/pecl], $v ); } _store_pecl_for( q[/usr/local/cpanel/3rdparty/bin/pecl], 'cpanel' ); return; } sub _check_pecl_packages ($self) { my $pecl = Elevate::StageFile::read_stage_file('pecl'); return unless ref $pecl && scalar keys $pecl->%*; foreach my $v ( sort keys $pecl->%* ) { my $previously_installed = $pecl->{$v}; return unless ref $previously_installed && scalar keys $previously_installed->%*; my $bin; if ( $v eq 'cpanel' ) { $bin = q[/usr/local/cpanel/3rdparty/bin/pecl]; } else { $bin = qq[/opt/cpanel/$v/root/usr/bin/pecl]; } my $currently_installed = _get_pecl_installed_for($bin) // {}; my $final_notification; my $displayed_header = 0; foreach my $pkg ( sort keys $previously_installed->%* ) { next if $currently_installed->{$pkg}; if ( !$displayed_header ) { $displayed_header = 1; WARN( q[*] x 20 ); $final_notification = <<~"EOS"; WARNING: Missing pecl package(s) for $bin Please reinstall these packages: EOS foreach my $l ( split( "\n", $final_notification ) ) { next unless length $l; WARN($l); } WARN( q[*] x 20 ); } WARN("- $pkg"); $final_notification .= qq[- $pkg\n]; } Elevate::Notify::add_final_notification($final_notification); WARN('#') if $displayed_header; } return; } sub _store_pecl_for ( $bin, $name ) { my $list = _get_pecl_installed_for($bin); Elevate::StageFile::remove_from_stage_file("pecl.$name"); return unless ref $list && scalar keys $list->%*; Elevate::StageFile::update_stage_file( { pecl => { $name => $list } } ); return; } sub _get_pecl_installed_for ($bin) { return unless -x $bin; my $out = Cpanel::SafeRun::Simple::saferunnoerror( $bin, 'list' ) // ''; return if $?; my @lines = split( /\n/, $out ); return unless scalar @lines >= 4; shift @lines for 1 .. 3; # remove the header my $installed; foreach my $l (@lines) { my ( $package, $v, $state ) = split( /\s+/, $l, 3 ); $installed->{$package} = $v; } return $installed; } 1; } # --- END lib/Elevate/Components/PECL.pm { # --- BEGIN lib/Elevate/Components/PerlXS.pm package Elevate::Components::PerlXS; use cPstrict; use Elevate::Constants (); use Elevate::Notify (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Config; use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use File::Find (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant DISTRO_PERL_XS_PATH => '/usr/local/lib64/perl5'; sub pre_distro_upgrade ($self) { $self->purge_perl_xs(DISTRO_PERL_XS_PATH); $self->purge_perl_xs( $Config{'installsitearch'} ); return; } sub post_distro_upgrade ($self) { $self->restore_perl_xs(DISTRO_PERL_XS_PATH); $self->restore_perl_xs( $Config{'installsitearch'} ); return; } sub purge_perl_xs ( $self, $path ) { return unless length $path && -d $path; my @perl_modules; File::Find::find( sub { return unless substr( $_, -3 ) eq '.pm'; return if -l $_; return unless -f $_; push @perl_modules, $File::Find::name; }, $path ); my $path_len = length($path); @perl_modules = map { substr( $_, $path_len + 1 ) } sort { $a cmp $b } @perl_modules; my $xspm_to_rpm = xspm_to_rpm(); my %rpms_to_restore; my @modules_to_restore; foreach my $file (@perl_modules) { if ( $path eq DISTRO_PERL_XS_PATH && length $xspm_to_rpm->{$file} ) { $rpms_to_restore{$file} = $xspm_to_rpm->{$file}; } else { push @modules_to_restore, $file; } } my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $stash = {}; if (%rpms_to_restore) { INFO("The following `cpan` installed perl Modules will be removed and replaced with a $pretty_distro_name RPM after upgrade:"); foreach my $file ( sort { $rpms_to_restore{$a} cmp $rpms_to_restore{$b} || $a cmp $b } keys %rpms_to_restore ) { INFO( sprintf( " %20s => %s", $file, $rpms_to_restore{$file} ) ); my $files_in_rpm = $stash->{'restore'}->{$path}->{'rpm'}->{ $rpms_to_restore{$file} } //= []; unless ( grep { $_ eq $file } @$files_in_rpm ) { # Only if we've not stored this. push @$files_in_rpm, $file; unlink "$path/$file"; } } INFO(' '); } if (@modules_to_restore) { WARN("The following modules will likely not be functional on $pretty_distro_name and will be disabled. You will need to restore these manually:"); my $to_restore = $stash->{'restore'}->{$path}->{'cpan'} //= []; foreach my $file (@modules_to_restore) { WARN(" $path/$file"); next if grep { $_ eq $file } @$to_restore; # We've already stashed this. push @$to_restore, $file; rename "$path/$file", "$path/$file.o"; } } Elevate::StageFile::update_stage_file($stash); return; } sub restore_perl_xs ( $self, $path ) { my $stash = Elevate::StageFile::read_stage_file(); if ( $path eq DISTRO_PERL_XS_PATH ) { my $rpms = $stash->{'restore'}->{ DISTRO_PERL_XS_PATH() }->{'rpm'}; if ( scalar keys %$rpms ) { # If there are no XS modules to replace, there is no point to running the dnf install: my $pkgmgr_options = [ '--enablerepo=epel', '--enablerepo=powertools', ]; my @pkgs = sort keys %$rpms; Elevate::PkgMgr::install_with_options( $pkgmgr_options, \@pkgs ); } } my $cpan_modules = $stash->{$path}->{'cpan'} // return; my $msg = "The following XS modules will need to be re-installed:\n\n"; foreach my $module (@$cpan_modules) { $msg .= " $module\n"; } Elevate::Notify::add_final_notification($msg); return; } sub xspm_to_rpm () { return { 'IO/Pty.pm' => 'perl-IO-Tty', 'IO/Tty.pm' => 'perl-IO-Tty', 'IO/Tty/Constant.pm' => 'perl-IO-Tty', 'JSON/Syck.pm' => 'perl-YAML-Syck', 'JSON/XS.pm' => 'perl-JSON-XS', 'JSON/XS/Boolean.pm' => 'perl-JSON-XS', 'YAML/Dumper/Syck.pm' => 'perl-YAML-Syck', 'YAML/Loader/Syck.pm' => 'perl-YAML-Syck', 'YAML/Syck.pm' => 'perl-YAML-Syck', 'common/sense.pm' => 'perl-common-sense', 'version.pm' => 'perl-version', 'version/regex.pm' => 'perl-version', 'version/vpp.pm' => 'perl-version', 'version/vxs.pm' => 'perl-version', }; } 1; } # --- END lib/Elevate/Components/PerlXS.pm { # --- BEGIN lib/Elevate/Components/PostgreSQL.pm package Elevate::Components::PostgreSQL; use cPstrict; use Simple::Accessor qw{service}; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use Elevate::Constants (); use Elevate::Notify (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Elevate::SystemctlService (); use Cpanel::Pkgr (); use Cpanel::SafeRun::Object (); use Cpanel::Services::Enabled (); use Whostmgr::Postgres (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use File::Copy::Recursive (); use File::Slurp (); sub _build_service ($self) { return Elevate::SystemctlService->new( name => 'postgresql' ); } sub check ($self) { return if Elevate::OS::supports_postgresql(); if ( Cpanel::Pkgr::is_installed('postgresql') ) { my $name = Elevate::OS::default_upgrade_to(); $self->has_blocker( <<~"END" ); ELevate does not currently support PostgreSQL for upgrades to $name. END } return; } sub pre_distro_upgrade ($self) { return unless Elevate::OS::supports_postgresql(); if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { $self->_store_postgresql_encoding_and_locale(); $self->_disable_postgresql_service(); $self->_backup_postgresql_datadir(); } return; } sub _store_postgresql_encoding_and_locale ($self) { return if $self->_gave_up_on_postgresql; # won't hurt INFO("Fetching encoding and locale information from PostgreSQL."); my $is_active_prior = $self->service->is_active; $self->service->start(); if ( $self->service->is_active ) { my $psql_sro = Cpanel::SafeRun::Object->new( program => '/usr/bin/psql', args => [ qw{-F | -At -U postgres -c}, q{SELECT pg_encoding_to_char(encoding), datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'}, ], ); if ( $psql_sro->CHILD_ERROR ) { WARN("The system instance of PostgreSQL did not return information about the encoding and locale of core databases."); WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); return; } my $output = $psql_sro->stdout; chomp $output; my ( $encoding, $collation, $ctype ) = split /\|/, $output; Elevate::StageFile::update_stage_file( { postgresql_locale => { encoding => $encoding, collation => $collation, ctype => $ctype, } } ); $self->service->stop() unless $is_active_prior; } else { WARN("The system instance of PostgreSQL could not start to give information about the encoding and locale of core databases."); WARN("ELevate will assume the system defaults and attempt an upgrade anyway."); } return; } sub _disable_postgresql_service ($self) { if ( Cpanel::Services::Enabled::is_enabled('postgresql') ) { Elevate::StageFile::update_stage_file( { 're-enable_postgresql_in_sm' => 1 } ); Cpanel::Services::Enabled::touch_disable_file('postgresql'); } return; } sub _backup_postgresql_datadir ($self) { $self->service->stop() if $self->service->is_active; # for safety my $pgsql_datadir_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR; my $pgsql_datadir_backup_path = $pgsql_datadir_path . '_elevate_' . time() . '_' . $$; INFO("Backing up the system PostgreSQL data directory at $pgsql_datadir_path to $pgsql_datadir_backup_path."); my ( $uid, $gid ) = ( scalar( getpwnam('postgres') ), scalar( getgrnam('postgres') ) ); my $outcome = 0; { local ( $>, $) ) = ( $uid, "$gid $gid" ); $outcome = File::Copy::Recursive::dircopy( $pgsql_datadir_path, $pgsql_datadir_backup_path ); } if ( !$outcome ) { ERROR("The system encountered an error while trying to make a backup."); $self->_give_up_on_postgresql(); } else { Elevate::Notify::add_final_notification( <<~"EOS" ); ELevate backed up your system PostgreSQL data directory to $pgsql_datadir_backup_path prior to any modification or attempt to upgrade, in case the upgrade needs to be performed manually, or if old settings need to be referenced. EOS } return; } sub post_distro_upgrade ($self) { return unless Elevate::OS::supports_postgresql(); if ( Cpanel::Pkgr::is_installed('postgresql-server') ) { $self->_perform_config_workaround(); $self->_perform_postgresql_upgrade(); $self->_re_enable_service_if_needed(); $self->_run_whostmgr_postgres_update_config(); } return; } sub _perform_config_workaround ($self) { return if $self->_gave_up_on_postgresql; my $pgconf_path = Elevate::Constants::POSTGRESQL_SYSTEM_DATADIR . '/postgresql.conf'; return unless -e $pgconf_path; # if postgresql.conf isn't there, there's nothing to work around my $pgconf = eval { File::Slurper::read_text($pgconf_path) }; if ($@) { ERROR("Attempting to read $pgconf_path resulted in an error: $@"); $self->_give_up_on_postgresql(); return; } my $changed = 0; my @lines = split "\n", $pgconf; foreach my $line (@lines) { next if $line =~ m/^\s*$/a; if ( $line =~ m/^\s*unix_socket_directories/ ) { $line = "#$line"; $changed = 1; } } if ($changed) { push @lines, "unix_socket_directory = '/var/run/postgresql'"; INFO("Modifying $pgconf_path to work around a defect in the system's PostgreSQL upgrade package."); my $pgconf_altered = join "\n", @lines; eval { File::Slurper::write_text( $pgconf_path, $pgconf_altered ) }; if ($@) { ERROR("Attempting to overwrite $pgconf_path resulted in an error: $@"); $self->_give_up_on_postgresql(); } } return; } sub _perform_postgresql_upgrade ($self) { return if $self->_gave_up_on_postgresql; INFO("Installing PostgreSQL update package:"); Elevate::PkgMgr::install('postgresql-upgrade'); my $opts = Elevate::StageFile::read_stage_file('postgresql_locale'); my @args; push @args, "--encoding=$opts->{encoding}" if $opts->{encoding}; push @args, "--lc-collate=$opts->{collation}" if $opts->{collation}; push @args, "--lc-ctype=$opts->{ctype}" if $opts->{ctype}; local $ENV{PGSETUP_INITDB_OPTIONS} = join ' ', @args if scalar @args > 0; INFO("Upgrading PostgreSQL data directory:"); my $outcome = $self->ssystem( { keep_env => ( scalar @args > 0 ? 1 : 0 ) }, qw{/usr/bin/postgresql-setup --upgrade} ); if ( $outcome == 0 ) { INFO("The PostgreSQL upgrade process was successful."); Elevate::Notify::add_final_notification( <<~EOS ); ELevate successfully ran the upgrade procedure on the system instance of PostgreSQL. If no problems are reported with configuring the upgraded instance to work with cPanel, you should proceed with applying any relevant customizations to the configuration and authentication settings, since the upgrade process reset this information to system defaults. EOS } else { ERROR("The upgrade attempt of the system PostgreSQL instance failed. See the log files mentioned in the output of postgresql-setup for more information."); $self->_give_up_on_postgresql(); } return; } sub _re_enable_service_if_needed ($self) { return if $self->_gave_up_on_postgresql; # keep disabled if there was a failure if ( Elevate::StageFile::read_stage_file( 're-enable_postgresql_in_sm', 0 ) ) { Cpanel::Services::Enabled::remove_disable_files('postgresql'); } return; } sub _run_whostmgr_postgres_update_config ($self) { return if $self->_gave_up_on_postgresql; INFO("Configuring PostgreSQL to work with cPanel's installation of phpPgAdmin."); $self->service->start(); # less noisy when it's online, but still works my ( $success, $msg ) = Whostmgr::Postgres::update_config(); if ( !$success ) { ERROR("The system failed to update the PostgreSQL configuration: $msg"); Elevate::Notify::add_final_notification( <<~EOS ); ELevate could not configure the upgraded system PostgreSQL instance to work with cPanel. See the log for additional information. Once the issue has been addressed, perform this step manually using the "Postgres Config Install" area in WHM: https://go.cpanel.net/whmdocsConfigurePostgreSQL Do this before restoring any customizations to PostgreSQL configuration or authentication files, since performing this action resets these to cPanel defaults. EOS } return; } sub _give_up_on_postgresql ($self) { ERROR('Skipping attempt to upgrade the system instance of PostgreSQL.'); Elevate::StageFile::update_stage_file( { postgresql_give_up => 1 } ); Elevate::Notify::add_final_notification( <<~EOS ); The process of upgrading the system instance of PostgreSQL failed. The PostgreSQL service has been disabled in the Service Manager in WHM: https://go.cpanel.net/whmdocsServiceManager If you do not have cPanel users who use PostgreSQL or otherwise do not use it, you do not have to take any action. Otherwise, see the ELevate logs for further information. EOS return; } sub _gave_up_on_postgresql ($self) { return Elevate::StageFile::read_stage_file( 'postgresql_give_up', 0 ); } sub _given_up_on_postgresql { goto &_gave_up_on_postgresql; } 1; } # --- END lib/Elevate/Components/PostgreSQL.pm { # --- BEGIN lib/Elevate/Components/R1Soft.pm package Elevate::Components::R1Soft; use cPstrict; use Elevate::Constants (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::Pkgr (); use File::Slurper (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { my $r1soft_agent_installed = 0; my $r1soft_repo_present = 0; my $r1soft_repo_enabled = 0; if ( Cpanel::Pkgr::is_installed(Elevate::Constants::R1SOFT_MAIN_AGENT_PACKAGE) ) { $r1soft_agent_installed = 1; my @repo_list = Elevate::PkgMgr::repolist_enabled(); if ( scalar grep { index( $_, Elevate::Constants::R1SOFT_REPO ) == 0 } @repo_list ) { $r1soft_repo_present = 1; $r1soft_repo_enabled = 1; } else { @repo_list = Elevate::PkgMgr::repolist_all(); if ( scalar grep { index( $_, Elevate::Constants::R1SOFT_REPO ) == 0 } @repo_list ) { $r1soft_repo_present = 1; } } Elevate::PkgMgr::remove(Elevate::Constants::R1SOFT_AGENT_PACKAGES); } Elevate::StageFile::update_stage_file( { r1soft => { agent_installed => $r1soft_agent_installed, repo_present => $r1soft_repo_present, repo_enabled => $r1soft_repo_enabled, } } ); return; } sub post_distro_upgrade ($self) { my $r1soft_info = Elevate::StageFile::read_stage_file('r1soft'); return unless $r1soft_info->{agent_installed}; Elevate::PkgMgr::install('kernel-devel'); if ( !$r1soft_info->{repo_present} ) { $self->_create_r1soft_repo(); } else { $self->_enable_r1soft_repo() unless $r1soft_info->{repo_enabled}; } Elevate::PkgMgr::install(Elevate::Constants::R1SOFT_AGENT_PACKAGES); if ( !$r1soft_info->{repo_present} || !$r1soft_info->{repo_enabled} ) { $self->_disable_r1soft_repo(); } return; } sub _enable_r1soft_repo ($self) { return $self->_run_yum_config_manager( '--enable', Elevate::Constants::R1SOFT_REPO ); } sub _disable_r1soft_repo ($self) { return $self->_run_yum_config_manager( '--disable', Elevate::Constants::R1SOFT_REPO ); } sub _run_yum_config_manager ( $self, @args ) { my $yum_config = Cpanel::Binaries::path('yum-config-manager'); my $err = $self->ssystem( $yum_config, @args ); ERROR("Error running $yum_config: $err") if $err; return $err; } sub _create_r1soft_repo ($self) { my $yum_repo_contents = <<~"EOS"; [r1soft] name=R1Soft Repository Server baseurl=https://repo.r1soft.com/yum/stable/\$basearch/ enabled=1 gpgcheck=0 EOS if ( -e Elevate::Constants::R1SOFT_REPO_FILE ) { ERROR( "Cannot create R1Soft repo. File already exists: " . Elevate::Constants::R1SOFT_REPO_FILE ); return; } File::Slurper::write_binary( Elevate::Constants::R1SOFT_REPO_FILE, $yum_repo_contents ); chmod 0644, Elevate::Constants::R1SOFT_REPO_FILE; return; } 1; } # --- END lib/Elevate/Components/R1Soft.pm { # --- BEGIN lib/Elevate/Components/Repositories.pm package Elevate::Components::Repositories; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Cpanel::SafeRun::Simple (); use Cwd (); use File::Copy (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant EPEL_RPM_URL => 'https://archives.fedoraproject.org/pub/archive/epel/7/x86_64/Packages/e/epel-release-7-14.noarch.rpm'; use constant YUM_COMPLETE_TRANSACTION_BIN => '/usr/sbin/yum-complete-transaction'; use constant FIX_RPM_SCRIPT => '/usr/local/cpanel/scripts/find_and_fix_rpm_issues'; use constant EXPECTED_EXTRA_PACKAGES => ( qr/^cpanel-/, qr/^easy-/, qr/^kernel/, qr/^mysql/i, qr/^plesk-/, 'basesystem', 'filesystem', 'grub', 'grubby', 'python35', 'python38-opt', 'virt-what', 'vzdummy-systemd-el7', ), Elevate::Constants::R1SOFT_AGENT_PACKAGES, Elevate::Constants::ACRONIS_OTHER_PACKAGES; sub pre_distro_upgrade ($self) { return if Elevate::OS::is_apt_based(); $self->run_once("_disable_yum_plugin_fastestmirror"); $self->run_once("_disable_known_yum_repositories"); $self->run_once("_fixup_epel_repo"); return; } sub _disable_known_yum_repositories { my @repo_files = map { Elevate::Constants::YUM_REPOS_D . '/' . $_ } Elevate::OS::disable_mysql_yum_repos(); foreach my $f (@repo_files) { next unless -e $f; if ( -l $f ) { unlink $f; next; } File::Copy::mv( $f, "$f.off" ) or die qq[Failed to disable repo $f]; } Elevate::PkgMgr::clean_all(); return; } sub _disable_yum_plugin_fastestmirror ($self) { my $pkg = 'yum-plugin-fastestmirror'; $self->_erase_package($pkg); return; } sub _fixup_epel_repo ($self) { my $repo_file = Elevate::Constants::YUM_REPOS_D . '/epel.repo'; if ( -e $repo_file ) { unlink($repo_file) or ERROR("Could not delete $repo_file: $!"); } my $epel_url = EPEL_RPM_URL(); my $err = Elevate::PkgMgr::force_upgrade_pkg($epel_url); ERROR("Error installing epel-release: $err") if $err; return; } sub _erase_package ( $self, $pkg ) { return unless Cpanel::Pkgr::is_installed($pkg); Elevate::PkgMgr::remove_no_dependencies($pkg); return; } sub check ($self) { my $ok = 1; return $ok if Elevate::OS::is_apt_based(); $ok = 0 if $self->_blocker_packages_installed_without_associated_repo; $ok = 0 if $self->_blocker_invalid_yum_repos; $ok = 0 if $self->_yum_is_stable(); return $ok; } sub _blocker_packages_installed_without_associated_repo ($self) { my @extra_packages = map { $_->{package} } Elevate::PkgMgr::get_extra_packages(); my @unexpected_extra_packages; foreach my $pkg (@extra_packages) { next if grep { $pkg =~ m/$_/ } EXPECTED_EXTRA_PACKAGES(); push @unexpected_extra_packages, $pkg; } return unless scalar @unexpected_extra_packages; my $pkg_string = join "\n", @unexpected_extra_packages; return $self->has_blocker( <<~EOS ); There are packages installed that do not have associated repositories: $pkg_string EOS } sub _blocker_invalid_yum_repos ($self) { my $status_hr = $self->_check_yum_repos(); if ( _yum_status_hr_contains_blocker($status_hr) ) { my $msg = ''; if ( $status_hr->{'INVALID_SYNTAX'} ) { $msg .= <<~'EOS'; One or more enabled YUM repo are using invalid syntax. '\$' variables behave differently in repo files between RedHat 7 and RedHat 8. RedHat 7 interpolates '\$' variable whereas RedHat 8 does not. Please fix the files before continuing the update. EOS } if ( $status_hr->{'USE_RPMS_FROM_UNVETTED_REPO'} ) { $msg .= <<~'EOS'; One or more enabled YUM repositories are currently unsupported and have installed packages. You should disable these repositories and remove packages installed from them before continuing the update. EOS } if ( !$self->is_check_mode() ) { # autofix when --check is not used $self->_autofix_yum_repos(); $self->_autofix_duplicate_repoids(); $status_hr = $self->_check_yum_repos(); } return 0 unless _yum_status_hr_contains_blocker($status_hr); if ( $status_hr->{DUPLICATE_IDS} ) { my $duplicate_ids = join "\n", keys $self->{_duplicate_repoids}->%*; my $dupe_id_msg = <<~"EOS"; One or more enable YUM repo have repositories defined multiple times: $duplicate_ids A possible resolution for this issue is to either remove the duplicate repository definitions or change the repoids of the conflicting repositories on the system to prevent the conflict. EOS my $blocker_id = ref($self) . '::' . 'DuplicateRepoIds'; $self->has_blocker( $dupe_id_msg, blocker_id => $blocker_id, ); } for my $unsupported_repo ( @{ $self->{_yum_repos_unsupported_with_packages} } ) { my $blocker_id = ref($self) . '::' . $unsupported_repo->{'name'}; $self->has_blocker( $msg, info => $unsupported_repo->{info}, blocker_id => $blocker_id, quiet => 1, ); } } return 1; } sub _yum_status_hr_contains_blocker ($status_hr) { return 0 if ref $status_hr ne 'HASH' || !scalar keys( %{$status_hr} ); my @blockers = qw{INVALID_SYNTAX USE_RPMS_FROM_UNVETTED_REPO DUPLICATE_IDS}; foreach my $blocked (@blockers) { return 1 if $status_hr->{$blocked}; } return 0; } sub _yum_is_stable ($self) { my $errors = Elevate::PkgMgr::makecache(); if ( $errors =~ m/\S/ms ) { my $error_msg = <<~'EOS'; '/usr/bin/yum makecache' failed to return cleanly. This could be due to a temporary mirror problem, or it could indicate a larger issue, such as a broken repository. Since this script relies heavily on yum, you will need to address this issue before upgrading. If you need assistance, open a ticket with cPanel Support, as outlined here: https://docs.cpanel.net/knowledge-base/technical-support-services/how-to-open-a-technical-support-ticket/ EOS WARN("Initial run of \"yum makecache\" failed: $errors"); WARN("Running \"yum clean all\" in an attempt to fix yum"); my $ret = Elevate::PkgMgr::clean_all(); if ( $ret->{status} != 0 ) { WARN( "Errors encountered running \"yum clean all\": " . $ret->{stderr} ); } $errors = my $errors = Elevate::PkgMgr::makecache(); if ( $errors =~ m/\S/ms ) { ERROR($error_msg); ERROR($errors); my $id = ref($self) . '::YumMakeCacheError'; return $self->has_blocker( "$error_msg" . "$errors", info => { name => $id, error => $errors, }, blocker_id => $id, quiet => 1, ); } } if ( opendir( my $dfh, '/var/lib/yum' ) ) { my @transactions = grep { m/^transaction-all\./ } readdir $dfh; if (@transactions) { WARN('There are unfinished yum transactions remaining.'); my $yum_ct_bin = YUM_COMPLETE_TRANSACTION_BIN(); if ( $self->is_check_mode() ) { WARN("Unfinished yum transactions detected. Elevate will execute $yum_ct_bin --cleanup-only during upgrade"); } else { if ( -x $yum_ct_bin ) { INFO('Cleaning up unfinished yum transactions.'); my $ret = $self->ssystem_capture_output( $yum_ct_bin, '--cleanup-only' ); if ( $ret->{status} != 0 ) { return $self->has_blocker( "Errors encountered running $yum_ct_bin: " . $ret->{stderr} ); } $ret = $self->ssystem_capture_output(FIX_RPM_SCRIPT); if ( $ret->{status} != 0 ) { return $self->has_blocker( 'Errors encountered running ' . FIX_RPM_SCRIPT . ': ' . $ret->{stderr} ); } } else { return $self->has_blocker( <<~EOS ); $yum_ct_bin is missing. You must install the yum-utils package if you wish to clear the unfinished yum transactions. EOS } } } } else { my $err = $!; # Don't want to accidentally lose the error ERROR(qq{Could not read directory '/var/lib/yum': $err}); my $id = ref($self) . '::YumDirUnreadable'; return $self->has_blocker( qq{Could not read directory '/var/lib/yum': $err}, info => { name => $id, error => $err, }, blocker_id => $id, quiet => 1, ); } return 0; } sub _check_yum_repos ($self) { $self->{_yum_repos_path_using_invalid_syntax} = []; $self->{_yum_repos_to_disable} = []; $self->{_yum_repos_unsupported_with_packages} = []; $self->{_duplicate_repoids} = {}; my @vetted_repos = Elevate::OS::vetted_yum_repo(); my $repo_dir = Elevate::Constants::YUM_REPOS_D; my %status; my %repoids; my %duplicate_repoids; opendir( my $dh, $repo_dir ) or do { ERROR("Cannot read directory $repo_dir - $!"); return; }; foreach my $f ( readdir($dh) ) { next unless $f =~ m{\.repo$}; my $path = "${repo_dir}/$f"; next unless -f $path; my $txt = eval { File::Slurper::read_text($path) }; next unless length $txt; my @lines = split( qr/\n/, $txt ); my $current_repo_name; my $current_repo_enabled = 1; my $current_repo_use_valid_syntax = 1; my $check_last_known_repo = sub { return unless length $current_repo_name; my $is_vetted = grep { $current_repo_name =~ m/$_/ } @vetted_repos; if ( !$is_vetted ) { $status{'UNVETTED'} = 1; my @installed_packages = Elevate::PkgMgr::get_installed_pkgs_in_repo($current_repo_name); if ( my $total_pkg = scalar @installed_packages ) { # FIXME ERROR( sprintf( "%d package(s) installed from unsupported YUM repo '%s' from %s", $total_pkg, $current_repo_name, $path ) ); push( $self->{_yum_repos_unsupported_with_packages}->@*, { name => $current_repo_name, info => { name => $current_repo_name, path => $path, num_packages => scalar @installed_packages, packages => [ sort @installed_packages ], }, }, ); $status{'USE_RPMS_FROM_UNVETTED_REPO'} = 1; } else { return unless $current_repo_enabled; INFO( sprintf( "Unsupported YUM repo enabled '%s' without packages installed from %s, these will be disabled before ELevation", $current_repo_name, $path ) ); push( $self->{_yum_repos_to_disable}->@*, $current_repo_name ); $status{'HAS_UNUSED_REPO_ENABLED'} = 1; } } elsif ( !$current_repo_use_valid_syntax ) { return unless $current_repo_enabled; WARN( sprintf( "YUM repo '%s' is using unsupported '\\\$' syntax in %s", $current_repo_name, $path ) ); unless ( grep { $_ eq $path } $self->{_yum_repos_path_using_invalid_syntax}->@* ) { my $blocker_id = ref($self) . '::YumRepoConfigInvalidSyntax'; $self->has_blocker( sprintf( "YUM repo '%s' is using unsupported '\\\$' syntax in %s", $current_repo_name, $path ), info => { name => $blocker_id, error => 'YUM repository has unsupported syntax', repository => $current_repo_name, path => $path, }, blocker_id => $blocker_id, quiet => 1, ); push( $self->{_yum_repos_path_using_invalid_syntax}->@*, $path ); } $status{'INVALID_SYNTAX'} = 1; } return; }; foreach my $line (@lines) { next if $line =~ qr{^\s*\#}; # skip comments $line =~ s{\s*\#.+$}{}; # strip comments if ( $line =~ qr{^\s*\[\s*(.+)\s*\]} ) { $check_last_known_repo->(); $current_repo_name = $1; $current_repo_enabled = 1; # assume enabled unless explicitely disabled $current_repo_use_valid_syntax = 1; if ( $repoids{$current_repo_name} ) { $duplicate_repoids{$current_repo_name} = $path; } $repoids{$current_repo_name} = 1; next; } next unless defined $current_repo_name; $current_repo_enabled = 0 if $line =~ m{^\s*enabled\s*=\s*0}; $current_repo_use_valid_syntax = 0 if $line =~ m{\\\$}; } $check_last_known_repo->(); } if ( scalar keys %duplicate_repoids ) { $status{DUPLICATE_IDS} = 1; $self->{_duplicate_repoids} = \%duplicate_repoids; } return \%status; } sub _autofix_duplicate_repoids ($self) { return unless ref $self->{_duplicate_repoids} && ref $self->{_duplicate_repoids} eq 'HASH'; my %duplicate_ids = $self->{_duplicate_repoids}->%*; foreach my $id ( keys %duplicate_ids ) { if ( $id =~ m/^MariaDB[0-9]+/ ) { my $path = $duplicate_ids{$id}; File::Copy::mv( $path, "$path.disabled_by_elevate" ); } } return; } sub _autofix_yum_repos ($self) { if ( ref $self->{_yum_repos_path_using_invalid_syntax} ) { my @files_with_invalid_syntax = $self->{_yum_repos_path_using_invalid_syntax}->@*; foreach my $f (@files_with_invalid_syntax) { INFO( q[Fixing \$ variables in repo file: ] . $f ); Cpanel::SafeRun::Simple::saferunnoerror( $^X, '-pi', '-e', 's{\\\\\$}{\$}g', $f ); } } if ( ref $self->{_yum_repos_to_disable} ) { my @repos_to_disable = $self->{_yum_repos_to_disable}->@*; foreach my $repo (@repos_to_disable) { INFO(qq[Disabling unused yum repository: $repo]); Cpanel::SafeRun::Simple::saferunnoerror( qw{/usr/bin/yum-config-manager --disable}, $repo ); } } return; } 1; } # --- END lib/Elevate/Components/Repositories.pm { # --- BEGIN lib/Elevate/Components/RmMod.pm package Elevate::Components::RmMod; use cPstrict; use Elevate::Constants (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { $self->run_once("_rmod_ln"); return; } sub _rmod_ln ($self) { $self->ssystem( '/usr/sbin/rmmod', $_ ) foreach qw/floppy pata_acpi/; return; } 1; } # --- END lib/Elevate/Components/RmMod.pm { # --- BEGIN lib/Elevate/Components/RpmDB.pm package Elevate::Components::RpmDB; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::PkgMgr (); use Cwd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Cpanel::Pkgr (); use Cpanel::Yum::Vars (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant OBSOLETE_PACKAGES => ( 'compat-db', 'gd-progs', 'python-tools', 'python2-dnf', 'python2-libcomps', 'tcp_wrappers-devel', 'tkinter', 'yum-plugin-universal-hooks', 'eigid', 'quickinstall', ); sub pre_distro_upgrade ($self) { $self->run_once("_cleanup_rpms"); return; } sub _cleanup_rpms ($self) { Elevate::PkgMgr::remove_cpanel_arch_pkgs(); $self->_remove_obsolete_packages(); return; } sub _remove_obsolete_packages ($self) { return unless Elevate::OS::needs_leapp(); my @pkgs_to_remove = OBSOLETE_PACKAGES(); Elevate::PkgMgr::remove(@pkgs_to_remove); return; } sub post_distro_upgrade ($self) { $self->run_once("_sysup"); return; } sub _sysup ($self) { Cpanel::Yum::Vars::install(); Elevate::PkgMgr::clean_all(); if ( Elevate::OS::needs_epel() ) { my $epel_url = 'https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm'; unless ( Cpanel::Pkgr::is_installed('epel-release') ) { Elevate::PkgMgr::install_pkg_via_url($epel_url); } Elevate::PkgMgr::config_manager_enable('epel'); } Elevate::PkgMgr::config_manager_enable('powertools') if Elevate::OS::needs_powertools(); unlink('/usr/local/cpanel/3rdparty/perl/536/cpanel-lib/X/Tiny.pm'); { local $ENV{'CPANEL_BASE_INSTALL'} = 1; # Don't fix more than perl itself. $self->ssystem(qw{/usr/local/cpanel/scripts/fix-cpanel-perl}); } Elevate::PkgMgr::update_allow_erasing( '--disablerepo', 'cpanel-plugins' ); $self->ssystem_and_die(qw{/usr/local/cpanel/scripts/sysup}); return; } 1; } # --- END lib/Elevate/Components/RpmDB.pm { # --- BEGIN lib/Elevate/Components/SSH.pm package Elevate::Components::SSH; use cPstrict; use Elevate::Constants (); use Cwd (); use File::Slurper (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { my $sshd_config = q[/etc/ssh/sshd_config]; my $setup = File::Slurper::read_binary($sshd_config) // ''; return if ( $setup =~ m{^\s*PermitRootLogin\b}m ); if ( $setup !~ m{\n$} && length $setup ) { $setup .= "\n"; } $setup .= "PermitRootLogin yes\n"; File::Slurper::write_binary( $sshd_config, $setup ); return; } sub check ($self) { return $self->_check_ssh_config(); } sub _check_ssh_config ($self) { my $sshd_config = q[/etc/ssh/sshd_config]; my $setup = eval { File::Slurper::read_binary($sshd_config) } // ''; if ( my $exception = $@ ) { ERROR("The system could not read the sshd config file ($sshd_config): $exception"); return $self->has_blocker(qq[Unable to read the sshd config file: $sshd_config]); } if ( $setup !~ m{^\s*PermitRootLogin\b}m ) { WARN( <<~"EOS" ); OpenSSH configuration file does not explicitly state the option PermitRootLogin in sshd_config file, which will default in RHEL8 to "prohibit-password". We will set the 'PermitRootLogin' value in $sshd_config to 'yes' before upgrading. EOS return 0; } return 1; } 1; } # --- END lib/Elevate/Components/SSH.pm { # --- BEGIN lib/Elevate/Components/Softaculous.pm package Elevate::Components::Softaculous; use cPstrict; use Elevate::Fetch (); use Elevate::StageFile (); use Cpanel::Binaries (); use Cpanel::SafeRun::Object (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Simple::Accessor qw(cli_path); INIT { Simple::Accessor->import(qw{cli_path}); } sub _build_cli_path { return '/usr/local/cpanel/whostmgr/docroot/cgi/softaculous/cli.php' } sub pre_distro_upgrade ($self) { return unless -r $self->cli_path; my $sr = _run_script( $self->cli_path ); return if $sr->exec_failed() || $sr->to_exception(); my $version = $sr->stdout() // ''; chomp $version; if ( length $version ) { INFO('Softaculous has been detected. The system will re-install that software after the distro upgrade.'); Elevate::StageFile::update_stage_file( { softaculous => $version } ); } return; } sub _run_script ($path) { return Cpanel::SafeRun::Object->new( program => Cpanel::Binaries::path('php'), args => [ $path, '--version' ], ); } sub post_distro_upgrade ($self) { my $version = Elevate::StageFile::read_stage_file( 'softaculous', '' ); return unless length $version; my $path = Elevate::Fetch::script( 'https://files.softaculous.com/install.sh', 'softaculous_install' ); if ($path) { INFO('Re-installing Softaculous:'); if ( $self->ssystem( Cpanel::Binaries::path('bash'), $path, '--reinstall' ) ) { ERROR('Re-installation of Softaculous failed.'); } } else { ERROR('Failed to download Softaculous installer.'); } return; } 1; } # --- END lib/Elevate/Components/Softaculous.pm { # --- BEGIN lib/Elevate/Components/UnconvertedModules.pm package Elevate::Components::UnconvertedModules; use cPstrict; use Elevate::OS (); use Elevate::PkgMgr (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant EXEMPTED_PACKAGES => ( qr/^kernel-/, qr/^acronis/, ); sub post_distro_upgrade ($self) { return unless Elevate::OS::needs_leapp(); $self->run_once('_remove_leapp_packages'); $self->run_once('_warn_about_other_modules_that_did_not_convert'); return; } sub _remove_leapp_packages ($self) { my @leapp_packages = qw{ elevate-release leapp leapp-data-almalinux leapp-data-cloudlinux leapp-deps leapp-repository-deps leapp-upgrade-el7toel8 leapp-upgrade-el7toel8-deps python2-leapp }; INFO('Removing packages provided by leapp'); my @to_remove = grep { Cpanel::Pkgr::is_installed($_) } @leapp_packages; Elevate::PkgMgr::remove(@to_remove); return; } sub _warn_about_other_modules_that_did_not_convert ($self) { my $installed_packages = Elevate::PkgMgr::get_installed_pkgs(); my @el7_installed_packages; foreach my $pkg ( sort keys %$installed_packages ) { if ( $installed_packages->{$pkg} =~ m/el7/ ) { push @el7_installed_packages, "$pkg-$installed_packages->{$pkg}"; } } my @el7_packages_minus_exemptions; foreach my $pkg (@el7_installed_packages) { next if grep { $pkg =~ m/$_/ } EXEMPTED_PACKAGES(); push @el7_packages_minus_exemptions, $pkg; } return unless @el7_packages_minus_exemptions; my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $msg = "The following packages should probably be removed as they will not function on $pretty_distro_name\n\n"; foreach my $pkg (@el7_packages_minus_exemptions) { $msg .= " $pkg\n"; } $msg .= "\nYou can remove these by running: yum -y remove " . join( ' ', @el7_packages_minus_exemptions ) . "\n"; Elevate::Notify::add_final_notification($msg); return; } 1; } # --- END lib/Elevate/Components/UnconvertedModules.pm { # --- BEGIN lib/Elevate/Components/UpdateReleaseUpgrades.pm package Elevate::Components::UpdateReleaseUpgrades; use cPstrict; use Elevate::OS (); use File::Copy (); use File::Slurper (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant BLOCK_UBUNTU_UPGRADES => q[/usr/local/cpanel/install/BlockUbuntuUpgrades.pm]; use constant RELEASE_UPGRADES_FILE => q[/etc/update-manager/release-upgrades]; # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { return unless Elevate::OS::needs_do_release_upgrade(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); INFO("Removing install script that blocks upgrades to $pretty_distro_name"); unlink(BLOCK_UBUNTU_UPGRADES); INFO("Updating config file to allow upgrades to $pretty_distro_name"); my $content = File::Slurper::read_binary(RELEASE_UPGRADES_FILE) // ''; my @lines = split "\n", $content; my $in_default = 0; my $was_updated = 0; foreach my $line (@lines) { if ( $line =~ qr{^\s*\[(\w+)\]}a ) { if ( $1 eq 'DEFAULT' ) { $in_default = 1; } next; } next unless $in_default; return if $line =~ m{^\s*Prompt\s*=\s*lts}a; if ( $line =~ s{^\s*Prompt\s*=\s*(?:normal|never)}{Prompt=lts} ) { $was_updated = 1; last; } } if ($was_updated) { $content = join "\n", @lines; $content .= "\n"; File::Slurper::write_binary( RELEASE_UPGRADES_FILE, $content ); } else { my $upgrade_file = RELEASE_UPGRADES_FILE; my $backup_file = $upgrade_file . '.pre_elevate'; INFO( <<~"EOS" ); Expected line was not found in the config file. Backing up the config file to $backup_file and replacing the contents with the necessary config to ensure that the elevate script can upgrade the server. EOS File::Copy::cp( $upgrade_file, $backup_file ); my $new_content = <<~'EOS'; [DEFAULT] Prompt=lts EOS File::Slurper::write_binary( RELEASE_UPGRADES_FILE, $new_content ); } return; } 1; } # --- END lib/Elevate/Components/UpdateReleaseUpgrades.pm { # --- BEGIN lib/Elevate/Components/UpdateSystem.pm package Elevate::Components::UpdateSystem; use cPstrict; use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { Elevate::PkgMgr::clean_all(); $self->ssystem_and_die(qw{/scripts/update-packages}); if ( Elevate::OS::is_apt_based() ) { INFO('Removing /etc/apt/preferences.d/99-cpanel-exclude-packages'); unlink('/etc/apt/preferences.d/99-cpanel-exclude-packages'); } Elevate::PkgMgr::update(); return; } 1; } # --- END lib/Elevate/Components/UpdateSystem.pm { # --- BEGIN lib/Elevate/Components/WHM.pm package Elevate::Components::WHM; use cPstrict; use Elevate::Constants (); use Elevate::Notify (); use Elevate::OS (); use Cpanel::Backup::Sync (); use Cpanel::Version::Tiny (); use Cpanel::Update::Tiers (); use Cpanel::License (); use Cpanel::Unix::PID::Tiny (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant BACKUP_ID => Cpanel::Backup::Sync::BACKUP_TYPE_NEW; use constant BACKUP_LOGDIR => '/usr/local/cpanel/logs/cpbackup'; use constant UPCP_PIDFILE => '/var/run/upcp.pid'; sub check ($self) { my $ok = 1; $ok = 0 unless $self->_blocker_is_missing_cpanel_whm; $ok = 0 unless $self->_blocker_is_invalid_cpanel_whm; $ok = 0 unless $self->_blocker_is_newer_than_lts; $ok = 0 unless $self->_blocker_cpanel_needs_license; $ok = 0 unless $self->_blocker_cpanel_needs_update; $ok = 0 unless $self->_blocker_is_sandbox; $ok = 0 unless $self->_blocker_is_upcp_running; $ok = 0 unless $self->_blocker_is_cpanel_backup_running; return $ok; } sub _blocker_is_missing_cpanel_whm ($self) { if ( !-x q[/usr/local/cpanel/cpanel] ) { return $self->has_blocker('This script is only designed to work with cPanel & WHM installs. cPanel & WHM do not appear to be present on your system.'); } return 0; } sub _blocker_is_invalid_cpanel_whm ($self) { if ( !$Cpanel::Version::Tiny::major_version ) { return $self->has_blocker('Invalid cPanel & WHM major_version.'); } return 0; } sub _blocker_is_newer_than_lts ($self) { my $major = $Cpanel::Version::Tiny::major_version; if ( $major != Elevate::OS::lts_supported() && $major != Elevate::OS::lts_supported() - 1 ) { my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); return $self->has_blocker( sprintf( "This version %s does not support upgrades to %s. Please ensure the cPanel version is %s.", $Cpanel::Version::Tiny::VERSION_BUILD, $pretty_distro_name, Elevate::OS::lts_supported(), ) ); } return 0; } sub _blocker_cpanel_needs_license ($self) { return 0 if Cpanel::License::is_licensed( skip_max_user_check => 1 ); require Cpanel::DIp::MainIP; require Cpanel::NAT; my $localip = Cpanel::DIp::MainIP::getmainip() // ''; my $publicip = Cpanel::NAT::get_public_ip($localip) // ''; my $ip_msg = ""; if ($publicip) { $ip_msg = <<~EOS; cPanel expects to find a license for the IP address $publicip. Verify whether that IP address is licensed using the following site: https://verify.cpanel.net/ EOS } else { $ip_msg = <<~'EOS'; Additionally, cPanel cannot determine which IP address is being used for licensing. EOS } return $self->has_blocker( <<~EOS ); cPanel does not detect a valid license for itself on the system at this time. This could cause problems during the update. $ip_msg EOS } sub _blocker_cpanel_needs_update ($self) { if ( !$self->getopt('skip-cpanel-version-check') ) { my $lts_supported = Elevate::OS::lts_supported(); my $tiers_obj = Cpanel::Update::Tiers->new( logger => Log::Log4perl->get_logger(__PACKAGE__) ); my $expected_version = $tiers_obj->get_flattened_hash()->{"11.$lts_supported"}; if ( !Cpanel::Version::Compare::compare( $Cpanel::Version::Tiny::VERSION_BUILD, '==', $expected_version ) ) { return $self->has_blocker( <<~"EOS" ); This installation of cPanel ($Cpanel::Version::Tiny::VERSION_BUILD) does not appear to be up to date. Please upgrade cPanel to $expected_version. EOS } } else { Elevate::Notify::warn_skip_version_check(); } return 0; } sub _blocker_is_sandbox ($self) { if ( -e q[/var/cpanel/dev_sandbox] ) { return $self->has_blocker('Cannot elevate a sandbox...'); } return 0; } sub _blocker_is_upcp_running ($self) { return 0 unless $self->getopt('start'); my $upid = Cpanel::Unix::PID::Tiny->new(); my $upcp_pid = $upid->get_pid_from_pidfile(UPCP_PIDFILE); if ($upcp_pid) { $self->components->abort_on_first_blocker(1); return $self->has_blocker( <<~"EOS"); cPanel Update (upcp) is currently running. Please wait for the upcp (PID $upcp_pid) to complete, then try again. You can use the command 'ps --pid $upcp_pid' to check if the process is running. EOS } return 0; } sub _blocker_is_cpanel_backup_running ($self) { return 0 unless $self->getopt('start'); if ( !Cpanel::Backup::Sync::handle_already_running( BACKUP_ID, BACKUP_LOGDIR, Log::Log4perl->get_logger(__PACKAGE__) ) ) { $self->components->abort_on_first_blocker(1); return $self->has_blocker( <<~'EOS'); A cPanel backup is currently running. Please wait for the cPanel backup to complete, then try again. EOS } return 0; } 1; } # --- END lib/Elevate/Components/WHM.pm { # --- BEGIN lib/Elevate/Components/WPToolkit.pm package Elevate::Components::WPToolkit; use cPstrict; use Elevate::Constants (); use Elevate::Fetch (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cwd (); use File::Copy (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { $self->run_once("_remove_wordpress_toolkit"); return; } sub post_distro_upgrade ($self) { $self->run_once('_reinstall_wordpress_toolkit'); return; } sub _remove_wordpress_toolkit ($self) { return unless Cpanel::Pkgr::is_installed('wp-toolkit-cpanel'); INFO("Removing Wordpress Toolkit"); INFO("Removing the rpm wp-toolkit-cpanel"); backup_3rdparty_file('/usr/local/cpanel/3rdparty/wp-toolkit/var/wp-toolkit.sqlite3'); backup_3rdparty_file('/usr/local/cpanel/3rdparty/wp-toolkit/var/etc/.shadow'); Elevate::PkgMgr::remove('wp-toolkit-cpanel'); Elevate::PkgMgr::remove_pkgs_from_repos(qw/wp-toolkit-cpanel wp-toolkit-thirdparties/); Elevate::StageFile::update_stage_file( { 'reinstall' => { 'wordpress_toolkit' => 1 } } ); return; } sub _reinstall_wordpress_toolkit ($self) { return unless Elevate::StageFile::read_stage_file('reinstall')->{'wordpress_toolkit'}; INFO("Restoring Wordpress Toolkit"); my $installer_script = Elevate::Fetch::script( 'https://wp-toolkit.plesk.com/cPanel/installer.sh', 'wptk_installer' ); $self->ssystem( '/usr/bin/bash', $installer_script ); unlink $installer_script; return; } sub backup_3rdparty_file ($file) { my $target = "$file.elevate_backup"; return File::Copy::cp( $file, $target ); } 1; } # --- END lib/Elevate/Components/WPToolkit.pm { # --- BEGIN lib/Elevate/Components/Acronis.pm package Elevate::Components::Acronis; use cPstrict; use Elevate::Constants (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Cpanel::Pkgr (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } sub pre_distro_upgrade ($self) { return unless Cpanel::Pkgr::is_installed(Elevate::Constants::ACRONIS_BACKUP_PACKAGE); Elevate::PkgMgr::remove( Elevate::Constants::ACRONIS_BACKUP_PACKAGE, Elevate::Constants::ACRONIS_OTHER_PACKAGES ); Elevate::StageFile::update_stage_file( { 'reinstall' => { 'acronis' => 1 } } ); return; } sub post_distro_upgrade ($self) { return unless Elevate::StageFile::read_stage_file('reinstall')->{'acronis'}; Elevate::PkgMgr::install(Elevate::Constants::ACRONIS_BACKUP_PACKAGE); return; } 1; } # --- END lib/Elevate/Components/Acronis.pm { # --- BEGIN lib/Elevate/OS.pm package Elevate::OS; use cPstrict; use Carp (); use Elevate::StageFile (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant SUPPORTED_DISTROS => ( 'CentOS 7', 'CloudLinux 7', 'Ubuntu 20', ); our $OS; sub factory { my $distro_with_version = Elevate::StageFile::read_stage_file( 'upgrade_from', '' ); my $distro; my $major; if ( !$distro_with_version ) { $distro = Cpanel::OS::distro(); ## no critic(Cpanel::CpanelOS) $distro = 'CentOS' if $distro eq 'centos'; $distro = 'CloudLinux' if $distro eq 'cloudlinux'; $distro = 'Ubuntu' if $distro eq 'ubuntu'; $major = Cpanel::OS::major(); ## no critic(Cpanel::CpanelOS) $distro_with_version = $distro . $major; } my $class = "Elevate::OS::" . $distro_with_version; my $class_path = "Elevate/OS/$distro_with_version.pm"; require $class_path unless $INC{$class_path}; my $self = bless {}, $class; return $self; } sub instance { return $OS if $OS; $OS = eval { factory(); }; if ( !$OS ) { DEBUG("Unable to acquire Elevate::OS instance, dying") unless -t STDOUT; my $supported_distros = join( "\n", SUPPORTED_DISTROS() ); die "This script is only designed to upgrade the following OSs:\n\n$supported_distros\n"; } Elevate::OS::_set_cache(); return $OS; } my %methods; BEGIN { %methods = map { $_ => 0 } ( 'default_upgrade_to', # This is the default OS that the current OS should upgrade to (i.e. CL7->CL8, C7->A8) 'disable_mysql_yum_repos', # This is a list of mysql repo files to disable 'ea_alias', # This is the value for the --target-os flag used when backing up an EA4 profile 'elevate_rpm_url', # This is the URL used to install the leapp RPM/repo 'expected_post_upgrade_major', # The OS version we expect to upgrade to 'leapp_repo_prod', # This is the repo name for the production repo. 'leapp_repo_beta', # This is the repo name for the beta repo. The OS might not provide a beta repo in which case it'll be blank. 'is_apt_based', # This is used to determine if the OS uses apt as its package manager 'is_experimental', # This is used to determine if upgrades for this OS are experimental 'is_supported', # This is used to determine if the OS is supported or not 'leapp_can_handle_imunify', # This is used to determine if we can skip the Imunify component or not 'leapp_can_handle_kernelcare', # This is used to determine if we can skip the kernelcare component or not 'leapp_data_pkg', # This is used to determine which leapp data package to install 'leapp_flag', # This is used to determine if we need to pass any flags to the leapp script or not 'lts_supported', # This is the major cPanel version supported for this OS 'name', # This is the name of the OS we are upgrading from (i.e. CentOS7, or CloudLinux7) 'needs_do_release_upgrade', # This is used to determine if the OS requires the do-release-upgrade utility to upgrade 'needs_epel', # This is used to determine if the OS requires the epel repo 'needs_leapp', # This is used to determine if the OS requires the leapp utility to upgrade 'needs_powertools', # This is used to determine if the OS requires the powertools repo 'original_os_major', # The initial starting OS major version 'package_manager', # This is the package manager that the OS uses. i.e. RPM 'pretty_name', # This is the pretty name of the OS we are upgrading from (i.e. 'CentOS 7') 'provides_mysql_governor', # This is used to determine if the OS provides the governor-mysql package 'remove_els', # This is used to indicate if we are to remove ELS for this OS 'should_check_cloudlinux_license', # This is used to determine if we should check the cloudlinux license 'skip_minor_version_check', # Used to determine if we need to skip the minor version check for the OS 'supported_cpanel_mysql_versions', # Returns array of supported mysql versions for the OS we are upgrading to 'supported_cpanel_nameserver_types', # Returns array of supported nameserver types 'supports_jetbackup', # This is used to determine if jetbackup is currently supported 'supports_kernelcare', # This is used to determine if kernelcare is currently supported for this upgrade 'supports_postgresql', # This is used to determine if postgresql is supported for this upgrade 'upgrade_to_pretty_name', # Returns the pretty name of the OS we are upgrading to (i.e. 'Ubuntu 22') 'vetted_apt_lists', # This is a list of known apt lists that we do not block on 'vetted_mysql_yum_repo_ids', # This is a list of known mysql yum repo ids 'vetted_yum_repo', # This is a list of known yum repos that we do not block on ); } sub supported_methods { return sort keys %methods; ##no critic qw( ProhibitReturnSort ) - this will always be a list. } our $AUTOLOAD; sub AUTOLOAD { ## no critic(RequireArgUnpacking) - Most of the time we do not need to process args. my $sub = $AUTOLOAD; $sub =~ s/.*:://; exists $methods{$sub} or Carp::croak("$sub is not a supported data variable for Elevate::OS"); my $i = instance(); my $can = $i->can($sub) or Carp::croak( ref($i) . " does not implement $sub" ); return $can->( $i, @_ ); } sub DESTROY { } # This is a must for autoload modules sub clear_cache () { undef $OS unless $INC{'Test/Elevate.pm'}; Elevate::StageFile::remove_from_stage_file('upgrade_from'); return; } sub _set_cache () { Elevate::StageFile::update_stage_file( { upgrade_from => Elevate::OS::name() } ); return; } 1; } # --- END lib/Elevate/OS.pm { # --- BEGIN lib/Elevate/OS/CentOS7.pm package Elevate::OS::CentOS7; use cPstrict; # use Elevate::OS::RHEL(); our @ISA; BEGIN { push @ISA, qw(Elevate::OS::RHEL); } use constant default_upgrade_to => 'AlmaLinux'; use constant ea_alias => 'CentOS_8'; use constant elevate_rpm_url => 'https://repo.almalinux.org/elevate/elevate-release-latest-el7.noarch.rpm'; use constant expected_post_upgrade_major => 8; use constant leapp_data_pkg => 'leapp-data-almalinux'; use constant leapp_repo_prod => 'elevate'; use constant name => 'CentOS7'; use constant original_os_major => 7; use constant pretty_name => 'CentOS 7'; use constant remove_els => 1; use constant upgrade_to_pretty_name => 'AlmaLinux 8'; sub vetted_yum_repo ($self) { my @repos = $self->SUPER::vetted_yum_repo(); push @repos, qr/centos7[-]*els(-rollout-[0-9]+|)/; return @repos; } 1; } # --- END lib/Elevate/OS/CentOS7.pm { # --- BEGIN lib/Elevate/OS/CloudLinux7.pm package Elevate::OS::CloudLinux7; use cPstrict; # use Elevate::OS::RHEL(); our @ISA; BEGIN { push @ISA, qw(Elevate::OS::RHEL); } use constant default_upgrade_to => 'CloudLinux'; use constant ea_alias => 'CloudLinux_8'; use constant elevate_rpm_url => 'https://repo.cloudlinux.com/elevate/elevate-release-latest-el7.noarch.rpm'; use constant expected_post_upgrade_major => 8; use constant leapp_repo_prod => 'elevate'; use constant leapp_repo_beta => 'elevate-updates-testing'; use constant leapp_can_handle_imunify => 1; use constant leapp_can_handle_kernelcare => 1; use constant leapp_data_pkg => 'leapp-data-cloudlinux'; use constant leapp_flag => '--nowarn'; use constant name => 'CloudLinux7'; use constant original_os_major => 7; use constant pretty_name => 'CloudLinux 7'; use constant provides_mysql_governor => 1; use constant should_check_cloudlinux_license => 1; use constant upgrade_to_pretty_name => 'CloudLinux 8'; sub vetted_yum_repo ($self) { my @vetted_cloudlinux_yum_repo = ( qr/^cloudlinux(?:-(?:base|updates|extras|compat|imunify360|elevate))?$/, qr/^cloudlinux-rollout(?:-[0-9]+)?$/, qr/^cloudlinux-ea4(?:-[0-9]+)?$/, qr/^cloudlinux-ea4-rollout(?:-[0-9]+)?$/, 'cl-ea4', qr/^cl-mysql(?:-meta)?/, 'mysqclient', 'mysql-debuginfo', 'cl7h', ); my @repos = $self->SUPER::vetted_yum_repo(); push @repos, @vetted_cloudlinux_yum_repo; return @repos; } 1; } # --- END lib/Elevate/OS/CloudLinux7.pm { # --- BEGIN lib/Elevate/OS/RHEL.pm package Elevate::OS::RHEL; use cPstrict; use constant disable_mysql_yum_repos => qw{ Mysql57.repo Mysql80.repo MariaDB102.repo MariaDB103.repo MariaDB105.repo MariaDB106.repo mysql-community.repo }; use constant vetted_mysql_yum_repo_ids => ( qr/^mysql-cluster-[0-9.]{3}-community(?:-(?:source|debuginfo))?$/, qr/^mysql-connectors-community(?:-(?:source|debuginfo))?$/, qr/^mysql-tools-community(?:-(?:source|debuginfo))?$/, qr/^mysql-tools-preview(?:-source)?$/, qr/^mysql[0-9]{2}-community(?:-(?:source|debuginfo))?$/, qr/^MariaDB[0-9]{3}$/, ); use constant vetted_yum_repo => ( 'base', 'c7-media', qr/^centos-kernel(?:-experimental)?$/, 'centosplus', 'cp-dev-tools', 'cpanel-addons-production-feed', 'cpanel-plugins', 'cr', 'ct-preset', 'digitalocean-agent', 'droplet-agent', qr/^EA4(?:-c\$releasever)?$/, qr/^elasticsearch(?:7\.x)?$/, qr/^elevate(?:-source)?$/, qr/^epel(?:-testing)?$/, 'extras', 'fasttrack', 'imunify360', 'imunify360-ea-php-hardened', qr/^imunify360-rollout-[0-9]+$/, 'influxdb', 'jetapps', qr/^jetapps-(?:stable|beta|edge)$/, 'kernelcare', qr/^ul($|_)/, 'hgdedi', 'updates', 'r1soft', qr/^panopta(?:\.repo)?$/, qr/^fortimonitor(?:\.repo)?$/, qr/^wp-toolkit-(?:cpanel|thirdparties)$/, ), vetted_mysql_yum_repo_ids; use constant supported_cpanel_mysql_versions => qw{ 8.0 10.3 10.4 10.5 10.6 }; use constant supported_cpanel_nameserver_types => qw{ bind disabled powerdns }; use constant default_upgrade_to => undef; use constant ea_alias => undef; use constant elevate_rpm_url => undef; use constant is_apt_based => 0; use constant is_experimental => 0; use constant is_supported => 1; use constant leapp_can_handle_imunify => 0; use constant leapp_can_handle_kernelcare => 0; use constant leapp_data_package => undef; use constant leapp_flag => undef; use constant lts_supported => 110; use constant name => 'RHEL'; use constant needs_do_release_upgrade => 0; use constant needs_epel => 1; use constant needs_leapp => 1; use constant needs_powertools => 1; use constant package_manager => 'YUM'; use constant pretty_name => 'RHEL'; use constant provides_mysql_governor => 0; use constant remove_els => 0; use constant should_check_cloudlinux_license => 0; use constant supports_jetbackup => 1; use constant supports_kernelcare => 1; use constant supports_postgresql => 1; use constant skip_minor_version_check => 0; use constant leapp_repo_beta => ''; # Unavailable by default. use constant upgrade_to_pretty_name => undef; use constant vetted_apt_lists => {}; 1; } # --- END lib/Elevate/OS/RHEL.pm { # --- BEGIN lib/Elevate/OS/Ubuntu.pm package Elevate::OS::Ubuntu; use cPstrict; use constant default_upgrade_to => undef; use constant disable_mysql_yum_repos => undef; use constant ea_alias => undef; use constant elevate_rpm_url => undef; use constant is_apt_based => 1; use constant is_experimental => 0; use constant is_supported => 1; use constant leapp_can_handle_imunify => undef; use constant leapp_can_handle_kernelcare => undef; use constant leapp_data_pkg => undef; use constant leapp_flag => undef; use constant leapp_repo_beta => undef; use constant leapp_repo_prod => undef; use constant lts_supported => 118; use constant name => 'Ubuntu'; use constant needs_do_release_upgrade => 1; use constant needs_epel => 0; use constant needs_leapp => 0; use constant needs_powertools => 0; use constant package_manager => 'APT'; use constant pretty_name => 'Ubuntu'; use constant provides_mysql_governor => 0; use constant remove_els => 0; use constant should_check_cloudlinux_license => 0; use constant skip_minor_version_check => 1; use constant supports_jetbackup => 0; use constant supports_kernelcare => 0; use constant supports_postgresql => 0; use constant upgrade_to_pretty_name => undef; use constant vetted_yum_repo => undef; 1; } # --- END lib/Elevate/OS/Ubuntu.pm { # --- BEGIN lib/Elevate/OS/Ubuntu20.pm package Elevate::OS::Ubuntu20; use cPstrict; # use Elevate::OS::Ubuntu(); our @ISA; BEGIN { push @ISA, qw(Elevate::OS::Ubuntu); } use constant vetted_apt_lists => { 'cpanel-plugins.list' => q{deb mirror://httpupdate.cpanel.net/cpanel-plugins-u22-mirrorlist ./}, 'droplet-agent.list' => q{deb [signed-by=/usr/share/keyrings/droplet-agent-keyring.gpg] https://repos-droplet.digitalocean.com/apt/droplet-agent main main}, 'EA4.list' => q{deb mirror://httpupdate.cpanel.net/ea4-u22-mirrorlist ./}, 'imunify-rollout.list' => q{deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-1/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-2/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-3/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-4/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-5/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-6/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-7/ jammy main deb [arch=amd64] https://download.imunify360.com/ubuntu/22.04/slot-8/ jammy main}, 'imunify360.list' => q{deb [arch=amd64] https://repo.imunify360.cloudlinux.com/imunify360/ubuntu/22.04/ jammy main'}, 'mysql.list' => q{# Use command 'dpkg-reconfigure mysql-apt-config' as root for modifications. deb https://repo.mysql.com/apt/ubuntu/ jammy mysql-apt-config deb https://repo.mysql.com/apt/ubuntu/ jammy mysql-8.0 deb https://repo.mysql.com/apt/ubuntu/ jammy mysql-tools deb-src https://repo.mysql.com/apt/ubuntu/ jammy mysql-8.0}, 'wp-toolkit-cpanel.list' => q{# WP Toolkit deb https://wp-toolkit.plesk.com/cPanel/Ubuntu-22.04-x86_64/latest/wp-toolkit/ ./ deb https://wp-toolkit.plesk.com/cPanel/Ubuntu-22.04-x86_64/latest/thirdparty/ ./}, map { my $thing = $_; "jetapps-$_.list" => "deb [arch=amd64] https://repo.jetlicense.com/ubuntu jammy/$_ main" } qw{base plugins alpha beta edge rc release stable}, }; use constant supported_cpanel_mysql_versions => qw{ 8.0 10.6 10.11 }; use constant supported_cpanel_nameserver_types => qw{ disabled powerdns }; use constant default_upgrade_to => 'Ubuntu'; use constant ea_alias => 'Ubuntu_22.04'; use constant expected_post_upgrade_major => 22; use constant is_experimental => 1; use constant name => 'Ubuntu20'; use constant original_os_major => 20; use constant pretty_name => 'Ubuntu 20.04'; use constant upgrade_to_pretty_name => 'Ubuntu 22.04'; 1; } # --- END lib/Elevate/OS/Ubuntu20.pm { # --- BEGIN lib/Elevate/PkgMgr.pm package Elevate::PkgMgr; use cPstrict; use Elevate::PkgMgr::YUM (); use Elevate::PkgMgr::APT (); our $PKGUTILITY; sub factory { my $pkg = 'Elevate::PkgMgr::' . Elevate::OS::package_manager(); return $pkg->new; } sub instance { $PKGUTILITY //= factory(); return $PKGUTILITY; } sub name () { return instance()->name(); } sub get_config_files_for_pkg_prefix ($prefix) { return instance()->get_config_files_for_pkg_prefix($prefix); } sub get_config_files ($pkgs) { return instance()->get_config_files($pkgs); } sub restore_config_files (@files) { return instance()->restore_config_files(@files); } sub remove_no_dependencies ($pkg) { return instance()->remove_no_dependencies($pkg); } sub remove_no_dependencies_and_justdb ($pkg) { return instance()->remove_no_dependencies_and_justdb($pkg); } sub remove_no_dependencies_or_scripts_and_justdb ($pkg) { return instance()->remove_no_dependencies_or_scripts_and_justdb($pkg); } sub force_upgrade_pkg ($pkg) { return instance()->force_upgrade_pkg($pkg); } sub get_installed_pkgs ( $filter = undef ) { return instance()->get_installed_pkgs($filter); } sub get_cpanel_arch_pkgs () { return instance()->get_cpanel_arch_pkgs(); } sub remove_cpanel_arch_pkgs () { return instance()->remove_cpanel_arch_pkgs(); } sub remove (@pkgs) { return instance()->remove(@pkgs); } sub clean_all () { return instance()->clean_all(); } sub install_pkg_via_url ($rpm_url) { return instance()->install_pkg_via_url($rpm_url); } sub install_with_options ( $options, $pkgs ) { return instance()->install_with_options( $options, $pkgs ); } sub install (@pkgs) { return instance()->install(@pkgs); } sub reinstall (@pkgs) { return instance()->reinstall(@pkgs); } sub repolist_all () { return instance()->repolist_all(); } sub repolist_enabled () { return instance()->repolist_enabled(); } sub repolist (@options) { return instance()->repolist(@options); } sub get_extra_packages () { return instance()->get_extra_packages(); } sub config_manager_enable ($repo) { return instance()->config_manager_enable($repo); } sub update () { return instance()->update(); } sub update_with_options ( $options, $pkgs ) { return instance()->update_with_options( $options, $pkgs ); } sub update_allow_erasing (@additional_args) { return instance()->update_allow_erasing(@additional_args); } sub makecache () { return instance()->makecache(); } sub pkg_list ( $invalidate_cache = 0 ) { return instance()->pkg_list($invalidate_cache); } sub get_installed_pkgs_in_repo (@pkg_list) { return instance()->get_installed_pkgs_in_repo(@pkg_list); } sub remove_pkgs_from_repos (@pkg_list) { return instance()->remove_pkgs_from_repos(@pkg_list); } sub showhold () { return instance()->showhold(); } 1; } # --- END lib/Elevate/PkgMgr.pm { # --- BEGIN lib/Elevate/PkgMgr/APT.pm package Elevate::PkgMgr::APT; use cPstrict; use File::Copy (); use File::Slurper (); use Elevate::OS (); use Cpanel::Pkgr (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::PkgMgr::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::PkgMgr::Base); } use constant APT_NON_INTERACTIVE_ARGS => qw{ -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold }; our $apt_get = '/usr/bin/apt-get'; our $apt_mark = '/usr/bin/apt-mark'; our $dpkg = '/usr/bin/dpkg'; our $dpkg_query = '/usr/bin/dpkg-query'; sub name ($self) { return Elevate::OS::package_manager(); } sub get_config_files ( $self, $pkgs ) { my %config_files; foreach my $pkg (@$pkgs) { my $out = $self->ssystem_capture_output( $dpkg_query, q[--showformat=${Conffiles}\n], '--show', $pkg ); if ( $out->{status} != 0 ) { WARN( <<~"EOS"); Failed to retrieve config files for $pkg. If you have custom config files for this pkg, then you will need to manually evaluate the configs and potentially move the rpmsave file back into place. EOS } else { my $restore_suffix = $self->_get_config_file_suffix(); my @pkg_config_files; foreach my $line ( @{ $out->{stdout} } ) { next if $line =~ m{^\s*$}; $line =~ s/^\s+|\s+$//g; my ( $config_file, $md5sum ) = split qr/\s+/, $line; push @pkg_config_files, $config_file; File::Copy::cp( $config_file, $config_file . $restore_suffix ); } $config_files{$pkg} = \@pkg_config_files; } } return \%config_files; } sub _get_config_file_suffix ($self) { return '.pre_elevate'; } sub remove_no_dependencies_and_justdb ( $self, $pkg ) { return; } sub remove_no_dependencies_or_scripts_and_justdb ( $self, $pkg ) { return; } sub force_upgrade_pkg ( $self, $pkg ) { return $self->update($pkg); } sub remove ( $self, @pkgs ) { return unless scalar @pkgs; my @apt_args = ( '-y', APT_NON_INTERACTIVE_ARGS, ); $self->ssystem_and_die( $apt_get, @apt_args, 'purge', @pkgs ); return; } sub clean_all ($self) { my $out = $self->ssystem_capture_output( $apt_get, 'clean' ); return $out; } sub install_with_options ( $self, $options, $pkgs ) { return unless scalar @$pkgs; return $self->install(@$pkgs); } sub install ( $self, @pkgs ) { return unless scalar @pkgs; my @apt_args = ( '-y', APT_NON_INTERACTIVE_ARGS, ); $self->ssystem_and_die( $apt_get, @apt_args, 'install', @pkgs ); return; } sub reinstall ( $self, @pkgs ) { return unless scalar @pkgs; my @apt_args = ( '-y', APT_NON_INTERACTIVE_ARGS, ); $self->ssystem_and_die( $apt_get, @apt_args, 'reinstall', @pkgs ); return; } sub update ($self) { my @apt_args = ( '-y', '--with-new-pkgs', APT_NON_INTERACTIVE_ARGS, ); $self->ssystem_and_die( $apt_get, @apt_args, 'upgrade' ); return; } sub update_with_options ( $self, $options, $pkgs ) { return $self->update(); } sub update_allow_erasing ( $self, @additional_args ) { $self->ssystem_and_die( $apt_get, '-y', 'autoremove', '--purge' ); $self->update(); return; } sub makecache ($self) { my $out = $self->ssystem_capture_output( $apt_get, 'update' ); my @errors = grep { $_ !~ m/apt does not have a stable CLI interface/ } @{ $out->{stderr} }; my $stderr = join "\n", @errors; return $stderr; } sub showhold ($self) { my $out = $self->ssystem_capture_output( $apt_mark, 'showhold' ); return $out; } sub pkg_list ( $self, $invalidate_cache = 0 ) { return {}; } sub remove_pkgs_from_repos ( $self, @pkg_list ) { return; } my %repo2filepart = ( map { my $dollar_underscore = $_; "jetapps-$dollar_underscore" => "repo.jetlicense.com_ubuntu_dists_jammy_$dollar_underscore" } qw{base plugins alpha beta edge rc release stable}, ); sub get_installed_pkgs_in_repo ( $self, @repos ) { return unless scalar @repos; my @apt_args = (APT_NON_INTERACTIVE_ARGS); $self->ssystem_and_die( $Elevate::PkgMgr::APT::apt_get, @apt_args, 'update' ); my $dir2read = '/var/lib/apt/lists'; opendir( my $dh, $dir2read ) or die "Can't open $dir2read: $!"; my @pkglists = grep { m/_Packages$/ } readdir($dh); closedir($dh); $repos[0] = 'jetapps-base' if $repos[0] eq 'jetapps'; my @installed; foreach my $repo (@repos) { my $filepart = $repo2filepart{$repo}; foreach my $pkglist (@pkglists) { if ( $pkglist =~ qr/^$filepart/ ) { my @lines = File::Slurper::read_lines("$dir2read/$pkglist"); my %pkgs = map { substr( $_, 9 ) => 1 } grep { m/^Package: / } @lines; push @installed, sort grep { Cpanel::Pkgr::is_installed($_) } keys %pkgs; last; } } } return @installed; } 1; } # --- END lib/Elevate/PkgMgr/APT.pm { # --- BEGIN lib/Elevate/PkgMgr/Base.pm package Elevate::PkgMgr::Base; use cPstrict; use File::Copy (); use Cpanel::Pkgr (); # use Elevate::Roles::Run(); our @ISA; BEGIN { push @ISA, qw(Elevate::Roles::Run); } sub new ( $class, $opts = undef ) { $opts //= {}; my $self = {%$opts}; bless $self, $class; return $self; } sub name ($self) { die "name unimplemented"; } sub _pkgmgr ($self) { die "_pkgmgr unimplemented"; } sub get_config_files_for_pkg_prefix ( $self, $prefix ) { my $installed_pkgs = $self->get_installed_pkgs($prefix); my @wanted_pkgs = sort keys %$installed_pkgs; my $config_files = $self->get_config_files( \@wanted_pkgs ); return $config_files; } sub _get_config_file_suffix ($self) { die "_get_config_file_suffix unimplemented"; } sub get_config_files ( $self, $pkgs ) { die "get_config_files unimplemented"; } sub restore_config_files ( $self, @files ) { my $suffix = $self->_get_config_file_suffix(); foreach my $file (@files) { next unless length $file; my $backup_file = $file . $suffix; next unless -e $backup_file; File::Copy::mv( $backup_file, $file ) or WARN("Unable to restore config file $backup_file: $!"); } return; } sub remove_no_dependencies ( $self, $pkg ) { return Cpanel::Pkgr::remove_packages_nodeps($pkg); } sub remove_no_dependencies_and_justdb ( $self, $pkg ) { die "remove_no_dependencies_and_justdb unimplemented"; } sub remove_no_dependencies_or_scripts_and_justdb ( $self, $pkg ) { die "remove_no_dependencies_or_scripts_and_justdb unimplemented"; } sub force_upgrade_pkg ( $self, $pkg ) { die "force_upgrade_pkg unimplemented"; } sub get_installed_pkgs ( $self, $filter = undef ) { return $filter ? Cpanel::Pkgr::installed_packages($filter) : Cpanel::Pkgr::installed_packages(); } sub get_cpanel_arch_pkgs ($self) { my $pkg_info = $self->get_installed_pkgs(); my @installed_pkgs = keys %$pkg_info; my @cpanel_arch_pkgs = grep { $_ =~ m/^cpanel-.*\.x86_64$/ } @installed_pkgs; return @cpanel_arch_pkgs; } sub remove_cpanel_arch_pkgs ($self) { my @pkgs_to_remove = $self->get_cpanel_arch_pkgs(); foreach my $pkg (@pkgs_to_remove) { $self->remove_no_dependencies_and_justdb($pkg); } return; } sub remove ( $self, @pkgs ) { die "remove unimplemented"; } sub clean_all ($self) { die "clean_all unimplemented"; } sub install_pkg_via_url ( $self, $rpm_url ) { die "install_pkg_via_url unimplemented"; } sub install_with_options ( $self, $options, $pkgs ) { die "install_with_options unimplemented"; } sub install ( $self, @pkgs ) { die "install unimplemented"; } sub reinstall ( $self, @pkgs ) { die "reinstall unimplemented"; } sub repolist_all ($self) { die "repolist_all unimplemented"; } sub repolist_enabled ($self) { die "repolist_enabled unimplemented"; } sub repolist ( $self, @options ) { die "repolist unimplemented"; } sub get_extra_packages ($self) { die "get_extra_packages unimplemented"; } sub config_manager_enable ( $self, $repo ) { die "config_manager_enable unimplemented"; } sub update ($self) { die "update unimplemented"; } sub update_with_options ( $self, $options, $pkgs ) { die "update_with_options unimplemented"; } sub update_allow_erasing ( $self, @additional_args ) { die "update_allow_erasing unimplemented"; } sub makecache ($self) { die "makecache unimplemented"; } sub pkg_list ( $self, $invalidate_cache = 0 ) { die "pkg_list unimplemented"; } sub get_installed_pkgs_in_repo ( $self, @pkg_list ) { die "get_installed_pkgs_in_repo unimplemented"; } sub remove_pkgs_from_repos ( $self, @pkg_list ) { die "remove_pkgs_from_repos unimplemented"; } sub showhold ($self) { die "showhold unimplemented"; } 1; } # --- END lib/Elevate/PkgMgr/Base.pm { # --- BEGIN lib/Elevate/PkgMgr/YUM.pm package Elevate::PkgMgr::YUM; use cPstrict; use Cpanel::OS (); use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } # use Elevate::PkgMgr::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::PkgMgr::Base); } our $rpm = '/usr/bin/rpm'; sub name ($self) { return Elevate::OS::package_manager(); } sub _pkgmgr ($self) { return '/usr/bin/' . Cpanel::OS::package_manager(); } sub get_config_files ( $self, $pkgs ) { my %config_files; foreach my $pkg (@$pkgs) { my $out = $self->ssystem_capture_output( $rpm, '-qc', $pkg ) || {}; if ( $out->{status} != 0 ) { WARN( <<~"EOS"); Failed to retrieve config files for $pkg. If you have custom config files for this pkg, then you will need to manually evaluate the configs and potentially move the rpmsave file back into place. EOS $config_files{$pkg} = []; } else { my @pkg_config_files = grep { $_ =~ m{^/} } @{ $out->{stdout} }; $config_files{$pkg} = \@pkg_config_files; } } return \%config_files; } sub _get_config_file_suffix ($self) { return '.rpmsave'; } sub remove_no_dependencies_and_justdb ( $self, $pkg ) { $self->ssystem( $rpm, '-e', '--nodeps', '--justdb', $pkg ); return; } sub remove_no_dependencies_or_scripts_and_justdb ( $self, $pkg ) { $self->ssystem( $rpm, '-e', '--nodeps', '--noscripts', '--justdb', $pkg ); return; } sub force_upgrade_pkg ( $self, $pkg ) { my $err = $self->ssystem( $rpm, '-Uv', '--force', $pkg ); return $err; } sub remove ( $self, @pkgs ) { return unless scalar @pkgs; my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'remove', @pkgs ); return; } sub clean_all ($self) { my $pkgmgr = $self->_pkgmgr(); my $out = $self->ssystem_capture_output( $pkgmgr, 'clean', 'all' ); return $out; } sub install_pkg_via_url ( $self, $pkg_url ) { my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'install', $pkg_url ); return; } sub install_with_options ( $self, $options, $pkgs ) { return unless scalar @$options; return unless scalar @$pkgs; my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'install', @$options, @$pkgs ); return; } sub install ( $self, @pkgs ) { return unless scalar @pkgs; my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'install', @pkgs ); return; } sub reinstall ( $self, @pkgs ) { return unless scalar @pkgs; my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'reinstall', @pkgs ); return; } sub repolist_all ($self) { return $self->repolist("all"); } sub repolist_enabled ($self) { return $self->repolist("enabled"); } sub repolist ( $self, @options ) { my $pkgmgr = $self->_pkgmgr(); my $out = $self->ssystem_hide_and_capture_output( $pkgmgr, '-q', 'repolist', @options ); my @lines = @{ $out->{stdout} }; shift @lines; my @repos; foreach my $line (@lines) { my $repo = ( split( '\s+', $line ) )[0]; push @repos, $repo; } return @repos; } sub get_extra_packages ($self) { my $pkgmgr = $self->_pkgmgr(); my $out = $self->ssystem_hide_and_capture_output( $pkgmgr, 'list', 'extras' ); my @lines = @{ $out->{stdout} }; while ( my $line = shift @lines ) { last if $line && $line =~ m/^Extra Packages/; } my @extra_packages; while ( my $line = shift @lines ) { chomp $line; my ( $package, $version, $repo ) = split( qr{\s+}, $line ); if ( !length $version ) { my $extra_line = shift @lines; chomp $extra_line; $extra_line =~ s/^\s+//; ( $version, $repo ) = split( ' ', $extra_line ); } if ( !length $repo ) { $repo = shift @lines; chomp $repo; $repo =~ s/\s+//g; } length $repo or next; # We screwed up the parse. move on. $repo eq 'installed' or next; $package =~ s/\.(noarch|x86_64)$//; my $arch = $1 // '?'; push @extra_packages, { package => $package, version => $version, arch => $arch }; } return @extra_packages; } sub config_manager_enable ( $self, $repo ) { my $pkgmgr = $self->_pkgmgr(); $self->ssystem( $pkgmgr, 'config-manager', '--enable', $repo ); return; } sub update ($self) { my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'update' ); return; } sub update_with_options ( $self, $options, $pkgs ) { return unless scalar @$options; return unless scalar @$pkgs; my $pkgmgr = $self->_pkgmgr(); $self->ssystem_and_die( $pkgmgr, '-y', 'update', @$options, @$pkgs ); return; } sub update_allow_erasing ( $self, @additional_args ) { my $pkgmgr = $self->_pkgmgr(); my @args = ( '-y', '--allowerasing', ); push @args, @additional_args; $self->ssystem_and_die( $pkgmgr, @args, 'update' ); return; } sub makecache ($self) { my $pkgmgr = $self->_pkgmgr(); my $out = $self->ssystem_capture_output( $pkgmgr, 'makecache' ); my $stderr = join "\n", @{ $out->{stderr} }; return $stderr; } my $pkg_list_cache; sub pkg_list ( $self, $invalidate_cache = 0 ) { return $pkg_list_cache if !$invalidate_cache && $pkg_list_cache; my $pkgmgr = $self->_pkgmgr(); my @lines = split "\n", Cpanel::SafeRun::Errors::saferunnoerror( $pkgmgr, 'list', 'installed' ); while ( my $line = shift @lines ) { last if $line && $line =~ m/^Installed Packages/; } my %repos; while ( my $line = shift @lines ) { chomp $line; my ( $package, $version, $repo ) = split( qr{\s+}, $line ); if ( !length $version ) { my $extra_line = shift @lines; chomp $extra_line; $extra_line =~ s/^\s+//; ( $version, $repo ) = split( ' ', $extra_line ); } if ( !length $repo ) { $repo = shift @lines; chomp $repo; $repo =~ s/\s+//g; } length $repo or next; # We screwed up the parse. move on. $repo =~ s/^\@// or next; $repos{$repo} ||= []; next if $repo eq 'installed'; # Not installed from a repo. $package =~ s/\.(noarch|x86_64)$//; my $arch = $1 // '?'; push $repos{$repo}->@*, { 'package' => $package, 'version' => $version, arch => $arch }; } return $pkg_list_cache = \%repos; } sub get_installed_pkgs_in_repo ( $self, @pkg_list ) { my @installed_pkgs; my $installed = $self->pkg_list(); if ( ref $pkg_list[0] eq 'Regexp' ) { scalar @pkg_list == 1 or Carp::confess("too many args"); my $regex = shift @pkg_list; @pkg_list = grep { $_ =~ $regex } keys %$installed; } foreach my $repo (@pkg_list) { next unless ref $installed->{$repo}; next unless scalar $installed->{$repo}->@*; push @installed_pkgs, map { $_->{'package'} } $installed->{$repo}->@*; } return @installed_pkgs; } sub remove_pkgs_from_repos ( $self, @pkg_list ) { my @to_remove = $self->get_installed_pkgs_in_repo(@pkg_list); return unless @to_remove; INFO( "Removing packages for " . join( ", ", @pkg_list ) ); $self->remove(@to_remove); return; } 1; } # --- END lib/Elevate/PkgMgr/YUM.pm { # --- BEGIN lib/Elevate/Database.pm package Elevate::Database; use cPstrict; use Elevate::OS (); use Elevate::StageFile (); use Cpanel::MysqlUtils::Version (); use Cpanel::MysqlUtils::Versions (); use Cpanel::Pkgr (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant MYSQL_BIN => '/usr/sbin/mysqld'; sub is_database_provided_by_cloudlinux ( $use_cache = 1 ) { if ($use_cache) { my $cloudlinux_database_installed = Elevate::StageFile::read_stage_file( 'cloudlinux_database_installed', '' ); return $cloudlinux_database_installed if length $cloudlinux_database_installed; } if ( !Elevate::OS::provides_mysql_governor() ) { Elevate::StageFile::update_stage_file( { cloudlinux_database_installed => 0 } ); return 0; } my ( $db_type, $db_version ) = Elevate::Database::get_db_info_if_provided_by_cloudlinux(0); return 1 if $db_type && $db_version; return 0; } sub get_db_info_if_provided_by_cloudlinux ( $use_cache = 1 ) { if ($use_cache) { my $cloudlinux_database_info = Elevate::StageFile::read_stage_file( 'cloudlinux_database_info', '' ); return ( $cloudlinux_database_info->{db_type}, $cloudlinux_database_info->{db_version} ) if length $cloudlinux_database_info; } my $pkg = Cpanel::Pkgr::what_provides(MYSQL_BIN); my ( $db_type, $db_version ) = $pkg =~ m/^cl-(mysql|mariadb|percona)([0-9]+)-server$/i; my $cloudlinux_database_installed = ( $db_type && $db_version ) ? 1 : 0; Elevate::StageFile::update_stage_file( { cloudlinux_database_installed => $cloudlinux_database_installed } ); if ($cloudlinux_database_installed) { Elevate::StageFile::update_stage_file( { cloudlinux_database_info => { db_type => lc $db_type, db_version => $db_version, } } ); } return ( $db_type, $db_version ); } sub get_local_database_version () { my $version; eval { local $Cpanel::MysqlUtils::Version::USE_LOCAL_MYSQL = 1; $version = Cpanel::MysqlUtils::Version::uncached_mysqlversion(); }; if ( my $exception = $@ ) { WARN("Error encountered querying the version from the database server: $exception"); my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf(); $version = $cpconf->{'mysql-version'} // ''; } return $version; } sub is_database_version_supported ($version) { return scalar grep { $version eq $_ } Elevate::OS::supported_cpanel_mysql_versions(); } sub get_default_upgrade_version () { require Whostmgr::Mysql::Upgrade; return Whostmgr::Mysql::Upgrade::get_latest_available_version( version => get_local_database_version() ); } sub get_database_type_name_from_version ($version) { return Cpanel::MariaDB::version_is_mariadb($version) ? 'MariaDB' : 'MySQL'; } sub upgrade_database_server () { require Whostmgr::Mysql::Upgrade; my $upgrade_version = Elevate::StageFile::read_stage_file( 'mysql-version', '' ); $upgrade_version ||= Elevate::Database::get_default_upgrade_version(); my $upgrade_dbtype_name = Elevate::Database::get_database_type_name_from_version($upgrade_version); INFO("Beginning upgrade to $upgrade_dbtype_name $upgrade_version"); my $failed_step = Whostmgr::Mysql::Upgrade::unattended_upgrade( { upgrade_type => 'unattended_automatic', selected_version => $upgrade_version, } ); if ($failed_step) { LOGDIE("FAILED to upgrade to $upgrade_dbtype_name $upgrade_version"); } else { INFO("Finished upgrade to $upgrade_dbtype_name $upgrade_version"); } return; } sub pretty_type_name ($type) { $type =~ s/^mysql$/MySQL/i; $type =~ s/^mariadb$/MariaDB/i; $type =~ s/^(?:pgsql|postgres(?:ql)?)$/PostgreSQL/i; $type =~ s/^percona$/Percona/i; # yes, Percona is a valid type; see get_db_info_if_provided_by_cloudlinux return $type; } 1; } # --- END lib/Elevate/Database.pm { # --- BEGIN lib/Elevate/EA4.pm package Elevate::EA4; use cPstrict; use File::Temp (); use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); use Cpanel::Config::Httpd (); use Cpanel::JSON (); use Cpanel::Pkgr (); use Cpanel::SafeRun::Simple (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant IMUNIFY_AGENT => Elevate::Constants::IMUNIFY_AGENT; sub backup ( $check_mode = 0 ) { Elevate::EA4::_backup_ea4_profile($check_mode); Elevate::EA4::_backup_ea_addons(); return; } sub _backup_ea4_profile ($check_mode) { my $use_ea4 = Cpanel::Config::Httpd::is_ea4() ? 1 : 0; Elevate::StageFile::remove_from_stage_file('ea4'); Elevate::StageFile::update_stage_file( { ea4 => { enable => $use_ea4 } } ); unless ($use_ea4) { WARN('Skipping EA4 backup. EA4 does not appear to be enabled on this system'); return; } my $json_path = Elevate::EA4::_get_ea4_profile($check_mode); my $data = { profile => $json_path }; my $profile = eval { Cpanel::JSON::LoadFile($json_path) } // {}; if ( ref $profile->{os_upgrade} && ref $profile->{os_upgrade}->{dropped_pkgs} ) { $data->{dropped_pkgs} = $profile->{os_upgrade}->{dropped_pkgs}; } Elevate::StageFile::update_stage_file( { ea4 => $data } ); return; } sub _imunify360_is_installed_and_provides_hardened_php () { return 0 unless -x IMUNIFY_AGENT; my $out = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{version --json} ); my $license_data = eval { Cpanel::JSON::Load($out) } // {}; return 0 unless ref $license_data->{license}; if ( $license_data->{'license'}->{'license_type'} eq 'imunify360' ) { my $output = Cpanel::SafeRun::Simple::saferunnoerror( IMUNIFY_AGENT, qw{features list} ); my @features = map { my $trim_spaces = $_; $trim_spaces =~ s/\s+//g; $trim_spaces; } grep { m/\S/ } split( "\n", $output ); foreach my $feature (@features) { if ( $feature eq 'hardened-php' ) { my $version = Cpanel::Pkgr::get_package_version('ea-cpanel-tools'); return $version =~ m{cloudlinux} ? 1 : 0; } } } return 0; } sub _get_ea4_profile ($check_mode) { my $ea_alias = Elevate::OS::ea_alias(); $ea_alias = 'CloudLinux_8' if Elevate::EA4::_imunify360_is_installed_and_provides_hardened_php(); my @cmd = ( '/usr/local/bin/ea_current_to_profile', "--target-os=$ea_alias" ); my $profile_file; if ($check_mode) { $profile_file = Elevate::EA4::tmp_dir() . '/ea_profile.json'; push @cmd, "--output=$profile_file"; } my $cmd_str = join( ' ', @cmd ); INFO("Running: $cmd_str"); my $output = Cpanel::SafeRun::Simple::saferunnoerror(@cmd) // ''; die qq[Unable to backup EA4 profile. Failure from $cmd_str] if $?; if ( !$profile_file ) { my @lines = split( "\n", $output ); if ( scalar @lines == 1 ) { $profile_file = $lines[0]; } else { foreach my $l ( reverse @lines ) { next unless $l =~ m{^/.*\.json}; if ( -f $l ) { $profile_file = $l; last; } } } } die "Unable to backup EA4 profile running: $cmd_str" unless length $profile_file && -f $profile_file && -s _; INFO("Backed up EA4 profile to $profile_file"); return $profile_file; } my $tmp_dir; sub tmp_dir () { return $tmp_dir //= File::Temp->newdir(); # auto cleanup on destroy } sub _backup_ea_addons () { if ( Cpanel::Pkgr::is_installed('ea-nginx') ) { Elevate::StageFile::update_stage_file( { ea4 => { nginx => 1 } } ); } else { Elevate::StageFile::update_stage_file( { ea4 => { nginx => 0 } } ); } return; } my $php_get_vhost_versions; sub php_get_vhost_versions () { return $php_get_vhost_versions if defined $php_get_vhost_versions && ref $php_get_vhost_versions eq 'HASH'; my $out = Cpanel::SafeRun::Simple::saferunnoerror(qw{/usr/local/cpanel/bin/whmapi1 --output=json php_get_vhost_versions}); my $result = eval { Cpanel::JSON::Load($out); } // {}; unless ( $result->{metadata}{result} ) { WARN( <<~"EOS" ); The php_get_vhost_versions API call failed. Unable to determine current PHP usage by domain. EOS return; } my $php_get_vhost_versions = $result->{data}{versions}; return $php_get_vhost_versions; } 1; } # --- END lib/Elevate/EA4.pm { # --- BEGIN lib/Elevate/Fetch.pm package Elevate::Fetch; use cPstrict; use Elevate::Constants (); use Cpanel::HTTP::Client (); use File::Temp (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub script ( $url, $template, $suffix = '.sh' ) { my $response = eval { my $http = Cpanel::HTTP::Client->new()->die_on_http_error(); $http->get($url); }; if ( my $exception = $@ ) { ERROR("The system could not fetch the script for $template: $exception"); return; } my $fh = File::Temp->new( TEMPLATE => "${template}_XXXX", SUFFIX => $suffix, UNLINK => 0, PERMS => 0600, TMPDIR => 1 ) or do { ERROR(qq[Cannot create a temporary file]); return; }; print {$fh} $response->{'content'}; close $fh; return "$fh"; } 1; } # --- END lib/Elevate/Fetch.pm { # --- BEGIN lib/Elevate/Leapp.pm package Elevate::Leapp; use cPstrict; use Cpanel::Binaries (); use Cpanel::JSON (); use Cpanel::LoadFile (); use Cpanel::Pkgr (); use Elevate::OS (); use Elevate::PkgMgr (); use Elevate::StageFile (); use Config::Tiny (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant LEAPP_REPORT_JSON => q[/var/log/leapp/leapp-report.json]; use constant LEAPP_REPORT_TXT => q[/var/log/leapp/leapp-report.txt]; use constant LEAPP_UPGRADE_LOG => q[/var/log/leapp/leapp-upgrade.log]; use constant LEAPP_FAIL_CONT_FILE => q[/var/cpanel/elevate_leap_fail_continue]; use constant LEAPP_COMPLETION_STRING => qr/Starting stage After of phase FirstBoot/; our $time_to_wait_for_leapp_completion = 10 * 60; use Simple::Accessor qw{ cpev }; sub _build_cpev { die q[Missing cpev]; } sub install ($self) { unless ( Cpanel::Pkgr::is_installed('elevate-release') ) { my $elevate_rpm_url = Elevate::OS::elevate_rpm_url(); Elevate::PkgMgr::install_pkg_via_url($elevate_rpm_url); $self->beta_if_enabled; # If --leappbeta was passed, then enable it. } my $leapp_data_pkg = Elevate::OS::leapp_data_pkg(); unless ( Cpanel::Pkgr::is_installed('leapp-upgrade') && Cpanel::Pkgr::is_installed($leapp_data_pkg) ) { Elevate::PkgMgr::install( 'leapp-upgrade', $leapp_data_pkg ); } return; } sub beta_if_enabled ($self) { return unless $self->cpev->getopt('leappbeta'); my $yum_config = Cpanel::Binaries::path('yum-config-manager'); $self->cpev->ssystem_and_die( $yum_config, '--disable', Elevate::OS::leapp_repo_prod() ); $self->cpev->ssystem_and_die( $yum_config, '--enable', Elevate::OS::leapp_repo_beta() ); return 1; } sub remove_kernel_devel ($self) { if ( Cpanel::Pkgr::is_installed('kernel-devel') ) { Elevate::PkgMgr::remove('kernel-devel'); } return; } sub preupgrade ($self) { INFO("Running leapp preupgrade checks"); my $out = $self->cpev->ssystem_hide_and_capture_output( '/usr/bin/leapp', 'preupgrade' ); INFO("Finished running leapp preupgrade checks"); return $out; } sub upgrade ($self) { return unless $self->cpev->upgrade_distro_manually(); $self->cpev->run_once( setup_answer_file => sub { $self->setup_answer_file(); }, ); my $leapp_flag = Elevate::OS::leapp_flag(); my $leapp_bin = '/usr/bin/leapp'; my @leapp_args = ('upgrade'); push( @leapp_args, $leapp_flag ) if $leapp_flag; INFO("Running leapp upgrade"); my $ok = eval { local $ENV{LEAPP_OVL_SIZE} = Elevate::StageFile::read_stage_file('env')->{'LEAPP_OVL_SIZE'} || 3000; $self->cpev->ssystem_and_die( { keep_env => 1 }, $leapp_bin, @leapp_args ); 1; }; return 1 if $ok; $self->_report_leapp_failure_and_die(); return; } sub search_report_file_for_inhibitors ( $self, @ignored_blockers ) { my @blockers; my $leapp_json_report = LEAPP_REPORT_JSON; if ( !-e $leapp_json_report ) { ERROR("Leapp did not generate the expected report file: $leapp_json_report"); return [ { title => "Leapp did not generate $leapp_json_report", summary => "It is expected that leapp generate a JSON report file", } ]; } my $report = eval { Cpanel::JSON::LoadFile($leapp_json_report) } // {}; if ( my $exception = $@ ) { ERROR("Unable to parse leapp report file ($leapp_json_report): $exception"); return [ { title => "Unable to parse leapp report file $leapp_json_report", summary => "The JSON report file generated by LEAPP is unreadable or corrupt", } ]; } my $entries = $report->{entries}; return [] unless ( ref $entries eq 'ARRAY' ); foreach my $entry (@$entries) { next unless ( ref $entry eq 'HASH' ); my $flags = $entry->{flags}; next unless ( ref $flags eq 'ARRAY' ); next unless scalar grep { $_ eq 'inhibitor' } @$flags; next if scalar grep { $_ eq $entry->{actor} } @ignored_blockers; my $blocker = { title => $entry->{title}, summary => $entry->{summary}, }; if ( ref $entry->{detail}{remediations} eq 'ARRAY' ) { foreach my $rem ( @{ $entry->{detail}{remediations} } ) { next unless ( $rem->{type} && $rem->{context} ); if ( $rem->{type} eq 'hint' ) { $blocker->{hint} = $rem->{context}; } if ( $rem->{type} eq 'command' && ref $rem->{context} eq 'ARRAY' ) { $blocker->{command} = join ' ', @{ $rem->{context} }; } } } push @blockers, $blocker; } return \@blockers; } sub extract_error_block_from_output ( $self, $text_ar ) { my $error_block = ''; my $found_banner_line = 0; my $found_second_banner_line = 0; my $in_error_block = 0; foreach my $line (@$text_ar) { if ( !$found_banner_line ) { $found_banner_line = 1 if $line =~ /^={10}/; next; } if ( !$in_error_block ) { if ( $line =~ /^\s+ERRORS/ ) { $in_error_block = 1; } else { $found_banner_line = 0; } next; } if ( !$found_second_banner_line ) { $found_second_banner_line = 1 if $line =~ /^={10}/; next; } last if $line =~ /^={10}/; $error_block .= $line . "\n"; } return $error_block; } sub wait_for_leapp_completion ($self) { return 1 unless Elevate::OS::needs_leapp(); return 1 unless $self->cpev->upgrade_distro_manually(); my $upgrade_log = LEAPP_UPGRADE_LOG; if ( !-e $upgrade_log ) { ERROR("No LEAPP upgrade log detected at [$upgrade_log]"); return 0; } my $elapsed = 0; my $sleep_time = 3; # Time to sleep, in seconds, between checks while ( $elapsed < $time_to_wait_for_leapp_completion ) { my $contents = Cpanel::LoadFile::loadfile(LEAPP_UPGRADE_LOG) // ''; return 1 if $contents =~ LEAPP_COMPLETION_STRING; if ( -e LEAPP_FAIL_CONT_FILE ) { INFO( 'LEAPP does not appear to have completed successfully, but continuing anyway because [' . LEAPP_FAIL_CONT_FILE . '] exists' ); return 2; } if ( $elapsed % 30 == 0 ) { INFO('Waiting for the LEAPP upgrade process to complete.'); if ( $elapsed == 0 ) { # First loop give extra advice INFO( 'This process may take up to ' . int( $time_to_wait_for_leapp_completion / 60 ) . ' minutes.' ); INFO( 'You can `touch ' . LEAPP_FAIL_CONT_FILE . '` if you would like to skip this check.' ); } } sleep $sleep_time; $elapsed += $sleep_time; } my $touch_file = LEAPP_FAIL_CONT_FILE; my $leapp_txt = LEAPP_REPORT_TXT; my $msg = <<~"END"; The command 'leapp upgrade' did not complete successfully. Please investigate and resolve the issue. Once resolved, you can continue the upgrade by running the following commands: touch $touch_file /scripts/elevate-cpanel --continue The following log files may help in your investigation. $upgrade_log $leapp_txt END ERROR($msg); return 0; } sub _report_leapp_failure_and_die ($self) { my $msg = <<'EOS'; The 'leapp upgrade' process failed. Please investigate, resolve then re-run the following command to continue the update: /scripts/elevate-cpanel --continue EOS my $entries = $self->search_report_file_for_inhibitors; if ( ref $entries eq 'ARRAY' ) { foreach my $e (@$entries) { $msg .= "\n$e->{summary}" if $e->{summary}; if ( $e->{command} ) { my $cmd = $e->{command}; $cmd =~ s[^leapp][/usr/bin/leapp]; $msg .= "\n\n"; $msg .= <<"EOS"; Consider running this command: $cmd EOS } } } if ( -e LEAPP_REPORT_TXT ) { $msg .= qq[\nYou can read the full leapp report at: ] . LEAPP_REPORT_TXT; } die qq[$msg\n]; } sub setup_answer_file ($self) { my $leapp_dir = '/var/log/leapp'; mkdir $leapp_dir unless -d $leapp_dir; my $answerfile_path = $leapp_dir . '/answerfile'; system touch => $answerfile_path unless -e $answerfile_path; my $do_write; # no point in overwriting the file if nothing needs to change my $ini_obj = Config::Tiny->read( $answerfile_path, 'utf8' ); LOGDIE( 'Failed to read leapp answerfile: ' . Config::Tiny->errstr ) unless $ini_obj; my $SECTION = 'remove_pam_pkcs11_module_check'; if ( not defined $ini_obj->{$SECTION}->{'confirm'} or $ini_obj->{$SECTION}->{'confirm'} ne 'True' ) { $do_write = 1; $ini_obj->{$SECTION}->{'confirm'} = 'True'; } if ($do_write) { $ini_obj->write( $answerfile_path, 'utf8' ) # or LOGDIE( 'Failed to write leapp answerfile: ' . $ini_obj->errstr ); } return; } 1; } # --- END lib/Elevate/Leapp.pm { # --- BEGIN lib/Elevate/Logger.pm package Elevate::Logger; use cPstrict; use Elevate::Constants (); use Term::ANSIColor (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } my %colors = ( TRACE => 'cyan', DEBUG => 'bold white', INFO => 'green', WARN => 'yellow', ERROR => 'red', FATAL => 'bold red', ); sub init ( $self, $debug_level = 'DEBUG' ) { my $log_file = Elevate::Constants::LOG_FILE; my $config = <<~"EOF"; log4perl.appender.File=Log::Log4perl::Appender::File log4perl.appender.File.filename=$log_file log4perl.appender.File.syswrite=1 log4perl.appender.File.utf8=1 log4perl.appender.File.layout=Log::Log4perl::Layout::PatternLayout log4perl.appender.File.layout.ConversionPattern=* %d{yyyy-MM-dd HH:mm:ss} (%L) [%s%p%u] %m%n EOF if ( $self->getopt('service') ) { $config .= <<~"EOF"; log4perl.rootLogger = $debug_level, File EOF } else { $config .= <<~"EOF"; log4perl.appender.Screen=Log::Log4perl::Appender::Screen log4perl.appender.Screen.stderr=0 log4perl.appender.screen.utf8=1 log4perl.appender.Screen.layout=Log::Log4perl::Layout::PatternLayout log4perl.appender.Screen.layout.ConversionPattern=* %d{yyyy-MM-dd HH:mm:ss} [%s%p%u] %m{indent=2,chomp}%n log4perl.rootLogger = $debug_level, Screen, File EOF } Log::Log4perl::Layout::PatternLayout::add_global_cspec( 's' => sub { Term::ANSIColor::color( $colors{ $_[3] } ) } ); Log::Log4perl::Layout::PatternLayout::add_global_cspec( 'u' => sub { Term::ANSIColor::color('reset') } ); Log::Log4perl->init( \$config ); return; } sub INFO_nolog ($msg) { return _nolog( $msg, 'INFO' ); } sub ERROR_nolog ($msg) { return _nolog( $msg, 'ERROR' ); } sub WARN_nolog ($msg) { return _nolog( $msg, 'WARN' ); } sub _nolog ( $msg, $type = 'DEBUG' ) { return unless length $msg; print '# -------------------------> ['; print Term::ANSIColor::color( $colors{$type} ); print $type; print Term::ANSIColor::color('reset'); print '] '; print Term::ANSIColor::color('bold white'); say $msg; print Term::ANSIColor::color('reset'); return; } 1; } # --- END lib/Elevate/Logger.pm { # --- BEGIN lib/Elevate/Marker.pm package Elevate::Marker; use cPstrict; use POSIX (); use Cpanel::LoadFile (); use Cpanel::MD5 (); use Cpanel::Version::Tiny (); use Elevate::StageFile (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub startup ( $sum = undef ) { $sum //= Cpanel::MD5::getmd5sum($0); chomp($sum); _write_debug_line($sum); Elevate::StageFile::update_stage_file( { '_elevate_process' => { script_md5 => $sum, cpanel_build => $Cpanel::Version::Tiny::VERSION_BUILD, started_at => _bq_now(), redhat_release_pre => _read_redhat_release(), elevate_version_start => cpev::VERSION(), } } ); return; } sub success () { Elevate::StageFile::update_stage_file( { '_elevate_process' => { finished_at => _bq_now(), redhat_release_post => _read_redhat_release(), elevate_version_finish => cpev::VERSION(), } } ); return; } sub _bq_now () { return POSIX::strftime( '%FT%T', gmtime ); } sub _read_redhat_release() { my ($first_line) = split( "\n", Cpanel::LoadFile::loadfile('/etc/redhat-release') // '' ); return $first_line; } sub _write_debug_line ($sum) { DEBUG( sprintf( "Running $0 (%s/%s)", -s $0, $sum ) ); return; } 1; } # --- END lib/Elevate/Marker.pm { # --- BEGIN lib/Elevate/Motd.pm package Elevate::Motd; use cPstrict; sub setup { my $f = _motd_file(); my $notice = _motd_notice_message(); local $/; my $fh; my $content = ''; if ( open( $fh, '+<', $f ) ) { $content = <$fh> // ''; } elsif ( open( $fh, '>', $f ) ) { 1; } return 0 if $content =~ qr{elevate in progress}mi; print {$fh} "\n" if length($content) && $content !~ qr{\n\z}; print {$fh} $notice; return 1; } sub cleanup { my $f = _motd_file(); my $content; open( my $fh, '+<', $f ) or return; { local $/; $content = <$fh>; } return 0 unless $content && $content =~ qr{elevate in progress}mi; my $notice = _motd_notice_message(); if ( $content =~ s{\Q$notice\E}{} ) { seek( $fh, 0, 0 ); print {$fh} $content; truncate( $fh, tell($fh) ); close($fh); return 1; } return; } sub _motd_notice_message { return "# -----------------------------------------------------------------------------\n#\n" . "# /!\\ ELEVATE IN PROGRESS /!\\ \n#\n" . "# Do not make any changes until it's complete\n" . "# you can check the current process status by running:\n#\n" . "#\t\t/scripts/elevate-cpanel --status\n#\n" . "# Or monitor the progress by running:\n#\n" . "#\t\t/scripts/elevate-cpanel --log\n#\n" . "# -----------------------------------------------------------------------------\n"; } sub _motd_file { # allow us to mock it, we cannot use Test::MockFile GH #77 - https://github.com/cpanel/Test-MockFile/issues/77 return q[/etc/motd]; } 1; } # --- END lib/Elevate/Motd.pm { # --- BEGIN lib/Elevate/NICs.pm package Elevate::NICs; use cPstrict; use Elevate::Constants (); use Cpanel::SafeRun::Errors (); sub get_nics () { my $sbin_ip = Elevate::Constants::SBIN_IP(); my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( $sbin_ip, 'addr' ) // ''; my @eths; foreach my $line ( split /\n/xms, $ip_info ) { $line =~ /^[0-9]+: \s (eth[0-9]):/xms or next; my $eth = $1; my $value = readlink "/sys/class/net/$eth" or next; $value =~ m{/virtual/}xms and next; push @eths, $eth; } return @eths; } 1; } # --- END lib/Elevate/NICs.pm { # --- BEGIN lib/Elevate/Notify.pm package Elevate::Notify; use cPstrict; use Elevate::StageFile (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } sub warn_skip_version_check { WARN("The --skip-cpanel-version-check option was specified! This option is provided for testing purposes only! cPanel may not be able to support the resulting conversion. Please consider whether this is what you want."); return; } sub add_final_notification ( $msg, $warn_now = 0 ) { my $stage_info = Elevate::StageFile::read_stage_file(); return unless defined $msg && length $msg; Elevate::StageFile::update_stage_file( { final_notifications => [$msg] } ); # stacked to the previously stored if ($warn_now) { foreach my $l ( split( "\n", $msg ) ) { next unless length $l; WARN($l); } } return 1; } sub send_notification ( $subject, $msg, %opts ) { eval { _send_notification( $subject, $msg, %opts ); 1; } or warn "Failed to send notification: $@"; return; } sub _send_notification ( $subject, $msg, %opts ) { my $is_success = delete $opts{is_success}; require Cpanel::iContact; INFO("Sending notification: $subject"); my $log = $is_success ? \&INFO : \&ERROR; my @lines = split( "\n", $msg ); foreach my $line (@lines) { $log->($line); } Cpanel::iContact::icontact( 'application' => 'elevate', 'subject' => $subject, 'message' => $msg, ); return; } 1; } # --- END lib/Elevate/Notify.pm { # --- BEGIN lib/Elevate/Roles/Run.pm package Elevate::Roles::Run; use cPstrict; use Cpanel::IOCallbackWriteLine (); use Cpanel::SafeRun::Object (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Carp (); sub ssystem_capture_output ( $, @args ) { my %opts; if ( ref $args[0] ) { my $ropts = shift @args; %opts = %$ropts; } $opts{should_capture_output} = 1; return _ssystem( \@args, %opts ); } sub ssystem_hide_and_capture_output ( $, @args ) { my %opts; if ( ref $args[0] ) { my $ropts = shift @args; %opts = %$ropts; } $opts{should_hide_output} = 1; $opts{should_capture_output} = 1; return _ssystem( \@args, %opts ); } sub ssystem ( $, @args ) { my %opts; if ( ref $args[0] ) { my $ropts = shift @args; %opts = %$ropts; } return _ssystem( \@args, %opts ); } sub ssystem_and_die ( $self, @args ) { $self->ssystem(@args) or return 0; my $cmd = join ' ', @args; if ( $args[0] =~ m/yum|dnf|apt|rpm|dpkg/ ) { WARN("Initial attempt to execute '$cmd' failed. Attempting again"); $self->sleep(); $self->ssystem(@args) or return 0; } Carp::croak("‘$cmd’ failed. Review and fix the error, then try again with ‘/scripts/elevate-cpanel --continue’"); } sub sleep ($self) { sleep 15; return; } sub _ssystem ( $command, %opts ) { my @args = @{ $command // [] }; Carp::croak("Program '$args[0]' is not an executable absolute path.") if $args[0] !~ '^/' || !-x $args[0]; INFO( "Running: " . join( " ", @args ) ); INFO(); # Buffer so they can more easily read the output. if ( scalar @args == 1 && $args[0] =~ m/[\$&*(){}\[\]'";\\|?<>~`\n]/ ) { unshift @args, qw(/usr/bin/bash -c); } my $capture_output = { stdout => [], stderr => [] }; my $program = shift @args; my ( $callback_out, $callback_err ) = map { my $label = $_; Cpanel::IOCallbackWriteLine->new( sub ($line) { chomp $line; INFO($line) unless $opts{should_hide_output}; if ( $opts{should_capture_output} ) { push $capture_output->{$label}->@*, $line; } return; } ) } qw(stdout stderr); my $sr = Cpanel::SafeRun::Object->new( program => $program, args => [@args], stdout => $callback_out, stderr => $callback_err, timeout => 0, keep_env => $opts{keep_env} // 0, read_timeout => 0, ); INFO() unless $opts{should_hide_output}; # Buffer so they can more easily read the output. $? = $sr->CHILD_ERROR; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- emulate return behavior of system() if ( $opts{should_capture_output} ) { $capture_output->{status} = $?; return $capture_output; } return $?; } 1; } # --- END lib/Elevate/Roles/Run.pm { # --- BEGIN lib/Elevate/Script.pm package Elevate::Script; use cPstrict; use Elevate::Constants (); use Elevate::Fetch (); use Cpanel::HTTP::Client (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Simple::Accessor qw{ latest_version base_url }; use constant DEFAULT_ELEVATE_BASE_URL => 'https://raw.githubusercontent.com/cpanel/elevate/release/'; sub _build_base_url ($self) { return $ENV{'ELEVATE_BASE_URL'} || DEFAULT_ELEVATE_BASE_URL; } sub _build_latest_version ($self) { my $response = Cpanel::HTTP::Client->new->get( $self->base_url() . 'version' ); return undef if !$response->success; my $version = $response->content // ''; chomp $version if length $version; return $version; } sub is_out_of_date ($self) { my ( $should_block, $message ); my ( $latest_version, $self_version ) = ( $self->latest_version(), cpev::VERSION() ); if ( !defined $latest_version ) { $should_block = 1; $message = "The script could not fetch information about the latest version."; } elsif ( $self_version > $latest_version ) { $message = qq[You are using a development version of elevate-cpanel. Latest version available is v$latest_version.]; } elsif ( $self_version < $latest_version ) { $should_block = 1; $message = <<~EOS; This script (version $self_version) does not appear to be the newest available release ($latest_version). Run this script with the --update option: /scripts/elevate-cpanel --update EOS } return ( $should_block, $message ); } sub fetch ($self) { return Elevate::Fetch::script( $self->base_url . 'elevate-cpanel', 'elevate-cpanel', '' ); } 1; } # --- END lib/Elevate/Script.pm { # --- BEGIN lib/Elevate/Service.pm package Elevate::Service; use cPstrict; use Cpanel::SafeRun::Simple (); use Cpanel::RestartSrv::Systemd (); use Elevate::Constants (); use Elevate::OS (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Elevate::Roles::Run (); # for fatpck use Elevate::Stages (); use Simple::Accessor qw{ name file short_name cpev }; # use Elevate::Roles::Run(); our @ISA; BEGIN { push @ISA, qw(Elevate::Roles::Run); } # use Elevate::SystemctlService(); BEGIN { push @ISA, qw(Elevate::SystemctlService); } sub _build_name { return Elevate::Constants::SERVICE_NAME; } sub _build_file ($self) { return Elevate::Constants::SERVICE_DIR . '/' . $self->name; } sub _build_short_name ($self) { my $s = $self->name; $s =~ s{\Q.service\E$}{}; return $s; } sub _build_cpev { die q[Missing cpev]; } sub install ($self) { my $upgrade_from = Elevate::OS::pretty_name(); my $upgrade_to = Elevate::OS::upgrade_to_pretty_name(); my $name = $self->name; INFO( "Installing service $name which will upgrade the server to " . $upgrade_to ); open( my $fh, '>', $self->file ) or die; print {$fh} <<~"EOF"; [Unit] Description=Upgrade process from $upgrade_from to $upgrade_to. After=network.target network-online.target [Service] Type=simple RemainAfterExit=yes ExecStart=/usr/local/cpanel/scripts/elevate-cpanel --service Environment="LANG=C" Environment="LANGUAGE=C" Environment="LC_ALL=C" Environment="LC_MESSAGES=C" Environment="LC_CTYPE=C" [Install] WantedBy=multi-user.target EOF close $fh; $self->ssystem_and_die( '/usr/bin/systemctl', 'daemon-reload' ); $self->ssystem_and_die( '/usr/bin/systemctl', 'enable', $name ); Elevate::Stages::bump_stage(); my $pid = fork(); die qq[Failed to fork: $!] unless defined $pid; if ($pid) { INFO("Starting service $name"); return 0; } else { unlink(Elevate::Constants::PID_FILE); # release the pid so the service can use it $self->ssystem_and_die( '/usr/bin/systemctl', 'start', $name ); exit(0); } } sub disable ($self) { $self->SUPER::disable( 'now' => 0 ); unlink $self->file; return; } 1; } # --- END lib/Elevate/Service.pm { # --- BEGIN lib/Elevate/StageFile.pm package Elevate::StageFile; use cPstrict; use Cpanel::JSON (); use File::Copy (); use Hash::Merge (); use constant ELEVATE_STAGE_FILE => '/var/cpanel/elevate'; use constant ELEVATE_SUCCESS_FILE => '/var/cpanel/version/elevate'; sub create_success_file () { File::Copy::cp( ELEVATE_STAGE_FILE, ELEVATE_SUCCESS_FILE ); return; } sub read_stage_file ( $k = undef, $default = {} ) { my $stage_info = Elevate::StageFile::_read_stage_file() // {}; return $stage_info->{$k} // $default if defined $k; return $stage_info; } sub remove_from_stage_file ($key) { return unless length $key; my $stage = Elevate::StageFile::read_stage_file(); my @list = split( qr/\./, $key ); return unless scalar @list; my $to_delete = pop @list; my $h = $stage; while ( my $k = shift @list ) { $h = $h->{$k}; last unless ref $h; } return if scalar @list; return unless exists $h->{$to_delete}; delete $h->{$to_delete}; return Elevate::StageFile::_save_stage_file($stage); } sub remove_stage_file () { unlink ELEVATE_STAGE_FILE; return; } sub update_stage_file ($data) { die q[Need a hash] unless ref $data eq 'HASH'; my $current = Elevate::StageFile::read_stage_file(); my $merged = Hash::Merge::merge( $data, $current ); return Elevate::StageFile::_save_stage_file($merged); } sub _read_stage_file () { return eval { Cpanel::JSON::LoadFile(ELEVATE_STAGE_FILE) }; } sub _save_stage_file ($stash) { open( my $fh, '>', ELEVATE_STAGE_FILE ) or LOGDIE( "Failed to open " . ELEVATE_STAGE_FILE . ": $!" ); print {$fh} Cpanel::JSON::pretty_canonical_dump($stash); close $fh; return 1; } 1; } # --- END lib/Elevate/StageFile.pm { # --- BEGIN lib/Elevate/Stages.pm package Elevate::Stages; use cPstrict; sub bump_stage ( $by = 1 ) { return Elevate::Stages::update_stage_number( Elevate::Stages::get_stage() + $by ); } sub get_stage { return Elevate::StageFile::read_stage_file( 'stage_number', 0 ); } sub update_stage_number ($stage_id) { if ( $stage_id > 10 ) { # protection for stage require Carp; Carp::confess("Invalid stage number $stage_id"); } Elevate::StageFile::update_stage_file( { stage_number => $stage_id } ); return $stage_id; } 1; } # --- END lib/Elevate/Stages.pm { # --- BEGIN lib/Elevate/SystemctlService.pm package Elevate::SystemctlService; use cPstrict; use Cpanel::SafeRun::Simple (); use Cpanel::RestartSrv::Systemd (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use Elevate::Roles::Run (); # for fatpck use Simple::Accessor qw{ name }; # use Elevate::Roles::Run(); our @ISA; BEGIN { push @ISA, qw(Elevate::Roles::Run); } sub _build_name { die q[Missing sevice name ] . __PACKAGE__; } sub is_active ($self) { my $service = $self->name; my $is_active; Cpanel::SafeRun::Simple::saferunnoerror( qw{/usr/bin/systemctl is-active}, $service ); $is_active = 1 if $? == 0; my $info = Cpanel::RestartSrv::Systemd::get_service_info_via_systemd($service); $info->{'ActiveState'} //= ''; $info->{'SubState'} //= ''; $is_active = 1 if $info->{'ActiveState'} eq 'activating' && $info->{'SubState'} eq 'start'; if ( $is_active && $info->{'SubState'} ne 'exited' ) { return 1; } return 0; } sub is_enabled ($self) { my $service = $self->name; my $out = Cpanel::SafeRun::Simple::saferunnoerror( qw{/usr/bin/systemctl is-enabled}, $service ) // ''; chomp $out; return 1 if $out eq 'enabled'; return 0; } sub restart ($self) { return $self->ssystem( qw{/usr/bin/systemctl restart}, $self->name ); } sub remove ($self) { my $info = eval { Cpanel::RestartSrv::Systemd::get_service_info_via_systemd( $self->name ) } // {}; $self->stop; $self->disable; if ( my $path = $info->{FragmentPath} ) { unlink $path; } return; } sub start ($self) { return if $self->is_active; $self->ssystem( '/usr/bin/systemctl', 'start', $self->name ); return; } sub stop ($self) { return unless $self->is_active; $self->ssystem( '/usr/bin/systemctl', 'stop', $self->name ); return; } sub disable ( $self, %opts ) { return unless $self->is_enabled; my $now = $opts{'now'} // 1; # by default disable it now... my @args = qw{ disable }; push @args, '--now' if $now; $self->ssystem( '/usr/bin/systemctl', @args, $self->name ); return; } sub enable ( $self, %opts ) { return if $self->is_enabled; my $now = $opts{'now'} // 1; # by default enable it now... my @args = qw{ enable }; push @args, '--now' if $now; $self->ssystem( '/usr/bin/systemctl', @args, $self->name ); return; } 1; } # --- END lib/Elevate/SystemctlService.pm { # --- BEGIN lib/Elevate/Usage.pm package Elevate::Usage; use cPstrict; use Getopt::Long (); sub _OPTIONS { return qw( help service start clean continue manual-reboots status log check:s skip-cpanel-version-check skip-elevate-version-check update version upgrade-distro-manually no-leapp non-interactive leappbeta ); } sub _validate_option_combos ($self) { my @solitary_options = qw( clean continue help log service status update version ); my @start_only_options = qw( manual-reboots non-interactive leappbeta ); return 1 if !%{ $self->{_getopt} }; foreach my $option (@solitary_options) { if ( $self->getopt($option) ) { if ( scalar( keys %{ $self->{_getopt} } ) > 1 ) { return $self->help( "Option \"$option\" is not compatible with any other option", 1 ); } else { return 1; } } } if ( $self->getopt('start') && defined $self->getopt('check') ) { return $self->help( "The options \"start\" and \"check\" are mutually exclusive", 1 ); } if ( !$self->getopt('start') && !defined $self->getopt('check') ) { return $self->help( "Invalid option combination", 1 ); } if ( !$self->getopt('start') ) { foreach my $option (@start_only_options) { if ( $self->getopt($option) ) { return $self->help( "Option \"$option\" is only compatible with \"start\"", 1 ); } } } return 1; } sub init ( $self, @args ) { $self->{_getopt} = {}; Getopt::Long::GetOptionsFromArray( \@args, $self->{_getopt}, _OPTIONS() ) or return $self->help( "Invalid Option", 1 ); return unless $self->_validate_option_combos(); return $self->full_help() if $self->getopt('help'); return; } sub getopt ( $self, $k ) { return $self->{_getopt}->{$k}; } sub help ( $self, $msg = undef, $exit_status = undef ) { say $self->_help($msg); exit( $exit_status // 0 ); ## no critic(Cpanel::NoExitsFromSubroutines) } sub full_help ( $self, $msg = undef, $exit_status = undef ) { my $out = $self->_help( $msg, 2 ); my ( $short_help, $extra ) = split( qr{^.+STAGES}m, $out ); my @lines = split "\n", $short_help; shift @lines for 1 .. 2; say foreach @lines; exit( $exit_status // 0 ); ## no critic(Cpanel::NoExitsFromSubroutines) } sub _help ( $class, $msg = undef, $verbosity = undef ) { my $val; open my $wfh, '>', \$val or die "Failed to open to a scalar: $!"; $msg .= "\n" if length $msg; local $Pod::Usage::Formatter; $Pod::Usage::Formatter = 'Pod::Text::Termcap' if _stdout_is_terminal(); require 'Pod/Usage.pm'; ##no critic qw(RequireBarewordIncludes) my $pod_path = $0; 'Pod::Usage'->can('pod2usage')->( -exitval => 'NOEXIT', -message => $msg, -verbose => $verbosity, -noperldoc => 1, -output => $wfh, -input => $pod_path, ); warn "No POD for “$class” in “$pod_path”!" if !$val; return $val; } sub _stdout_is_terminal() { return -t \*STDOUT } 1; } # --- END lib/Elevate/Usage.pm package main; # Copyright 2024 WebPros International, LLC # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. package cpev; =encoding utf8 =head1 NAME /scripts/elevate-cpanel =head1 DESCRIPTION Wrapper around the ELevate project which is designed to enable migrations between major versions of RHEL® derivatives. Currently supported upgrade paths: CentOS 7 => AlmaLinux 8 CloudLinux 7 => CloudLinux 8 =head1 SYNOPSIS /scripts/elevate-cpanel [OPTIONS] Optional: --start Start the conversion process. --continue Continue the conversion: retry the last step. --check[=BLOCKER_FILE] Check if your system has any known blockers to upgrade. --log Show the current elevation log. --status Check the current elevation status. --clean Cleanup scripts and files created by elevate-cpanel. --leappbeta Use the special beta repo from leapp. Do this only if instructed to by cPanel. --non-interactive Skip the yes/no prompt before proceeding with the upgrade. --update Instruct the script to replace itself on disk with a downloaded copy of the latest version. --version Print the version number and exit. --skip-elevate-version-check Skip the check for whether this script is up to date. --skip-cpanel-version-check Skip the check for whether cPanel is up to date. This option is intended only for testing! --upgrade-distro-manually --no-leapp Do not try to run the distro upgrade process, and pause instead. Once the upgrade has been run you should remove the file /waiting_for_distro_upgrade --help Display this documentation. =head1 COMMON USAGE =over =item Start an installation You can start an elevation update by running: /scripts/elevate-cpanel --start =item Start an installation and skip the confirmation prompt By default, you will be asked if you wish to proceed with the upgrade. If you wish to skip this prompt (by assuming yes): # Perform the update in non-interactive mode /scripts/elevate-cpanel --start --non-interactive =item Compatibility check with elevation You can check if your server is compatible with the upgrade process without starting an upgrade process. /scripts/elevate-cpanel --check This will also save a JSON representation of the blockers to a file, C by default, but passing an optional argument will use the file given instead: /scripts/elevate-cpanel --check /root/blockers.json =item Start an installation with manual reboot You can control the reboots by using the --manual-reboots flag when starting the process. This can be used for debugging purpose. /scripts/elevate-cpanel --start --manual-reboots =item Monitor an existing installation Once you have started an elevation process, you can check the current update by running: /scripts/elevate-cpanel You can also check the current status by running /scripts/elevate-cpanel --status You can check and monitor the elevation log /scripts/elevate-cpanel --log =item Resume an installation after an error If an error occurs during the elevation process, once you have fixed it, you can resume the update process by running: /scripts/elevate-cpanel --continue =back =head2 Advanced Options =head3 Using an alternative tool to upgrade your distro By default, the elevate script runs the L to upgrade you from 7 to 8. `Leapp` may not be compatible with your system. Using the `--upgrade-distro-manually` option gives you a way to do the actual distro upgrade in your own way. This, for instance, can be used to allow `Virtuozzo` systems to upgrade cPanel systems, which are not supported by `Leapp`. A `--upgrade-distro-manually` upgrade would look like: =over =item 1. User runs `/scripts/elevate-cpanel --start --upgrade-distro-manually` which starts the upgrade process. =item 2. `elevate-cpanel` does all preparatory steps to upgrade the system prior to the distro upgrade. =item 3. Elevate will then create the file `/waiting_for_distro_upgrade` to indicate that the operating system is ready for an upgrade. =over =item * This is when you would use your distro upgrade tool. =item * When you have completed upgrading your system, simply remove `/waiting_for_distro_upgrade` and reboot the system into normal multi-user mode. =back =item 4. Elevate will resume upon reboot and complete the upgrade just like it would have without `--upgrade-distro-manually` =back NOTE: `--upgrade-distro-manually` is not required for helper commands like `--continue` or `--status` =head1 WARNINGS The elevation process to perform a major OS upgrade is not a risk free update. Depending on the state of your current distribution multiple errors can occur. We recommend updating your system to the last upstream state before starting. It is *highly* recommended that you have a full backup or snapshot of your server before beginning the elevation process. The elevation process can take several long minutes and requires multiple reboots. Some reboots are expected to be longer than normal. Please do not interrupt the reboots and elevation process. =head1 SEE ALSO Read more from https://cpanel.github.io/elevate/ =cut use cPstrict; use Elevate::Constants (); BEGIN { # sanity check before loading the script eval { require Cpanel::Version::Tiny } or do { warn(qq[This script is designed to only run on cPanel servers.\n]); exit 1; ## no critic(Cpanel::NoExitsFromSubroutines) }; $ENV{LANG} = "C"; $ENV{LANGUAGE} = "C"; $ENV{LC_ALL} = "C"; $ENV{LC_MESSAGES} = "C"; $ENV{LC_CTYPE} = "C"; } use Log::Log4perl qw(:easy); use Config; use Carp (); use Errno (); use File::Copy (); use File::Copy::Recursive (); use File::Find (); use File::Path (); use File::Slurper (); use File::Spec (); use Hash::Merge (); use IO::Prompt (); use Term::ANSIColor (); use Cpanel::AccessIds::SetUids (); use Cpanel::Binaries (); use Cpanel::Config::Httpd (); use Cpanel::Config::LoadCpConf (); use Cpanel::JSON (); use Cpanel::OS (); use Cpanel::PID (); use Cpanel::Pkgr (); use Cpanel::RestartSrv::Systemd (); use Cpanel::SafeRun::Simple (); use Cpanel::SafeRun::Errors (); use Cpanel::SafeRun::Object (); use Cpanel::Update::Tiers (); use Cpanel::Version::Tiny (); use Cpanel::Version::Compare (); use Cpanel::Yum::Vars (); # - fatpack Components use Elevate::Components::Base (); use Elevate::Components (); use Elevate::Components::AbsoluteSymlinks (); use Elevate::Components::AutoSSL (); use Elevate::Components::BootKernel (); use Elevate::Components::CCS (); use Elevate::Components::CloudLinux (); use Elevate::Components::cPanelPlugins (); use Elevate::Components::cPanelPrep (); use Elevate::Components::DatabaseUpgrade (); use Elevate::Components::DiskSpace (); use Elevate::Components::Distros (); use Elevate::Components::DNS (); use Elevate::Components::EA4 (); use Elevate::Components::ElevateScript (); use Elevate::Components::ELS (); use Elevate::Components::Grub2 (); use Elevate::Components::Imunify (); use Elevate::Components::InfluxDB (); use Elevate::Components::IsContainer (); use Elevate::Components::JetBackup (); use Elevate::Components::KernelCare (); use Elevate::Components::Kernel (); use Elevate::Components::Leapp (); use Elevate::Components::Lists (); use Elevate::Components::LiteSpeed (); use Elevate::Components::MountPoints (); use Elevate::Components::MySQL (); use Elevate::Components::NICs (); use Elevate::Components::NixStats (); use Elevate::Components::OVH (); use Elevate::Components::PackageRestore (); use Elevate::Components::PackageDupes (); use Elevate::Components::Panopta (); use Elevate::Components::PECL (); use Elevate::Components::PerlXS (); use Elevate::Components::PostgreSQL (); use Elevate::Components::R1Soft (); use Elevate::Components::Repositories (); use Elevate::Components::RmMod (); use Elevate::Components::RpmDB (); use Elevate::Components::SSH (); use Elevate::Components::Softaculous (); use Elevate::Components::UnconvertedModules (); use Elevate::Components::UpdateReleaseUpgrades (); use Elevate::Components::UpdateSystem (); use Elevate::Components::WHM (); use Elevate::Components::WPToolkit (); use Elevate::Components::Acronis (); # - fatpack OS use Elevate::OS (); use Elevate::OS::CentOS7 (); use Elevate::OS::CloudLinux7 (); use Elevate::OS::RHEL (); use Elevate::OS::Ubuntu (); use Elevate::OS::Ubuntu20 (); # - fatpack package logic use Elevate::PkgMgr (); use Elevate::PkgMgr::APT (); use Elevate::PkgMgr::Base (); use Elevate::PkgMgr::YUM (); use Elevate::Database (); use Elevate::EA4 (); use Elevate::Fetch (); use Elevate::Leapp (); use Elevate::Logger (); use Elevate::Marker (); use Elevate::Motd (); use Elevate::NICs (); use Elevate::Notify (); use Elevate::Roles::Run (); # used as parent, but ensure fatpack use Elevate::Script (); use Elevate::Service (); use Elevate::StageFile (); use Elevate::Stages (); use Elevate::SystemctlService (); use Elevate::Usage (); #< 59; #>>V *** DO NOT EDIT THESE LINES MANUALLY *** use constant VALID_STAGES => 5; use constant NOC_RECOMMENDATIONS_TOUCH_FILE => q[/var/cpanel/elevate-noc-recommendations]; # XXX TODO verify that imunify reponames are in fact correct. # As of now they are straight up guesses. use constant ACTION_REBOOT_NEEDED => 4242; # just one unique id use constant ACTION_PAUSE_REQUESTED => 4243; use constant ACTION_CONTINUE => 4244; use constant PAUSE_ELEVATE_TOUCHFILE => q[/waiting_for_distro_upgrade]; use Simple::Accessor qw{ service script components leapp }; # after Simple::Accessor use parent qw{ Elevate::Roles::Run Elevate::Usage }; exit( __PACKAGE__->run(@ARGV) // 0 ) unless caller; sub _build_service ($self) { # FIXME weaken return Elevate::Service->new( cpev => $self ); } sub _build_components ($self) { # FIXME weaken return Elevate::Components->new( cpev => $self ); } sub _build_leapp ($self) { # FIXME weaken return Elevate::Leapp->new( cpev => $self ); } sub _build_script ($self) { return Elevate::Script->new; } sub _init ( $self_or_pkg, @args ) { my $self = ref $self_or_pkg ? $self_or_pkg : $self_or_pkg->new; Elevate::Usage::init( $self, @args ); Elevate::Logger::init($self); return $self; } sub run ( $pkg, @args ) { local $| = 1; my $self = _init( $pkg, @args ); if ( $self->getopt('version') ) { say VERSION; return 0; } if ( $self->getopt('update') ) { return $self->do_update(); } if ( $self->getopt('start') ) { die qq[Unsupported option with --start\n] if $self->getopt('continue') || $self->getopt('service'); die qq[BETA is unavailable for this leapp repo.] if $self->getopt('leappbeta') && !length Elevate::OS::leapp_repo_beta(); return 1 if $self->start(); } elsif ( $self->getopt('status') ) { return $self->check_status(); } return $self->do_cleanup() if $self->getopt('clean'); return $self->components->check() if defined $self->getopt('check'); my $stage = Elevate::Stages::get_stage(); if ( $stage == 0 ) { print_box_no_log( <<~EOS ); Please re-run this script with --help to know more about this script --check to check if your server is ready to upgrade --start if you would like to begin the upgrade process Read more from https://cpanel.github.io/elevate/ EOS return 0; } if ( $self->getopt('start') || $self->getopt('log') ) { 1; # these options are exclusive } elsif ( $self->getopt('continue') ) { $self->continue_elevation(); } elsif ( $self->getopt('service') ) { # running from the systemct service return $self->run_service_and_notify(); } $self->monitor_upgrade(); return 0; } sub get_component ( $self, $name ) { my $pkg = qq[Elevate::Components::$name]; # my $sub = $pkg->can( 'new' ) or die qq[Missing new from $pkg]; # my $x = $sub->( $pkg, cpev => $self ); # return $x; return $pkg->new( components => $self->components ); } sub get_blocker ( $self, $name ) { # helper for tests return $self->components->_get_blocker_for($name); } sub do_update ($self) { INFO( "Self-update of script version " . VERSION . " requested." ); my ( $needs_update, undef ) = $self->script->is_out_of_date(); if ($needs_update) { INFO("Newer version of script found. Downloading."); my $temp_file = $self->script->fetch; return 1 unless $temp_file; # Elevate::Fetch::script handled the error msg my $running_from = Cwd::abs_path($0) // ''; if ( File::Copy::mv( $temp_file, $running_from ) ) { chmod 0700, $running_from; my $confirmed_version = Cpanel::SafeRun::Simple::saferunnoerror( $running_from, '--version' ); chomp $confirmed_version; INFO("Script update to version $confirmed_version successful."); } else { ERROR("The system could not replace the existing copy of the script: $!"); return 1; } } else { INFO("Script is up to date."); } return 0; } sub monitor_upgrade ($self) { my $stage = Elevate::Stages::get_stage(); my $tail_msg = q[Running: tail -f ] . Elevate::Constants::LOG_FILE; my $status = $self->get_current_status(); if ( $status eq 'success' ) { Elevate::Logger::INFO_nolog("# Upgrade was successful. Showing the last lines of the update log."); } elsif ( $status eq 'running' || $status eq 'paused' ) { Elevate::Logger::INFO_nolog("# Monitoring existing $status upgrade (stage=$stage) ; $tail_msg"); } elsif ( $status eq 'failed' ) { Elevate::Logger::ERROR_nolog("# Upgrade process failed at stage=$stage ; $tail_msg"); } else { Elevate::Logger::WARN_nolog("# Monitoring upgrade file (stage=$stage) ; $tail_msg"); } exec( qw{/usr/bin/tail -n40 -F }, Elevate::Constants::LOG_FILE ); } sub start ($self) { return 1 unless _sanity_check(); my $stage = Elevate::Stages::get_stage(); if ( $stage != 0 ) { my $header; if ( $stage > VALID_STAGES ) { $header = q[The 'elevate-cpanel' script has already been executed on this server.]; } else { $header = qq[An elevation process is currently in progress: running stage $stage]; } die <<~"EOS"; $header You can check the log by running: /scripts/elevate-cpanel --log or check the elevation status: /scripts/elevate-cpanel --status EOS } # Check for blockers before starting the migration return 1 if ( $self->components->check() ); $self->give_last_chance(); system touch => Elevate::Constants::LOG_FILE; # store the manual reboots flag if ( $self->getopt('manual-reboots') ) { WARN('Manual Reboot would be required between each stages'); Elevate::StageFile::update_stage_file( { manual_reboots => 1 } ); } $self->_capture_env_variables(); # capture at startup if ( $self->getopt('upgrade-distro-manually') || $self->getopt('no-leapp') ) { my $touchfile = PAUSE_ELEVATE_TOUCHFILE; WARN( <<~"EOS"); Automated upgrade of the distro has been disabled. Please wait for the file '$touchfile' to be created. At that stage, the ELevate process is paused, and you can upgrade from 7 to 8 on your own. Once the system has been successfully upgraded from 7 to 8, you can then remove that file to let the ELevate process continue and update cPanel for the new distribution. EOS Elevate::StageFile::update_stage_file( { upgrade_distro_manually => 1 } ); } # This starts the service for us: return $self->service->install(); } sub _capture_env_variables ($self) { if ( my $ovl_size = $ENV{LEAPP_OVL_SIZE} ) { Elevate::StageFile::update_stage_file( { env => { LEAPP_OVL_SIZE => $ovl_size } } ); } return; } # This code used to be in stage 1, but it makes more sense for it to take place before a stage has been set. sub give_last_chance ($self) { my $upgrade_from_name = Elevate::OS::pretty_name(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); print_box(qq[/!\ Warning: You are about to convert your cPanel & WHM $upgrade_from_name to $pretty_distro_name server.]); present_noc_recommendations(); say <<'EOS'; The elevation process can take several long minutes and requires multiple reboots. Some reboots can be longer than normal, please do not interrupt the process. Please *do not interrupt* the reboots and elevation process. You can check at any time the current status of the update by running: /scripts/elevate-cpanel On failures after fixing them you can continue the elevation process by running: /scripts/elevate-cpanel --continue EOS Elevate::Notify::warn_skip_version_check() if $self->getopt('skip-cpanel-version-check'); # give one last reminder say <getopt('non-interactive') ) { if ( !IO::Prompt::prompt( '-one_char', '-yes_no', '-tty', -default => 'n', "Do you wish to proceed with the upgrade process [y/N]: ", ) ) { INFO("The update process has been canceled"); exit 0; ## no critic qw(Cpanel::NoExitsFromSubroutines) } } INFO("The update process has begun"); return; } sub continue_elevation ($self) { my $status = $self->get_current_status(); if ( $status eq 'success' ) { Elevate::Logger::INFO_nolog("Elevate process was successful. No process to continue."); return; } if ( $self->service->is_active ) { Elevate::Logger::WARN_nolog( "The service " . $self->service->name . " is still running. Please wait for it to finish before restarting it." ); return; } # check that no process is running and remove the pidfile for the other $self->check_and_create_pidfile()->remove_pid_file; my $stage = Elevate::Stages::get_stage(); if ( $stage == 0 ) { Elevate::Logger::ERROR_nolog("Looks like no elevate process was started. Please consider running: /scripts/elevate-cpanel --start"); return; } $self->_capture_env_variables(); # capture env before restart my $pid = fork(); LOGDIE(qq[Failed to fork: $!]) unless defined $pid; if ($pid) { Elevate::Logger::INFO_nolog("Restarting install process (stage=$stage)"); return 1; } else { # when restarting leapp process the restart process can hang for a long time # return earlier to let the user tail the service log $self->service->restart; exit(0); } } sub do_cleanup ( $self, $handle_service_manually = 0 ) { if ( !$handle_service_manually ) { $self->service->remove; $self->ssystem( '/usr/bin/systemctl', 'daemon-reload' ); } Elevate::StageFile::remove_stage_file(); unlink Elevate::Constants::PID_FILE; unlink Elevate::Constants::OVH_MONITORING_TOUCH_FILE; Elevate::Motd->cleanup(); return 0; } sub run_service_and_notify ($self) { # FIXME: move to Elevate::Service - need Notify # check it outside the eval block to avoid notification from the command line $self->check_and_create_pidfile(); my $stage = Elevate::Stages::get_stage(); if ( $stage >= 2 ) { my $status = $self->get_current_status(); Elevate::StageFile::update_stage_file( { status => q[running] } ) if $status ne 'paused'; } my $action_todo; do { my $ok = eval { $action_todo = $self->_run_service(); 1; }; $action_todo //= 0; if ($ok) { # only notify a success when reaching the last stage if ( $stage == VALID_STAGES ) { Elevate::StageFile::update_stage_file( { status => q[success] } ); $self->_notify_success(); Elevate::StageFile::create_success_file(); } if ( $action_todo == ACTION_PAUSE_REQUESTED ) { Elevate::StageFile::update_stage_file( { status => q[paused] } ); } elsif ( $action_todo == ACTION_REBOOT_NEEDED ) { $self->reboot(); } } else { my $error = $@ // q[Unknown error]; Elevate::StageFile::update_stage_file( { status => q[failed] } ); $self->_notify_error($error); LOGDIE($error); } } while ( $action_todo == ACTION_CONTINUE ); return $action_todo; } sub check_and_create_pidfile ($self) { my $upid = Cpanel::PID->new( { pid_file => Elevate::Constants::PID_FILE } ); if ( $upid->create_pid_file() != 1 ) { die qq[Another process is already running. Please wait for it to finish.\n]; } return $upid; } sub check_status ($self) { my $stage = Elevate::Stages::get_stage(); if ( !$stage ) { Elevate::Logger::ERROR_nolog('Elevation process has not started yet.'); return; } my $status = $self->get_current_status(); my $max_stage = VALID_STAGES; if ( $status eq 'success' ) { say q[success]; } elsif ( $status eq 'paused' ) { my $paused_file = PAUSE_ELEVATE_TOUCHFILE; say qq[Paused at stage $stage / $max_stage please read more from $paused_file]; } elsif ( $status eq 'running' ) { say qq[Running stage $stage / $max_stage]; } elsif ( $status eq 'failed' ) { say qq[Failed during stage $stage]; } else { say q[Unknown status]; return 1; } return; } sub _notify_success ($self) { my $upgrade_from_name = Elevate::OS::pretty_name(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $msg = <<"EOS"; The cPanel & WHM server has completed the elevation process from $upgrade_from_name to $pretty_distro_name. EOS if ( my $warnings = Elevate::StageFile::read_stage_file( 'final_notifications', [] ) ) { if ( scalar @$warnings ) { $msg .= "\nThe update to $pretty_distro_name was successful but please note that one ore more notifications require your attention:\n"; foreach my $w ( reverse @$warnings ) { # restore insert order $msg .= "\n* $w\n"; } } } Elevate::Notify::send_notification( qq[Successfully updated to $pretty_distro_name] => $msg, is_success => 1 ); return; } sub _notify_error ( $self, $error = '' ) { my $stage = Elevate::Stages::get_stage(); my $msg = <<"EOS"; The elevation process failed during stage $stage. You can continue the process after fixing the errors by running: $0 --continue You can check the error log by running: $0 Last Error: $error EOS my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); Elevate::Notify::send_notification( qq[Failed to update to $pretty_distro_name] => $msg ); return; } sub _run_service ($self) { my $stage = Elevate::Stages::get_stage(); print_box( "Starting stage $stage of " . VALID_STAGES ); $self->_wait_on_pause(); # handle pause when requested # sanity check if ( Cpanel::OS::major() == 8 && 2 <= $stage && $stage <= 3 ) { ## no critic(Cpanel::CpanelOS) WARN( "Detected " . Cpanel::OS::pretty_distro() . " while running stage $stage: swtiching to stage 4" ); $stage = 4; Elevate::Stages::update_stage_number($stage); } return $stage == 1 ? $self->run_stage_1() # Sanity check & install elevate service : $stage == 2 ? $self->run_stage_2() # Update the current distro packages then reboot : $stage == 3 ? $self->run_stage_3() # Prep leapp & run leapp process : $stage == 4 ? $self->run_stage_4() # reboot on AlmaLinux 8: restore packages : $stage == 5 ? $self->run_stage_5() # Final checks and cleanup : die "Unknown stage '$stage'. I don't know how to proceed"; } sub _wait_on_pause ($self) { my $pause_file = PAUSE_ELEVATE_TOUCHFILE; return unless -e $pause_file; Elevate::StageFile::update_stage_file( { status => q[paused] } ); WARN( <<~"EOS" ); The elevation process is currently paused by $pause_file Please read the file for more details. EOS while ( -e $pause_file ) { sleep 5; } Elevate::StageFile::update_stage_file( { status => q[running] } ); my $stage = Elevate::Stages::get_stage(); INFO("The pause file was removed. Continuing elevate process to stage $stage."); return 1; } sub present_noc_recommendations () { local $!; my $default_msg = <<~EOS; Your server provider has requested that you contact their technical support for additional information before you continue with this upgrade process. EOS my $can_read_file = open( my $fh, '<', NOC_RECOMMENDATIONS_TOUCH_FILE ); if ( $can_read_file || $! != Errno::ENOENT ) { WARN "Provider advisory file potentially present but unable to be opened for reading ($!): using default notice." unless $can_read_file; my $msg; if ($fh) { local $/; $msg = <$fh>; } $msg ||= $default_msg; say $msg; print "Proceed with the upgrade process? (yes/no) "; my $reply = lc ; chomp $reply; if ( $reply !~ m/^y(?:es?)?$/ ) { FATAL "Provider recommendations not acknowledged."; exit 1; ## no critic(Cpanel::NoExitsFromSubroutines) } INFO "Provider recommendations acknowledged; continuing."; } close $fh if $fh; return 1; } =head1 STAGES Description of the multiple stages used during the elevation process. Between each stage a reboot is performed before doing a final reboot at the very end. =head2 run_stage_1 Start the elevation process by installing the elevate-cpanel service responsible of the multiple reboots. =cut sub run_stage_1 ($self) { Elevate::Marker::startup(); Elevate::Motd->setup(); if ( !$self->upgrade_distro_manually() ) { Elevate::Stages::bump_stage(); return ACTION_CONTINUE; } $self->run_component_once( 'Grub2' => 'mark_cmdline' ); return ACTION_REBOOT_NEEDED; } =head2 run_stage_2 Update the current distro packages then reboot. =cut sub run_stage_2 ($self) { $self->run_component_once( 'Grub2' => 'verify_cmdline' ); # Best to do this before clearing the yum cache: $self->run_component_once( 'PackageDupes' => 'pre_distro_upgrade' ); $self->run_component_once( 'UpdateSystem' => 'pre_distro_upgrade' ); # This needs to happen before the database upgrade because # it will remove the packages we want to detect $self->run_component_once( 'PackageRestore' => 'pre_distro_upgrade' ); # This needs to execute before we disable cPanel services # or exporting the CCS data will fail $self->run_component_once( 'CCS' => 'pre_distro_upgrade' ); $self->run_component_once( 'DatabaseUpgrade' => 'pre_distro_upgrade' ); # This disable cPanel services $self->run_component_once( 'cPanelPrep' => 'pre_distro_upgrade' ); $self->run_component_once( 'SSH' => 'pre_distro_upgrade' ); $self->run_component_once( 'AutoSSL' => 'pre_distro_upgrade' ); $self->run_component_once( 'KernelCare' => 'pre_distro_upgrade' ); $self->run_component_once( 'Grub2' => 'pre_distro_upgrade' ); $self->run_component_once( 'NICs' => 'pre_distro_upgrade' ); # Imunify depends on EA4 so the Imunify component needs to # run before EA4 is removed. However, Imunify 360 can provide # hardened PHP so the EA4 profile backup needs to run before # Imunify is removed, so I split some of the EA4 logic out # to be executed before Imunify and left the removal until # after Imunify $self->run_component_once( 'EA4' => 'pre_imunify' ); $self->run_component_once( 'Imunify' => 'pre_distro_upgrade' ); $self->run_component_once( 'PECL' => 'pre_distro_upgrade' ); $self->run_component_once( 'EA4' => 'pre_distro_upgrade' ); # This reboot will invalidate the cPanel license causing # further whmapi1 calls to fail until stage 4 # if the NICs need to be renamed return ACTION_REBOOT_NEEDED; } =head2 run_stage_3 Setup the elevate-release-latest-el7 repo from the appropriate fork and install leapp Packages. Prepare the cPnael packages for the update. Remove some known conflicting packages. (Reinstall later). Provide answers to a few leapp questions. Attempt to perform the leapp upgrade itself. In case of failure you probably want to reply to a few extra questions or remove some conflicting packages. =cut sub run_stage_3 ($self) { $self->run_once('run_final_components_pre_distro_upgrade'); if ( !$self->upgrade_distro_manually() ) { return $self->_request_to_upgrade_distro_manually(); } if ( Elevate::OS::needs_leapp() ) { $self->run_once( install_leapp => sub { $self->leapp->install(); $self->leapp->remove_kernel_devel(); }, ); $self->leapp->upgrade(); } elsif ( Elevate::OS::needs_do_release_upgrade() ) { INFO('Remounting /tmp partition with exec option'); $self->ssystem_and_die( '/usr/bin/mount', '-o', 'remount,exec', '/tmp' ); INFO('Running do-release-upgrade'); $self->ssystem_and_die( '/usr/bin/do-release-upgrade', '-f', 'DistUpgradeViewNonInteractive' ); } WARN(<<~'EOS'); Rebooting for distro upgrade. This will take over 10 minutes to run. Do not interrupt power during this event or you will corrupt your system. EOS return ACTION_REBOOT_NEEDED; # This takes a while because on reboot it's installing 800 packages } sub _request_to_upgrade_distro_manually ($self) { my $pause_file = PAUSE_ELEVATE_TOUCHFILE; my $txt = <<"EOS"; The cPanel elevation process is currently paused. You should upgrade the distribution manually. After a successful upgrade, you can then remove the file rm -f $pause_file to let the elevation process continue and update cPanel for the updated distribution. EOS WARN($txt); File::Slurper::write_text( $pause_file, $txt ); return ACTION_PAUSE_REQUESTED; } =head2 run_stage_4 At this stage, we should now be run the upgraded OS (i.e. AlmaLinux 8). Update cPanel product for the new distro. Restore removed packages during the previous stage. =cut sub run_stage_4 ($self) { Cpanel::OS::flush_disk_caches(); Cpanel::OS::clear_cache_after_cloudlinux_update(); # 10 minute timeout if no completion is seen. unless ( $self->leapp->wait_for_leapp_completion ) { my $log_file = Elevate::Constants::LOG_FILE; die(<<~"END"); The LEAPP upgrade process did not succeed Review the log at $log_file for more details END } my $major = Cpanel::OS::major(); ## no critic(Cpanel::CpanelOS) if ( $major != Elevate::OS::expected_post_upgrade_major() ) { my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); my $current_distro = Cpanel::OS::pretty_distro(); if ( $major == Elevate::OS::original_os_major() ) { WARN("Detected $current_distro from stage 4, reverting back to stage 3."); Elevate::Stages::update_stage_number(3); } my $msg = <<~"END"; Server is currently running $current_distro $major after leapp upgrade. Upgrade to $pretty_distro_name did not succeed. END if ( Elevate::OS::needs_leapp() ) { $msg .= <<~'END'; Please review your upgrade logs and correct the problem. `leapp` logs are located at: /var/log/leapp/leapp-report.txt /var/log/leapp/leapp-report.json END } $msg .= <<~'END'; Once you believe you have corrected the problem you can continue the upgrade process by running: /scripts/elevate-cpanel --continue END die "$msg\n"; } my $stash = Elevate::StageFile::read_stage_file(); $stash->{stage4} //= {}; # run once each blocks $self->run_component_once( 'Lists', => 'post_distro_upgrade' ); $self->run_component_once( 'RpmDB', => 'post_distro_upgrade' ); $self->run_once( restore_cpanel_services => sub { $self->enable_all_cpanel_services(); $self->stop_services_before_upcp(); $self->clear_cpanel_caches(); return; } ); $self->run_component_once( 'Imunify' => 'post_distro_upgrade' ); $self->run_component_once( 'EA4' => 'post_distro_upgrade' ); $self->run_once( upcp => sub { { # MySQL server is not upgraded at this point - treat the upcp like a fresh install local $ENV{'CPANEL_BASE_INSTALL'} = 1; $self->ssystem_and_die(qw{/usr/local/cpanel/scripts/upcp.static --sync}); } # cleanup the license: cpsanitycheck.so binary is compiled for a different distro unlink('/usr/local/cpanel/cpsanitycheck.so'); $self->ssystem(qw{/usr/local/cpanel/cpkeyclt}); return; } ); $self->post_distro_upgrade_update_restore(); $self->run_component_once( 'UnconvertedModules' => 'post_distro_upgrade' ); # run upcp a second time as we had to run it before with CPANEL_BASE_INSTALL=1 $self->run_once( upcp_second_run => sub { $self->ssystem_and_die(qw{/usr/local/cpanel/scripts/upcp.static --sync}); } ); $self->run_component_once( 'CCS' => 'post_distro_upgrade' ); return ACTION_REBOOT_NEEDED; } =head2 run_stage_5 Final checks and cleanup. The elevate-cpanel service is now removed. =cut sub run_stage_5 ($self) { return 0 if $self->post_upgrade_check(); # we cannot stop the service ( ourself ) $self->service->disable; unlink Elevate::Constants::PID_FILE; unlink Elevate::Constants::CHKSRVD_SUSPEND_FILE; my $el_backup_dir = Elevate::Constants::ELEVATE_BACKUP_DIR; File::Path::remove_tree($el_backup_dir) if -d $el_backup_dir; $self->run_component_once( 'Grub2' => 'post_distro_upgrade' ); Elevate::Marker::success(); $self->restore_outdated_services(); INFO("Updating all packages before final reboot"); Elevate::PkgMgr::update_allow_erasing(); Elevate::Motd->cleanup(); my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); print_box("Great SUCCESS! Your upgrade to $pretty_distro_name is complete."); return ACTION_REBOOT_NEEDED; } sub run_component_once ( $self, $name, $function ) { my $component = $self->get_component($name); my $label = "component:$name:$function"; my $sub = $component->can($function) # or die qq[Missing functon $function from component '$name']; my $run = sub { DEBUG("Running '$function' from $name component."); return $sub->($component); }; return $self->run_once( $label, $run ); } sub run_once ( $self, $label, $code = undef ) { die unless defined $label; $code //= cpev->can($label) || Carp::confess(qq[Missing function '$label']); die unless ref $code eq 'CODE'; my $current_stage = Elevate::Stages::get_stage() // 0; my $stage_name = 'stage' . $current_stage; my $stash = Elevate::StageFile::read_stage_file(); $stash->{'_run_once'} //= {}; my $full_label = $stage_name . '_' . $label; # already run one time successfully if ( $stash->{'_run_once'}->{$full_label} ) { INFO( sprintf( "Stage %d: skipping %s (already run)", $current_stage, $label ) ); return 1; } $code->($self); # successfully run that block Elevate::StageFile::update_stage_file( { _run_once => { $full_label => 1 } } ); return; } sub _sanity_check { return 1 if -x $0; chmod 0700 => $0; return 1 if -x $0; Elevate::Logger::ERROR_nolog("The script $0 is not executable, please fix it before running it again."); return; } sub print_box ($message) { INFO( "*" x 90 ); INFO("*"); INFO("* $message"); INFO("*"); INFO( "*" x 90 ); return; } sub print_box_no_log ($message) { say( "#" x 90 ); say("#"); foreach my $line ( split( "\n", $message ) ) { say("# $line"); } say("#"); say( "#" x 90 ); return; } sub restore_outdated_services ($self) { my $outdated_services_file = Elevate::Constants::IGNORE_OUTDATED_SERVICES_FILE; return unless -e $outdated_services_file; my $content = File::Slurper::read_binary($outdated_services_file) // ''; my $service_name = $self->service->short_name; if ( $content =~ s{^$service_name$}{}gm ) { 1 while chomp $content; if ( $content =~ m{^\s*\z} ) { unlink $outdated_services_file; return 2; } else { $content .= "\n"; File::Slurper::write_binary( $outdated_services_file, $content ); return 1; } } return; } sub enable_all_cpanel_services ($self) { my $services = Elevate::StageFile::read_stage_file( 'disabled_cpanel_services', [] ); unless (@$services) { WARN('No cPanel services were disabled!'); } foreach my $service (@$services) { $self->ssystem( '/usr/bin/systemctl', 'enable', $service ); $self->ssystem( '/usr/bin/systemctl', 'stop', $service ); } return; } sub stop_services_before_upcp ($self) { # make sure we stop some extra services before updating them $self->ssystem(qw{/scripts/restartsrv_tailwatchd --stop}); $self->ssystem(qw{/scripts/restartsrv_dovecot --stop}); $self->ssystem(qw{/scripts/restartsrv_exim --stop}); return; } # remove and store sub run_final_components_pre_distro_upgrade ($self) { # order matters $self->run_component_once( 'RmMod' => 'pre_distro_upgrade' ); $self->run_component_once( 'MySQL' => 'pre_distro_upgrade' ); $self->run_component_once( 'PostgreSQL' => 'pre_distro_upgrade' ); $self->run_component_once( 'Repositories' => 'pre_distro_upgrade' ); $self->run_component_once( 'cPanelPlugins' => 'pre_distro_upgrade' ); $self->run_component_once( 'PerlXS' => 'pre_distro_upgrade' ); $self->run_component_once( 'WPToolkit' => 'pre_distro_upgrade' ); $self->run_component_once( 'InfluxDB' => 'pre_distro_upgrade' ); $self->run_component_once( 'JetBackup' => 'pre_distro_upgrade' ); $self->run_component_once( 'NixStats' => 'pre_distro_upgrade' ); $self->run_component_once( 'LiteSpeed' => 'pre_distro_upgrade' ); $self->run_component_once( 'AbsoluteSymlinks' => 'pre_distro_upgrade' ); $self->run_component_once( 'ELS' => 'pre_distro_upgrade' ); $self->run_component_once( 'R1Soft' => 'pre_distro_upgrade' ); $self->run_component_once( 'Softaculous' => 'pre_distro_upgrade' ); $self->run_component_once( 'Panopta' => 'pre_distro_upgrade' ); $self->run_component_once( 'Acronis' => 'pre_distro_upgrade' ); $self->run_component_once( 'UpdateReleaseUpgrades' => 'pre_distro_upgrade' ); $self->run_component_once( 'RpmDB' => 'pre_distro_upgrade' ); # remove the RPMs last return; } sub post_distro_upgrade_update_restore ($self) { if ( Elevate::OS::needs_leapp() ) { INFO('Removing leapp from excludes in /etc/yum.conf'); my $txt = eval { File::Slurper::read_text("/etc/yum.conf") }; if ( length $txt ) { $txt =~ s/\s*python2-leapp,snactor,leapp-upgrade-el7toel8,leapp//ms; File::Slurper::write_text( "/etc/yum.conf", $txt ); } } # plugins can use MySQL - restore database earlier $self->run_component_once( 'MySQL' => 'post_distro_upgrade' ); $self->run_component_once( 'PerlXS' => 'post_distro_upgrade' ); $self->run_component_once( 'PostgreSQL' => 'post_distro_upgrade' ); $self->run_component_once( 'cPanelPlugins' => 'post_distro_upgrade' ); $self->run_component_once( 'DatabaseUpgrade' => 'post_distro_upgrade' ); $self->run_component_once( 'PECL' => 'post_distro_upgrade' ); $self->run_component_once( 'WPToolkit' => 'post_distro_upgrade' ); $self->run_component_once( 'InfluxDB' => 'post_distro_upgrade' ); $self->run_component_once( 'JetBackup' => 'post_distro_upgrade' ); $self->run_component_once( 'Kernel' => 'post_distro_upgrade' ); $self->run_component_once( 'KernelCare' => 'post_distro_upgrade' ); $self->run_component_once( 'NixStats' => 'post_distro_upgrade' ); $self->run_component_once( 'LiteSpeed' => 'post_distro_upgrade' ); $self->run_component_once( 'R1Soft' => 'post_distro_upgrade' ); $self->run_component_once( 'Softaculous' => 'post_distro_upgrade' ); $self->run_component_once( 'PackageRestore' => 'post_distro_upgrade' ); $self->run_component_once( 'Acronis' => 'post_distro_upgrade' ); return; } sub clear_cpanel_caches ($self) { my @dirs = ( # note: all cpanel users also have a '.cpanel/datastore' # probably not necessary to cleanup '/root/.cpanel/datastore', # cleanup root CachedCommand ); my @files = qw{ /var/cpanel/GetOS.cache }; foreach my $d (@dirs) { $self->ssystem( qw{/usr/bin/rm -rf}, $d ); } unlink $_ foreach @files; return; } sub post_upgrade_check ($self) { my $expect_distro = Elevate::OS::default_upgrade_to(); $expect_distro = lc $expect_distro; unless ( Cpanel::OS::major() == Elevate::OS::expected_post_upgrade_major() && Cpanel::OS::distro() eq $expect_distro ) { ## no critic(Cpanel::CpanelOS) my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); die "Your distro does not look like $pretty_distro_name.\n"; } # call a cpanel binary $self->ssystem_and_die(qw{/usr/local/cpanel/cpanel -V}); return 0; } # TODO: We're going to need to store reboot time so we know if the last reboot has happened when we re-run the script. sub upgrade_distro_manually ($self) { # we store the upgrade_distro_manually option, but prefer using a positive check instead # we need to check to see if the CLI option is passed here too in order # to allow users to run this script with the '--check --upgrade-distro-manually', etc. options my $manual_distro_upgrade = scalar grep { $self->getopt($_) || Elevate::StageFile::read_stage_file( tr/-/_/r, 0 ) } qw/upgrade-distro-manually no-leapp/; return !$manual_distro_upgrade; } sub get_current_status ($self) { return Elevate::StageFile::read_stage_file( 'status', 'unknown' ); } sub reboot ($self) { Elevate::Stages::bump_stage(); # protection my $stage = Elevate::Stages::get_stage(); if ( $stage > VALID_STAGES + 1 ) { LOGDIE(qq[Cannot reboot reaching stage $stage]); } elsif ( $stage <= VALID_STAGES ) { print_box( "Rebooting into stage $stage of " . VALID_STAGES ); } else { print_box("Doing final reboot"); } if ( request_manual_reboots() ) { WARN("Manual Reboot Mode Enabled: please reboot the server to move to the next stage $stage"); return 0; } sleep(2); # ensure display is flushed (using tail...) my $exit = $self->ssystem( '/usr/sbin/reboot', 'now' ) or return 0; LOGDIE("Failed to reboot system please correct this and reboot"); return; } sub request_manual_reboots() { return !!Elevate::StageFile::read_stage_file( 'manual_reboots', 0 ); } 1;