/
scripts
/
up file
home
#!/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/CryptoPolicies.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/Grub2ControlTest.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Grub2ChecksWorkarounds.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/NetworkManager.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/Systemd.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/Components/Ufw.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/AlmaLinux8.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/AlmaLinux9.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CentOS7.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CloudLinux.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CloudLinux7.pm'} = 'script/elevate-cpanel.PL.static'; $INC{'Elevate/OS/CloudLinux8.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/OS/Ubuntu22.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/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 EX_UNAVAILABLE => 69; 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'; use constant SKIP_PRESERVE_PHP_VERSIONS => '/var/cpanel/elevate_skip_preserve_php_versions'; 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'} ) { ERROR(<<~"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::OS (); use Elevate::Stages (); 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::CryptoPolicies (); 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::Grub2ControlTest (); use Elevate::Components::Grub2ChecksWorkarounds (); 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::NetworkManager (); 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::Systemd (); use Elevate::Components::Ufw (); 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 (); use File::Copy (); our @CHECKS = qw{ IsContainer ElevateScript UpdateSystem MountPoints SSH DiskSpace WHM Distros CloudLinux Imunify DNS MySQL Repositories Lists JetBackup KernelCare NICs NetworkManager EA4 CryptoPolicies BootKernel Grub2ChecksWorkarounds OVH AbsoluteSymlinks AutoSSL }; our @NOOP_CHECKS = qw{ CCS DatabaseUpgrade ELS Grub2ControlTest InfluxDB Kernel LiteSpeed NixStats PECL PackageRestore PackageDupes Panopta PerlXS PostgreSQL R1Soft RmMod RpmDB Softaculous Systemd Ufw UnconvertedModules UpdateReleaseUpgrades 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'; use constant ARCHIVE_BASE_PATH => '/var/cpanel/elevate_archive'; use constant FILES_TO_ARCHIVE => qw{ /var/cpanel/elevate /var/cpanel/elevate-blockers /var/log/elevate-cpanel.log }; 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(); $self->archive_elevate_files(); 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 ( Elevate::OS::is_experimental() ) { my $pretty_name = Elevate::OS::pretty_name(); my $upgrade_to_pretty_name = Elevate::OS::upgrade_to_pretty_name(); INFO(<<"EOS"); Warning*: ELevate for $pretty_name to $upgrade_to_pretty_name is experimental software and is not recommended for production environments. EOS } 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 archive_elevate_files ($self) { return unless Elevate::OS::should_archive_elevate_files(); return unless -s '/var/cpanel/elevate' || -s '/var/cpanel/elevate-blockers'; my $stage = Elevate::Stages::get_stage(); return unless $stage > cpev::VALID_STAGES(); my $archive_base_path = ARCHIVE_BASE_PATH; my $archive_path = $archive_base_path . '/' . Elevate::OS::archive_dir(); mkdir $archive_base_path unless -d $archive_base_path; mkdir $archive_path unless -d $archive_path; foreach my $file ( FILES_TO_ARCHIVE() ) { my $backup = $file; $backup =~ s{/}{_}g; File::Copy::mv( $file, $archive_path . '/' . $backup ) or die "Can't archive $file: $!"; } return; } 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(); return unless %links; my $ln_string = join ", ", sort keys %links; WARN(<<~"EOS"); Symlinks with absolute paths were found in /: $ln_string This can cause problems during the upgrade, and the script will correct them to relative symlinks before elevation. EOS 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 if $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 Try::Tiny; 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'); try { Elevate::PkgMgr::reinstall(@$yum_arch_plugins); } catch { my $plugins = join( ' ', @$yum_arch_plugins ); WARN("Failed to reinstall cPanel plugins: $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/CryptoPolicies.pm package Elevate::Components::CryptoPolicies; use cPstrict; use Elevate::OS (); use Cpanel::SafeRun::Simple (); use File::Path (); # 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 CUSTOM_MODULE_TEXT => <<EOF; hash = SHA1+ sign = ECDSA-SHA1+ RSA-PSS-SHA1+ RSA-SHA1+ sha1_in_certs = 1 EOF sub UPDATE_CRYPTO_POLICIES_PATH { return "/usr/bin/update-crypto-policies"; } sub CRYPTO_POLICIES_MODULE_PATH { return "/etc/crypto-policies/policies/modules"; } sub CPANEL_MODULE_FILE { return "/usr/local/cpanel/etc/crypto-policies/CPANEL-SHA1.pmod"; } sub check ($self) { return unless Elevate::OS::has_crypto_policies() && Elevate::OS::needs_sha1_enabled(); return $self->has_blocker(<<~"EOS") unless Cpanel::Pkgr::is_installed('crypto-policies') && Cpanel::Pkgr::is_installed('crypto-policies-scripts'); ELevate expects to see the crypto-policies and crypto-policies-scripts packages installed, but one or both are missing. This should not be possible, so this suggests a severely broken system, but you can try installing these packages anyway: dnf -y install crypto-policies crypto-policies-scripts EOS return $self->has_blocker(<<~"EOS") unless -x UPDATE_CRYPTO_POLICIES_PATH(); There appear to be some file permission issues with the system's cryptographic policies framework. You can try re-installing the packages to see if this fixes the issue: dnf -y reinstall crypto-policies crypto-policies-scripts EOS my $current_policy = $self->current_policy(); return if $current_policy =~ m/^(?:(?:DEFAULT(:SHA1)?)|LEGACY)$/; return $self->has_blocker(<<~"EOS"); The system's cryptographic policy is set to a value ($current_policy) which is not compatible with ELevate. We recommend that you set the cryptographic policy to the default for your distribution: /usr/bin/update-crypto-policies --set DEFAULT EOS } sub pre_distro_upgrade ($self) { return unless Elevate::OS::has_crypto_policies() && Elevate::OS::needs_sha1_enabled(); $self->prepare_system_for_sha1_policy_changes(); $self->set_custom_crypto_policy(); return; } sub set_custom_crypto_policy ($self) { return if Elevate::OS::os_provides_sha1_module(); my $custom_crypto_policies_file = CRYPTO_POLICIES_MODULE_PATH() . '/CPANEL-SHA1.pmod'; File::Copy::cp( CPANEL_MODULE_FILE(), $custom_crypto_policies_file ); $self->set_policy('DEFAULT:CPANEL-SHA1'); return; } sub prepare_system_for_sha1_policy_changes ($self) { return unless Elevate::OS::os_provides_sha1_module(); my $current_policy = $self->current_policy(); return if $current_policy eq 'DEFAULT:SHA1' || $current_policy eq 'LEGACY'; LOGDIE("Unexpected crypto policy \"$current_policy\"; this should have been caught during checks!") unless $current_policy eq 'DEFAULT'; my $filename = CRYPTO_POLICIES_MODULE_PATH . "/SHA1.pmod"; if ( -e $filename && !-e "$filename.elevate.bak" ) { WARN("Custom $filename detected; renaming existing file to $filename.elevate.bak."); rename( $filename, "$filename.elevate.bak" ) or LOGDIE("Could not rename $filename to $filename.elevate.bak: $!"); } if ( !-e $filename || -z _ ) { open( my $fd, '>', $filename ) or LOGDIE("Could not open $filename for write: $!"); print {$fd} CUSTOM_MODULE_TEXT; close $fd or do { unlink $filename; LOGDIE("Could not write/close $filename: $!"); }; } INFO("Applying new cryptographic policy:"); $self->set_policy('DEFAULT:SHA1'); return; } sub post_distro_upgrade ($self) { return unless Elevate::OS::has_crypto_policies() && Elevate::OS::needs_sha1_enabled(); $self->set_to_os_provided_sha1_policy(); return; } sub set_to_os_provided_sha1_policy ($self) { return unless Elevate::OS::os_provides_sha1_module(); my $current_policy = $self->current_policy(); return if $current_policy eq 'LEGACY'; my $filename = CRYPTO_POLICIES_MODULE_PATH() . "/SHA1.pmod"; unlink $filename if -e $filename; INFO("Re-applying new cryptographic policy using system definitions:"); $self->set_policy('DEFAULT:SHA1'); return; } sub current_policy ($self) { my $current_policy = uc Cpanel::SafeRun::Simple::saferunnoerror( UPDATE_CRYPTO_POLICIES_PATH(), '--show' ); chomp $current_policy; return $current_policy; } sub set_policy ( $self, $policy ) { return $self->ssystem_and_die( UPDATE_CRYPTO_POLICIES_PATH(), '--set', uc $policy ); } 1; } # --- END lib/Elevate/Components/CryptoPolicies.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 File::Copy (); use File::Path (); use File::Slurper (); use Cpanel::SafeRun::Simple (); use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); use Elevate::SystemctlService (); # 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; use constant FSTAB_PATH => '/etc/fstab'; use constant FSTAB_BACKUP_SUFFIX => 'elevate_backup'; use constant DISABLE_SECURETMP_TOUCHFILE => '/var/cpanel/disabled/securetmp'; 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, }; $need_space->{'/tmp'} = 750 * MEG if Elevate::OS::needs_do_release_upgrade() && !$self->is_securetmp_installed(); 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 } sub is_securetmp_installed ($self) { my $fstab = File::Slurper::read_binary(FSTAB_PATH); return grep { $_ =~ /^\s*\/usr\/tmpDSK/ } split "\n", $fstab; } sub pre_distro_upgrade ($self) { return unless Elevate::OS::needs_do_release_upgrade(); return unless $self->is_securetmp_installed(); Elevate::StageFile::remove_from_stage_file('restore_fstab'); my $fstab = File::Slurper::read_binary(FSTAB_PATH); my @lines = split "\n", $fstab; foreach my $line (@lines) { $line = '' if $line =~ /^\s*\/usr\/tmpDSK/; $line = '' if $line =~ m{/tmp\s+/var/tmp\s+ext4\s+defaults,bind,noauto\s+0\s+0}; } my $new_fstab = join "\n", @lines; $new_fstab .= "\n"; File::Copy::cp( FSTAB_PATH, FSTAB_PATH . '.' . FSTAB_BACKUP_SUFFIX ); File::Slurper::write_text( FSTAB_PATH, $new_fstab ); $self->create_disable_securetmp_touchfile(); Elevate::StageFile::update_stage_file( { restore_fstab => 1 } ); return; } sub create_disable_securetmp_touchfile ($self) { system touch => DISABLE_SECURETMP_TOUCHFILE(); return; } sub post_distro_upgrade ($self) { return unless Elevate::StageFile::read_stage_file('restore_fstab'); return unless Elevate::OS::needs_do_release_upgrade(); return unless -s FSTAB_PATH . '.' . FSTAB_BACKUP_SUFFIX; File::Copy::mv( FSTAB_PATH . '.' . FSTAB_BACKUP_SUFFIX, FSTAB_PATH ); unlink DISABLE_SECURETMP_TOUCHFILE(); eval { File::Path::remove_tree( '/tmp', { keep_root => 1 } ) }; return; } sub check_tmp ($self) { my $ok = $self->_disk_space_check(); $self->has_blocker(q[disk space issue]) unless $ok; return if $ok; 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 detected that there is not enough disk space in /tmp to safely attempt to execute the distro upgrade tool. 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 will need to increase the disk space available in the /tmp directory. 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->post_distro_upgrade(); $self->cpev->do_cleanup(1); $self->_remove_but_dont_stop_service(); $self->ssystem_and_die( '/usr/sbin/reboot', 'now' ); exit Elevate::Constants::EX_UNAVAILABLE(); ## no critic(Cpanel::NoExitsFromSubroutines) } sub _remove_but_dont_stop_service ($self) { $self->cpev->service->disable(); $self->ssystem( '/usr/bin/systemctl', 'daemon-reload' ); return; } 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 operating system detected. This script does not support experimental OS 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::EA4::Install (); 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'); $self->_remove_ea4_repo(); 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 _remove_ea4_repo ($self) { unlink '/etc/yum.repos.d/EA4.repo'; 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; } Cpanel::EA4::Install::install_ea4_repo(); 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-*'); my $stash = Elevate::StageFile::read_stage_file(); my $dropped_pkgs = $stash->{'ea4'}->{'dropped_pkgs'} // {}; foreach my $pkg ( sort keys %$dropped_pkgs ) { delete $ea4_config_files->{$pkg}; } 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) { return if -e Elevate::Constants::SKIP_PRESERVE_PHP_VERSIONS; my $default_php_version = Elevate::StageFile::read_stage_file('php_get_system_default_version'); my $php_get_inherited_domains = Elevate::StageFile::read_stage_file('php_get_inherited_domains'); my $vhost_versions = Elevate::StageFile::read_stage_file('php_get_vhost_versions'); return unless ref $vhost_versions eq 'ARRAY'; return unless scalar $vhost_versions->@*; my $whmapi1_bin = '/usr/local/cpanel/bin/whmapi1'; my $desired_output = '--output=json'; if ( length $default_php_version ) { my @api_cmd = ( $whmapi1_bin, $desired_output, 'php_set_system_default_version', "version=$default_php_version", ); 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 the default PHP version back to its original version. To set it back to its original PHP version, execute the following command: $api_string EOS } } my %inherited_domains; if ( ref $php_get_inherited_domains eq 'ARRAY' ) { %inherited_domains = map { $_ => 1 } @$php_get_inherited_domains; } foreach my $vhost_entry (@$vhost_versions) { my $version = $vhost_entry->{version}; my $vhost = $vhost_entry->{vhost}; my $fpm = $vhost_entry->{php_fpm}; $version = 'inherit' if $inherited_domains{$vhost}; my @api_cmd = ( $whmapi1_bin, $desired_output, '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) { return if -e Elevate::Constants::SKIP_PRESERVE_PHP_VERSIONS; my $php_get_system_default_version = Elevate::EA4::php_get_system_default_version(); Elevate::StageFile::remove_from_stage_file('php_get_system_default_version'); Elevate::StageFile::update_stage_file( { php_get_system_default_version => $php_get_system_default_version } ); 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 } ); my $php_get_inherited_domains = Elevate::EA4::php_get_inherited_domains(); Elevate::StageFile::remove_from_stage_file('php_get_inherited_domains'); Elevate::StageFile::update_stage_file( { php_get_inherited_domains => $php_get_inherited_domains } ); 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/Grub2ControlTest.pm package Elevate::Components::Grub2ControlTest; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::StageFile (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use File::Slurper (); # use Elevate::Components::Base(); our @ISA; BEGIN { push @ISA, qw(Elevate::Components::Base); } use constant GRUBBY_PATH => '/usr/sbin/grubby'; use constant UPDATE_GRUB_PATH => '/usr/sbin/update-grub'; # This is an official alias for grub-mkconfig with the correct arguments use constant GRUB_MKCONFIG_FRAG_DIR_PATH => '/etc/default/grub.d'; use constant GRUB_MKCONFIG_FRAG_FILE_PATH => GRUB_MKCONFIG_FRAG_DIR_PATH . '/zzz-elevate.cfg'; use constant CMDLINE_PATH => '/proc/cmdline'; use constant ETC_DEFAULT_GRUB_PATH => '/etc/default/grub'; use constant GRUB2_MKCONFIG => '/usr/sbin/grub2-mkconfig'; use constant GRUB_CFG => '/boot/grub2/grub.cfg'; 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 _call_update_grub ( $self, $opts = {} ) { die "Argument must be a hashref" unless ref $opts eq 'HASH'; return $opts->{die_on_error} ? $self->ssystem_and_die(UPDATE_GRUB_PATH) : $self->ssystem(UPDATE_GRUB_PATH); } 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 _add_kernel_arg ( $self, $arg ) { if ( Elevate::OS::bootloader_config_method() eq 'grubby' ) { my $kernel_path = $self->_default_kernel; $self->_call_grubby( { die_on_error => 1 }, "--update-kernel=$kernel_path", "--args=$arg" ); } elsif ( Elevate::OS::bootloader_config_method() eq 'grub-mkconfig' ) { my $content = qq{GRUB_CMDLINE_LINUX_DEFAULT="\$GRUB_CMDLINE_LINUX_DEFAULT $arg"}; File::Slurper::write_text( GRUB_MKCONFIG_FRAG_FILE_PATH, $content ); $self->_call_update_grub( { die_on_error => 1 } ); } else { LOGDIE("We don't know how to manipulate the bootloader!"); } return; } sub _check_command_exists { if ( Elevate::OS::bootloader_config_method() eq 'grubby' ) { return -x GRUBBY_PATH; } elsif ( Elevate::OS::bootloader_config_method() eq 'grub-mkconfig' ) { return -x UPDATE_GRUB_PATH; } else { LOGDIE("We don't know how to manipulate the bootloader!"); } return; } sub _autofix_etc_default_grub ($self) { return unless Elevate::OS::needs_grub_enable_blscfg(); my $etc_default_grub = eval { File::Slurper::read_binary(ETC_DEFAULT_GRUB_PATH) } // ''; my @lines = split "\n", $etc_default_grub; my $found = 0; foreach my $line (@lines) { next unless $line =~ m/^\s*GRUB_ENABLE_BLSCFG/; $found = 1 if $line =~ m/true/; $line = '' unless $found; last; } push @lines, 'GRUB_ENABLE_BLSCFG=true' unless $found; @lines = grep { $_ ne '' } @lines; my $content = join "\n", @lines; $content .= "\n"; File::Slurper::write_text( ETC_DEFAULT_GRUB_PATH, $content ); $self->ssystem_and_die( GRUB2_MKCONFIG, '-o', GRUB_CFG ) unless $found; return; } sub mark_cmdline ($self) { return unless _check_command_exists(); $self->_autofix_etc_default_grub(); my $arg = "elevate-" . _persistent_id; INFO("Marking default boot entry with additional parameter \"$arg\"."); $self->_add_kernel_arg($arg); return; } sub _remove_but_dont_stop_service ($self) { $self->cpev->service->disable(); $self->ssystem( '/usr/bin/systemctl', 'daemon-reload' ); return; } sub _remove_kernel_arg ( $self, $arg ) { my $result; if ( Elevate::OS::bootloader_config_method() eq 'grubby' ) { my $kernel_path = $self->_default_kernel; $result = !$self->_call_grubby( "--update-kernel=$kernel_path", "--remove-args=$arg" ); } elsif ( Elevate::OS::bootloader_config_method() eq 'grub-mkconfig' ) { $result = unlink(GRUB_MKCONFIG_FRAG_FILE_PATH) && !$self->_call_update_grub(); } else { LOGDIE("We don't know how to manipulate the bootloader!"); } return $result; } sub verify_cmdline ($self) { return unless _check_command_exists(); 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 $result = $self->_remove_kernel_arg($arg); WARN("Unable to restore original command line. This should not cause problems but is unusual.") unless $result; 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 Elevate::Constants::EX_UNAVAILABLE(); ## no critic(Cpanel::NoExitsFromSubroutines) } } return; } 1; } # --- END lib/Elevate/Components/Grub2ControlTest.pm { # --- BEGIN lib/Elevate/Components/Grub2ChecksWorkarounds.pm package Elevate::Components::Grub2ChecksWorkarounds; 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::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'; 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 if $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/Grub2ChecksWorkarounds.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) { 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) { 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 if $self->upgrade_distro_manually; if ( _is_container_envtype() ) { return $self->has_blocker(<<~'EOS'); cPanel thinks that this is a container-like environment. This script cannot upgrade container environments. 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."); if ( Elevate::OS::jetbackup_repo_rpm_url() ) { Elevate::PkgMgr::install_pkg_via_url( Elevate::OS::jetbackup_repo_rpm_url() ); } 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_jetbackup_is_supported(); $self->_blocker_old_jetbackup; return; } sub _blocker_jetbackup_is_supported ($self) { return unless Cpanel::Pkgr::is_installed('jetbackup5-cpanel'); return if Elevate::OS::supports_jetbackup(); my $name = Elevate::OS::pretty_name(); return $self->has_blocker(<<~"END"); ELevate does not currently support JetBackup for upgrades of $name. Support for JetBackup on $name will be added in a future version of ELevate. END } 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'); INFO("Work around issue with occasional missing package signing keys."); $self->ssystem_and_die(qw{ /usr/bin/rpm --import https://repo.cloudlinux.com/kernelcare/RPM-GPG-KEY-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 File::Copy (); use File::Slurper (); # use Log::Log4perl qw(:easy); INIT { Log::Log4perl->import(qw{:easy}); } use constant YUM_CONF => '/etc/yum.conf'; use constant YUM_CONF_BAK => '/etc/yum.conf.elevate_bak'; sub check ($self) { return if $self->is_check_mode(); # skip for --check return unless Elevate::OS::needs_leapp(); return if $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(); File::Copy::cp( YUM_CONF(), YUM_CONF_BAK() ); $self->_remove_excludes(); my $out = $self->cpev->leapp->preupgrade(); File::Copy::cp( YUM_CONF_BAK(), YUM_CONF() ); 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 pre_distro_upgrade ($self) { $self->run_once("_remove_excludes"); return; } sub _check_for_inhibitors ($self) { my $inhibitors = $self->cpev->leapp->search_report_file_for_inhibitors( qw( check_deprecated_rpm_signature check_detected_devices_and_drivers check_installed_devel_kernels cl_mysql_repository_setup network_deprecations 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; } sub _remove_excludes ($self) { return unless Elevate::OS::needs_leapp(); my $yum_conf = YUM_CONF(); INFO("Removing excludes from $yum_conf"); my $txt = eval { File::Slurper::read_text($yum_conf) }; my @lines = split "\n", $txt; foreach my $line (@lines) { next unless $line =~ /^\s*exclude\s*=/; $line = ''; } my $config = join "\n", @lines; $config .= "\n"; File::Slurper::write_text( $yum_conf, $config ); 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 Cpanel::DbUtils (); 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::DbUtils::find_mysqlcheck(); my $output = $self->ssystem_capture_output( $mysqlcheck_path, '--default-character-set=utf8mb4', '-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/NetworkManager.pm package Elevate::Components::NetworkManager; use cPstrict; use Elevate::Constants (); use Elevate::OS (); use Elevate::SystemctlService (); # 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 DHCP_CLIENT_CONF => '/usr/lib/NetworkManager/conf.d/10-dhcp-dhclient.conf'; use constant NETWORKMANAGER => '/usr/sbin/NetworkManager'; use constant NMCLI_PATH => '/usr/bin/nmcli'; sub check ($self) { return unless Elevate::OS::needs_leapp(); return if Elevate::OS::network_scripts_are_supported(); $self->_autofix_dhcp_dhclient_added_by_leapp(); my $networkmanager = NETWORKMANAGER(); my $out = $self->ssystem_hide_and_capture_output( $networkmanager, '--print-config' ); if ( $out->{status} != 0 ) { my $status = $out->{status}; return $self->has_blocker(<<~"EOS"); An error code ($status) was returned when attempting to determine the NetworkManager on this system. To attempt to retrieve the configuration, you can execute: $networkmanager --print-config Resolve the issue with this command before proceeding. EOS } foreach my $line ( @{ $out->{stdout} } ) { next unless $line =~ m/^\s*dhcp\s*=\s*(.*)/; my $dhcp_setting = $1; if ( $dhcp_setting eq 'dhclient' ) { my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); return $self->has_blocker(<<~"EOS"); NetworkManager is configured to usage with the "dhclient" DHCP module. This setting will be ignored after upgrading to $pretty_distro_name. To see your config, execute the following command: $networkmanager --print-config Review the current configuration for this server and remove or update it to set dhcp to something other than dhclient. EOS } } return; } sub pre_distro_upgrade ($self) { return unless Elevate::OS::needs_leapp(); return if $self->upgrade_distro_manually(); $self->_enable_network_manager(); $self->run_once('_convert_network_scripts_to_network_manager'); return; } sub _enable_network_manager ($self) { return unless Elevate::OS::needs_network_manager(); my $service_name = 'NetworkManager'; my $service = Elevate::SystemctlService->new( name => $service_name ); return if $service->is_enabled(); $service->enable(); return; } sub _convert_network_scripts_to_network_manager ($self) { return if Elevate::OS::network_scripts_are_supported(); my $glob_path = Elevate::Constants::ETH_FILE_PREFIX() . '*'; my @ifcfg_files = grep { $_ !~ m{/ifcfg-lo$} } glob $glob_path; foreach my $file (@ifcfg_files) { $self->ssystem_and_die( NMCLI_PATH(), 'connection', 'migrate', $file ); } return; } sub _autofix_dhcp_dhclient_added_by_leapp ($self) { return unless -s DHCP_CLIENT_CONF(); my $txt = File::Slurper::read_binary(DHCP_CLIENT_CONF); return unless $txt =~ m/Generated by leapp when upgrading/g; unlink DHCP_CLIENT_CONF(); return; } 1; } # --- END lib/Elevate/Components/NetworkManager.pm { # --- BEGIN lib/Elevate/Components/NICs.pm package Elevate::Components::NICs; use cPstrict; use File::Slurper (); use Elevate::Constants (); use Elevate::OS (); use Cpanel::SafeRun::Errors (); # 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 check ($self) { return 1 unless Elevate::OS::needs_leapp(); return 1 if $self->upgrade_distro_manually; return if $self->_blocker_missing_sbin_ip(); return if $self->_nics_have_missing_ifcfg_files(); $self->_blocker_ifcfg_files_missing_type_parameter(); $self->_blocker_bad_nics_naming(); $self->_blocker_has_ifcfg_files(); return; } sub pre_distro_upgrade ($self) { return unless Elevate::OS::needs_leapp(); $self->_rename_eth_devices(); return; } sub _blocker_missing_sbin_ip ($self) { return $self->has_blocker( q[Missing ] . SBIN_IP . ' binary' ) unless -x SBIN_IP; return; } sub _rename_eth_devices ($self) { my @nics = $self->get_eths(); 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 _blocker_ifcfg_files_missing_type_parameter ($self) { return unless Elevate::OS::needs_type_in_ifcfg(); my @bad_ifcfg_files; my @nics = $self->get_nics(); foreach my $nic (@nics) { my $nic_path = ETH_FILE_PREFIX . $nic; my $found = 0; my $txt = File::Slurper::read_binary($nic_path); my @lines = split "\n", $txt; foreach my $line (@lines) { if ( $line =~ m/^\s*TYPE\s*=/ ) { $found = 1; last; } } push @bad_ifcfg_files, $nic_path unless $found; } if (@bad_ifcfg_files) { my $ifcfg_files = join "\n", @bad_ifcfg_files; return $self->has_blocker(<<~"EOS"); The following network-scripts files are missing the TYPE key: $ifcfg_files Since we will be converting from using network-scripts to using NetworkManager as part of the OS distro upgrade, the TYPE key needs to be explicitly defined within each ifcfg-* file. 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; } sub _blocker_bad_nics_naming ($self) { return unless Elevate::OS::network_scripts_are_supported(); my @eths = $self->get_eths(); 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; } 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) { return unless Elevate::OS::network_scripts_are_supported(); my @nics_missing_nic_path; my @nics = $self->get_nics(); 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; } sub get_eths ($self) { my @nics = $self->get_nics(); my @eths = grep { $_ =~ m/^eth[0-9]+$/ } @nics; return @eths; } sub get_nics ($self) { my $ip_info = Cpanel::SafeRun::Errors::saferunnoerror( SBIN_IP, 'addr' ) // ''; my @nics; foreach my $line ( split /\n/xms, $ip_info ) { next unless $line =~ m/^[0-9]+:\s([a-zA-Z0-9_-]+):/; my $nic = $1; my $value = readlink "/sys/class/net/$nic"; next unless $value; next if $value =~ m{/virtual/}; push @nics, $nic; } return @nics; } sub _blocker_has_ifcfg_files ($self) { return if Elevate::OS::network_scripts_are_supported(); my $glob_path = ETH_FILE_PREFIX() . '*'; my @ifcfg_files = grep { $_ !~ m{/ifcfg-lo$} } glob $glob_path; return unless @ifcfg_files; my $pretty_distro_name = Elevate::OS::upgrade_to_pretty_name(); WARN(<<~'EOS'); Your machine has network interface cards configured using the legacy network-scripts style configuration. EOS my @secondary_ifcfg_files = grep { $_ =~ m/:[0-9]+$/ } @ifcfg_files; if (@secondary_ifcfg_files) { my $alias_interfaces = join( "\n", @secondary_ifcfg_files ); return $self->has_blocker(<<~"EOS"); Additionally, this machine has the following alias interfaces defined within the legacy network-scripts configuration: $alias_interfaces This type of configuration is no longer supported and will be ignored by NetworkManager. As such, you will need to manually convert this configuration to be compatible with NetworkManager before elevating this server. EOS } if ( $self->is_check_mode() ) { INFO(<<~"EOS"); Starting with $pretty_distro_name, the legacy network-scripts style configuration is no longer supported. This script will need to convert these configuration files to the NetworkManager style configuration before upgrading. EOS return; } WARN(<<~"EOS"); Prior to elevating this system to $pretty_distro_name, this script will automatically convert the legacy network-scripts style configuration to the NetworkManager style configuration. EOS if ( !$self->getopt('non-interactive') ) { if ( !IO::Prompt::prompt( '-one_char', '-yes_no', '-tty', -default => 'y', 'Do you consent to converting your network configuration from network-scripts to NetworkManager [Y/n]: ', ) ) { my $files_to_convert = join( "\n", @ifcfg_files ); return $self->has_blocker(<<~"EOS"); The system cannot be elevated to $pretty_distro_name until the legacy network-scripts configuration has been converted to use the NetworkManager configuration. The following files will need to be converted: $files_to_convert To convert a file, you can run a command similar to the following: /usr/bin/nmcli connection migrate /path/to/ifcfg-file NOTE: replace /path/to/ifcfg-file with the real path of the file to convert 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 ( 'net-snmp' => 1, 'sys-snap' => 1, 'cpanel-exim' => 0, ); } sub pre_distro_upgrade ($self) { my %package_list = _get_packages_to_check(); my @installed_packages; foreach my $package ( keys %package_list ) { if ( Cpanel::Pkgr::is_installed($package) ) { push @installed_packages, $package; } } my @packages_to_remove = grep { $package_list{$_} } @installed_packages; Elevate::PkgMgr::remove(@packages_to_remove); 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) unless Cpanel::Pkgr::is_installed($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']; push( @$pkgmgr_options, '--enablerepo=powertools' ) if Elevate::OS::needs_powertools(); push( @$pkgmgr_options, '--enablerepo=crb' ) if Elevate::OS::needs_crb(); 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 List::MoreUtils (); # 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"); $self->run_once("_remove_cpaddons_repo"); return; } sub _remove_cpaddons_repo ($self) { return if Elevate::OS::supports_cpaddons(); unlink '/etc/yum.repos.d/cPAddons.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_yum_conf_missing_plugins(); $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_yum_conf_missing_plugins ($self) { return unless Elevate::OS::yum_conf_needs_plugins(); my $txt = eval { File::Slurper::read_text("/etc/yum.conf") }; return if grep { $_ =~ m/^\s*plugins\s*=\s*1\s*$/ } split "\n", $txt; my $msg = <<~'EOS'; The file '/etc/yum.conf' is missing the line plugins=1 from the [main] section. Plugins are required in order for yum to function properly on CloudLinux systems. EOS if ( $self->is_check_mode() ) { WARN($msg); return; } $msg .= "\n"; $msg .= "ELevate will attempt to autofix this issue before starting.\n"; WARN($msg); return if $self->_autofix_yum_conf(); return $self->has_blocker(<<~'EOS'); ELevate was unable to autofix '/etc/yum.conf'. Check the contents of this file and ensure that the line plugins=1 exists in the [main] section of this file EOS } sub _autofix_yum_conf ($self) { my $txt = eval { File::Slurper::read_text("/etc/yum.conf") }; my @lines = split "\n", $txt; my $fixed = 0; foreach my $line (@lines) { next unless $line =~ m/^\s*plugins/; $line = 'plugins=1'; $fixed++; last; } if ( !$fixed ) { foreach my $line (@lines) { next unless $line =~ m/^\[main\]$/; $line = "[main]\nplugins=1"; $fixed++; last; } } if ($fixed) { my $config = join "\n", @lines; $config .= "\n"; File::Slurper::write_text( "/etc/yum.conf", $config ); } return $fixed; } 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->{'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->{'INVALID_SYNTAX'} ) { my $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 my @paths = @{ $self->{_yum_repos_path_using_invalid_syntax} }; my @uniq_paths = List::Util::uniq(@paths); my $blocker_id = ref($self) . '::YumRepoConfigInvalidSyntax'; $self->has_blocker( $invalid_syntax_msg, info => { name => $blocker_id, error => 'YUM repository has unsupported syntax', path => \@uniq_paths, }, ); } 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, ); } } my $lib_path = Elevate::OS::pkgmgr_lib_path(); if ( opendir( my $dfh, $lib_path ) ) { 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 $lib_path: $err}); my $id = ref($self) . '::YumDirUnreadable'; return $self->has_blocker( qq{Could not read directory $lib_path: $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 ) ); 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', 'unattended-upgrades', ); 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) { my @pkgs_to_remove = grep { Cpanel::Pkgr::is_installed($_) } 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(); Elevate::PkgMgr::config_manager_enable('crb') if Elevate::OS::needs_crb(); unlink('/usr/local/cpanel/3rdparty/perl/536/cpanel-lib/X/Tiny.pm'); unlink('/usr/local/cpanel/3rdparty/perl/542/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"); The OpenSSH configuration file does not explicitly state the PermitRootLogin option in the sshd_config file. This option may default to "prohibit-password" after the upgrade is complete. 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/Systemd.pm package Elevate::Components::Systemd; use cPstrict; use File::Slurper (); use Cpanel::SafeDir::MK (); 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 RESOLVED_CONF_D => '/etc/systemd/resolved.conf.d'; use constant CPANEL_CONF => RESOLVED_CONF_D . '/cpanel.conf'; use constant ETC_RESOLV_CONF => '/etc/resolv.conf'; sub pre_distro_upgrade ($self) { return if $self->upgrade_distro_manually(); # skip when --upgrade-distro-manually is provided $self->run_once('_add_systemd_resolved_config'); $self->run_once('_store_etc_resolv_conf_contents'); return; } sub post_distro_upgrade ($self) { $self->run_once('_restore_etc_resolv_conf_contents'); return; } sub _add_systemd_resolved_config ($self) { return if -s CPANEL_CONF; Cpanel::SafeDir::MK::safemkdir( '/etc/systemd/resolved.conf.d', 0755 ); File::Slurper::write_text( CPANEL_CONF, <<'EOF' ); [Resolve] DNSStubListener=no EOF chmod 0644, CPANEL_CONF; $self->ssystem( '/usr/bin/systemctl', 'daemon-reload' ); $self->ssystem(qw{/usr/bin/systemctl restart systemd-resolved}); return; } sub _store_etc_resolv_conf_contents ($self) { return unless -f ETC_RESOLV_CONF && -s _; my $resolv_conf = File::Slurper::read_binary(ETC_RESOLV_CONF); Elevate::StageFile::update_stage_file( { etc_resolv_conf => $resolv_conf } ); return; } sub _restore_etc_resolv_conf_contents ($self) { return unless -l ETC_RESOLV_CONF; my $resolv_contents = Elevate::StageFile::read_stage_file( 'etc_resolv_conf', '' ); return unless $resolv_contents; unlink ETC_RESOLV_CONF; File::Slurper::write_binary( ETC_RESOLV_CONF, $resolv_contents ); chmod 0644, ETC_RESOLV_CONF; return; } 1; } # --- END lib/Elevate/Components/Systemd.pm { # --- BEGIN lib/Elevate/Components/Ufw.pm package Elevate::Components::Ufw; use cPstrict; use Elevate::OS (); 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 UFW => '/usr/sbin/ufw'; sub pre_distro_upgrade ($self) { return if $self->upgrade_distro_manually(); # skip when --upgrade-distro-manually is provided return unless Elevate::OS::needs_do_release_upgrade(); if ( !-x UFW ) { my $ufw = UFW; WARN(<<~"EOS"); '$ufw' is either missing or not executable on this server. Unable to ensure that port 1022 is open as a secondary ssh option for do-release-upgrade. EOS return; } my $current_status = $self->ssystem_capture_output( UFW, 'status' ); my $is_active = grep { $_ =~ m/^Status:\sactive$/ } @{ $current_status->{stdout} }; my $is_open = grep { $_ =~ m{^1022/tcp.*ALLOW.*Anywhere} } @{ $current_status->{stdout} }; my $data = { is_active => $is_active, is_open => $is_open, }; Elevate::StageFile::update_stage_file( { ufw => $data } ); return if $is_active && $is_open; $self->ssystem_and_die( UFW, 'allow', '1022/tcp' ); $is_active ? $self->ssystem_and_die( UFW, 'reload' ) : $self->ssystem_and_die( UFW, '--force', 'enable' ); return; } sub post_distro_upgrade ($self) { my $ufw_data = Elevate::StageFile::read_stage_file( 'ufw', '' ); return unless ref $ufw_data && ref $ufw_data eq 'HASH'; return if $ufw_data->{is_active} && $ufw_data->{is_open}; return unless -x UFW; $self->ssystem_and_die( UFW, 'delete', 'allow', '1022/tcp' ); return if $ufw_data->{is_active}; $self->ssystem_and_die( UFW, 'disable' ); return; } 1; } # --- END lib/Elevate/Components/Ufw.pm { # --- BEGIN lib/Elevate/Components/UnconvertedModules.pm package Elevate::Components::UnconvertedModules; use cPstrict; use File::Slurper (); 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 YUM_CONF => '/etc/yum.conf'; 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) { $self->_remove_leapp_packages_from_yum_excludes(); INFO('Removing packages provided by leapp'); my @leapp_packages = Elevate::PkgMgr::get_leapp_pkgs(); my @to_remove = grep { Cpanel::Pkgr::is_installed($_) } @leapp_packages; Elevate::PkgMgr::remove(@to_remove); return; } sub _remove_leapp_packages_from_yum_excludes ($self) { my $yum_conf = YUM_CONF(); INFO("Removing leapp from excludes in $yum_conf"); my $txt = eval { File::Slurper::read_text($yum_conf) }; if ( length $txt ) { my @lines = split "\n", $txt; foreach my $line (@lines) { if ( $line =~ m/^\s*exclude\s*=(.*)/ ) { my $exclude_txt = $1; my @exclude_pkgs = split /[ ,]+/, $exclude_txt; my @updated_exclude_pkgs = grep { $_ !~ m/leapp|snactor|elevate-release/ } @exclude_pkgs; my $updated_exclude_txt = join ' ', @updated_exclude_pkgs; $line = "exclude=$updated_exclude_txt"; } } my $updated_txt = join "\n", @lines; $updated_txt .= "\n"; File::Slurper::write_text( $yum_conf, $updated_txt ); } return; } sub _warn_about_other_modules_that_did_not_convert ($self) { my $installed_packages = Elevate::PkgMgr::get_installed_pkgs(); my @el_installed_packages; foreach my $pkg ( sort keys %$installed_packages ) { my $el_package_regex = Elevate::OS::el_package_regex(); if ( $installed_packages->{$pkg} =~ m/\Q$el_package_regex\E/ ) { push @el_installed_packages, "$pkg-$installed_packages->{$pkg}"; } } my @el_packages_minus_exemptions; foreach my $pkg (@el_installed_packages) { next if grep { $pkg =~ m/$_/ } EXEMPTED_PACKAGES(); push @el_packages_minus_exemptions, $pkg; } return unless @el_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 (@el_packages_minus_exemptions) { $msg .= " $pkg\n"; } $msg .= "\nYou can remove these by running: yum -y remove " . join( ' ', @el_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 check ($self) { $self->_check_cpanel_pkgs(); return; } sub _check_cpanel_pkgs ($self) { my $out = $self->ssystem_capture_output( '/usr/local/cpanel/scripts/check_cpanel_pkgs', '--list-only' ); my $altered = join "\n", @{ $out->{stdout} }; if ( $altered =~ /Problems were detected with cPanel-provided files which are controlled by packages/ ) { WARN(<<~"EOS"); /usr/local/cpanel/scripts/check_cpanel_pkgs reported that your system has altered packages. $altered Running check_cpanel_pkgs with the fix arguent should correct the issue. Example: /usr/local/cpanel/scripts/check_cpanel_pkgs --fix EOS return 0; } return 1; } sub pre_distro_upgrade ($self) { Elevate::PkgMgr::clean_all(); my $ok = $self->_check_cpanel_pkgs(); $self->_fix_cpanel_pkgs() if !$ok; $self->ssystem_and_die(qw{/usr/local/cpanel/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; } sub _fix_cpanel_pkgs ($self) { $self->ssystem(qw{/usr/local/cpanel/scripts/check_cpanel_pkgs --fix}); my $out = $self->ssystem_capture_output( '/usr/local/cpanel/scripts/check_cpanel_pkgs', '--list-only' ); my $altered = join "\n", @{ $out->{stdout} }; if ( $altered =~ /Problems were detected with cPanel-provided files which are controlled by packages/ ) { LOGDIE(<<~"EOS"); /usr/local/cpanel/scripts/check_cpanel_pkgs was unable to repair the packages on this system: $altered You may be able to resolve this by executing /usr/local/cpanel/scripts/check_cpanel_pkgs --fix Once the issue has been resolved, you may continue this process with $0 --continue EOS return 0; } return 1; } 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::License (); use Cpanel::Unix::PID::Tiny (); use Cpanel::Update::Config (); use Cpanel::Update::Tiers (); use Cpanel::Version::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_cpanel_needs_license; $ok = 0 unless $self->_blocker_is_sandbox; if ( Elevate::OS::supports_named_tiers() ) { my $major = $Cpanel::Version::Tiny::major_version; $ok = 0 unle