/
cpanel_installer
/
Upload File
HOME
package InstallerUbuntu; # Copyright 2025 WebPros International, LLC # All rights reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited. use strict; use warnings; use CpanelLogger; use CpanelMySQL (); use Installer (); our @ISA = qw/Installer/; sub distro_type { return 'debian' } # Ubuntu support for public installs of 20.04 has been dropped and there is a separate installer. use constant CPANEL_UBUNTU_SUPPORT => [qw{22 24}]; sub check_system_support { my ($self) = @_; $self->SUPER::check_system_support; my $distro_name = $self->distro_name; my $distro_major = $self->distro_major; if ( $distro_name ne 'ubuntu' ) { return $self->invalid_system("cPanel, L.L.C. does not support $distro_name for new installations."); } return if ( grep { $distro_major == $_ } @{ CPANEL_UBUNTU_SUPPORT() } ); return $self->invalid_system("cPanel, L.L.C. does not support $distro_name version $distro_major."); } sub check_networking_scripts { # We don't do this check on ubuntu?? return; } sub check_system_files { my ($self) = @_; $self->SUPER::check_system_files; # Unset the kernel flag that prevents even root from accessing other users files in /tmp Common::ssystem( "sysctl", "--ignore", "fs.protected_regular=0" ); # Then configure this to be the default upon reboot my $prot_file = '/usr/lib/sysctl.d/protect-links.conf'; my @orig_prot_file_contents; if ( -f $prot_file ) { open( my $prot_rd_fh, '<', $prot_file ); while (<$prot_rd_fh>) { push( @orig_prot_file_contents, $_ ); } close($prot_rd_fh); open( my $prot_wr_fh, '>', $prot_file ); foreach my $line (@orig_prot_file_contents) { if ( $line =~ m/fs\.protected_regular/ ) { print $prot_wr_fh "fs.protected_regular = 0\n"; } else { print $prot_wr_fh "$line"; } } close($prot_wr_fh); } verify_usrmerge_is_installed(); # Configure alternate temp dir for debconf since /tmp is often mounted noexec mkdir '/root/tmp'; open( my $debconf_fh, '>>', '/etc/apt/apt.conf.d/50extracttemplates' ); print $debconf_fh "APT\n{\n ExtractTemplates\n\t\{\n\t\tTempDir /root/tmp;\n\t};\n};\n"; close($debconf_fh); # Check if libc6 package is properly installed my $dpkg_exit_code = Common::ssystem( qw{/usr/bin/dpkg -s libc6}, { ignore_errors => 1, quiet => 1 } ); if ( $dpkg_exit_code != 0 ) { ERROR( q{Your operating system's package update method } . qq{(apt) could not locate the libc6 package. } . q{This is an indication of an improper setup. } . q{You must correct this error before you proceed. } ); DEBUG("dpkg -s libc6 failed with exit code: $dpkg_exit_code"); FATAL("\n\n"); } # This package is needed for File::FcntlLock, used by installd/apt-get-wait $self->apt_nohang_ssystem( '/usr/bin/apt-get', 'install', '-y', 'libfile-fcntllock-perl' ); # CPANEL-48815: Pre-flight check: Verify keyserver connectivity for MariaDB GPG keys # This must happen early to catch connectivity issues before the main installation $self->check_keyserver_connectivity(); return; } # This system clobbers resolv.conf even if you update it manually. sub setup_and_check_resolv_conf { my ($self) = @_; INFO("Checking if systemd-resolved is enabled..."); my $systemd_resolved_enabled = !Common::ssystem( qw{systemctl is-enabled systemd-resolved}, { ignore_errors => 1 } ); # Only take action if systemd-resolved is actually enabled (exit code 0 means enabled) if ($systemd_resolved_enabled) { WARN("systemd-resolved is enabled and will be disabled to prevent DNS conflicts during installation"); INFO("Disabling systemd-resolved..."); Common::ssystem(qw{systemctl disable --now systemd-resolved}); unlink '/etc/resolv.conf'; # Check if cloud-init has network configuration with DNS settings my $cloud_network_config = '/etc/cloud/cloud.cfg.d/99_network.cfg'; my $use_cloud_init = 0; if ( -f $cloud_network_config ) { if ( open( my $cloud_fh, '<', $cloud_network_config ) ) { my $config_content = do { local $/; <$cloud_fh> }; close($cloud_fh); # Check if both resolv_conf and nameservers patterns are present if ( $config_content =~ /resolv_conf/ && $config_content =~ /nameservers/ ) { INFO("Found cloud-init network configuration with DNS settings at /etc/cloud/cloud.cfg.d/99_network.cfg"); $use_cloud_init = 1; } } } if ($use_cloud_init) { # Use cloud-init to regenerate resolv.conf from cloud configuration INFO("Re-running cloud-init to regenerate /etc/resolv.conf from cloud configuration"); Common::ssystem(qw{cloud-init single --name cc_resolv_conf}); INFO("Successfully regenerated /etc/resolv.conf using cloud-init DNS configuration"); } else { # Fallback to hardcoded DNS servers INFO("No suitable cloud-init DNS configuration found, configuring fallback DNS servers"); if ( open( my $fh, '>', '/etc/resolv.conf' ) ) { print $fh "nameserver 1.1.1.1\nnameserver 8.8.8.8\n"; close($fh); INFO("Replaced /etc/resolv.conf with fallback DNS servers: 1.1.1.1 and 8.8.8.8"); } else { WARN( 'Could not create new /etc/resolv.conf : ' . $! ); } } } else { INFO("systemd-resolved is not enabled, leaving existing DNS configuration unchanged"); } return $self->SUPER::setup_and_check_resolv_conf; } sub install_basic_precursor_packages { my ($self) = @_; INFO("Installing packages needed to download and run the cPanel initial install."); # Assure wget/bzip2/gpg are installed for centhat. These packages are needed prior to sysup my @packages_to_install = qw/wget bzip2 gpg-agent xz-utils nscd psmisc python3 rdate cron sysstat net-tools debconf-utils libnet-ssleay-perl/; $self->apt_nohang_ssystem( './apt-get-wait', '-y', 'install', @packages_to_install ); return; } # we need to call update first to ensure we have a full package list, otherwise it won't be able to find packages for install sub update_apt { my ($self) = @_; return unless $self->{'update_apt'}++ == 0; # Run once. $self->apt_nohang_ssystem( '/usr/bin/apt-get', 'update' ); return; } sub apt_nohang_ssystem { my ( $self, @cmd ) = @_; $self->update_apt; #circular but it's ok because we bumped $update_apt already. my $failcount = 0; my $result = 1; while ($result) { # While apt is failing. $result = Common::ssystem(@cmd); last if ( !$result ); # apt came back clean. Stop re-trying $failcount++; if ( $failcount > 5 ) { FATAL("apt failed $failcount times. The installation process cannot continue."); } } return; } sub remove_distro_software { my ($self) = @_; # Stop unattended-upgrades service to prevent it from holding locks INFO('Stopping unattended-upgrades service to prevent dpkg lock conflicts...'); Common::ssystem( qw{systemctl stop unattended-upgrades}, { ignore_errors => 1 } ); Common::ssystem( qw{systemctl stop apt-daily.timer apt-daily-upgrade.timer}, { ignore_errors => 1 } ); Common::ssystem( qw{systemctl kill --kill-who=all apt-daily.service apt-daily-upgrade.service}, { ignore_errors => 1 } ); my @remove_pkgs = qw( apache2-data apache2-utils dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pop3d exim4 exim4-base exim4-config exim4-daemon-heavy exim4-daemon-light exim4-dev exim4-doc-html exim4-doc-info mysql-server mysql-server-8.0 mysql-server-core-8.0 mysql-common libmysqlclient21 mysql-client mysql-client-8.0 mysql-client-core-8.0 postfix sendmail spamassassin libapache2-mod-perl2 mariadb-client mariadb-client-10.3 mariadb-client-core-10.3 mariadb-common mariadb-plugin-connect mariadb-server mariadb-server-10.3 mariadb-server-core-10.3 mariadb-test mycli pure-ftpd proftpd-basic unattended-upgrades ); my $distro_major = int( $self->distro_major ); if ( $distro_major == 22 ) { push @remove_pkgs, 'portreserve'; } INFO('Ensuring that conflicting services are not installed...'); Common::ssystem( '/usr/bin/apt-get', '-y', 'purge', @remove_pkgs, { ignore_errors => 1 } ); return; } sub verify_mysql_version { my ( $self, $cpanel_config ) = @_; my $mysql_version = $cpanel_config->{'mysql-version'}; return unless length $mysql_version; # The only supported installable versions are: # 8.0, 8.4, 10.5, 10.6, 10.11, 11.4 my $supported_versions = qr{^(?: | 8(?:\.[04])? | 10(?:\.(?:[5-6]|11)) | 11(?:\.(?:[4])) )$}x; unless ( $mysql_version =~ $supported_versions ) { FATAL('The mysql-version value in /root/cpanel_profile/cpanel.config is either invalid or references an unsupported MySQL/MariaDB version. See https://go.cpanel.net/supported-mysql-mariadb-versions for a list of supported versions.'); } my $lts_version = $self->lts_version; if ( CpanelMySQL::version_is_mariadb($mysql_version) && $lts_version < 117 ) { FATAL("cPanel & WHM version $lts_version does not support MariaDB® on Ubuntu®. See https://go.cpanel.net/supported-mysql-mariadb-versions for a list of supported versions."); } my $distro_major = $self->distro_major; my %db_id = CpanelMySQL::get_db_identifiers($mysql_version); my $advice = CpanelMySQL::get_db_version_advice( $mysql_version, $lts_version, $distro_major, $db_id{'plain'} ); if ( $advice->{ver} && $advice->{action} ) { FATAL("You must set $db_id{'stylized'} to version $advice->{ver} or $advice->{action} in the /root/cpanel_profile/cpanel.config file for cPanel & WHM version $lts_version."); } return; } # These packages are needed for MySQL later in the install # By downloading them now we do not have to wait for them later sub background_download_packages_used_during_initial_install { my ($self) = @_; my @sysup_packages_to_install = qw{quota expat libexpat1-dev}; my @ea4_packages_to_install = qw{elinks libssh2-1 libssh2-1-dev libvpx-dev libwww-perl libkrb5-dev libcompress-raw-bzip2-perl libcompress-raw-zlib-perl autoconf automake}; my $distro_major = int( $self->distro_major ); if ( $distro_major == 22 ) { push @ea4_packages_to_install, 'libvpx7'; } elsif ( $distro_major == 24 ) { push @ea4_packages_to_install, 'libvpx9'; } # Only way this happens is during development or if the customer uses --force else { warn "Unrecognized OS version detected for EA4 software. Unable to install all prereq packages\n"; } my @mysql_support_packages_to_install = qw{libnuma1 grep libuser coreutils libdbi-perl}; my @packages_to_install = ( @mysql_support_packages_to_install, @sysup_packages_to_install, @ea4_packages_to_install ); return $self->run_in_background( sub { $self->apt_nohang_ssystem( './apt-get-wait', '--download-only', '-y', 'install', @packages_to_install ); } ); } sub verify_usrmerge_is_installed { my $required_symlinks = { '/bin' => 'usr/bin', '/sbin' => 'usr/sbin', '/lib' => 'usr/lib', }; foreach my $link ( keys %{$required_symlinks} ) { my $target = readlink $link; if ( !length $target || $target ne $required_symlinks->{$link} ) { my $ubuntu_major_csv = join( ", ", map { "$_.04" } @{ CPANEL_UBUNTU_SUPPORT() } ); my $errmsg = "You can only install cPanel & WHM on a fresh Ubuntu installation of the following versions: $ubuntu_major_csv"; FATAL($errmsg); } } return; } sub check_keyserver_connectivity { my ($self) = @_; INFO("Performing pre-flight check for keyserver connectivity..."); # Test connectivity to keyserver.ubuntu.com which is required for MariaDB GPG keys # These keys (3A79BD29, 5072E1F5, C74CD1D8) are downloaded during cPanel installation my $keyserver = 'keyserver.ubuntu.com'; my $test_key = '3A79BD29'; # One of the MariaDB keys # Quick connectivity test with short timeout my @gpg_cmd = ( 'timeout', '30', 'gpg', '--batch', '--no-tty', '--keyserver', $keyserver, '--recv-keys', $test_key ); INFO("Testing connectivity to $keyserver..."); DEBUG( "Running command: [" . join( ' ', @gpg_cmd ) . "]" ); my $output = Common::ssystem(@gpg_cmd); my $exit_code = $? >> 8; if ( $exit_code != 0 ) { # Keyserver connectivity failed - provide detailed error message ERROR("Failed to connect to keyserver $keyserver (exit code: $exit_code)"); DEBUG("GPG output: $output"); my $error_message = $self->_generate_keyserver_error_message( $keyserver, $output, $exit_code ); FATAL($error_message); } INFO("Keyserver connectivity check passed - $keyserver is accessible"); # Clean up the test key from the temporary keyring my @clean_cmd = ( 'gpg', '--batch', '--yes', '--delete-keys', $test_key ); Common::ssystem(@clean_cmd); return; } sub _generate_keyserver_error_message { my ( $self, $keyserver, $output, $exit_code ) = @_; my $message = <<EOF; ================================================================================ KEYSERVER CONNECTIVITY ERROR ================================================================================ Unfortunately, we are unable to download mandatory GPG signing keys from $keyserver that are required for the cPanel & WHM installation. This error typically occurs when: • Your server's network connectivity to keyserver.ubuntu.com is blocked • Firewall rules are preventing access to keyserver ports (11371, 80, 443) • DNS resolution for keyserver.ubuntu.com is failing • Your hosting provider has network restrictions in place REQUIRED ACTION: Please contact your hosting provider or system administrator and ask them to: 1. Ensure outbound connectivity to keyserver.ubuntu.com on ports 11371, 80, and 443 2. Verify DNS resolution is working for keyserver.ubuntu.com 3. Check that no firewall rules are blocking keyserver access 4. Test GPG key retrieval manually with: gpg --keyserver keyserver.ubuntu.com --recv-keys 3A79BD29 TECHNICAL DETAILS: • Keyserver: $keyserver • Exit Code: $exit_code • Error Output: $output The cPanel & WHM installation cannot proceed without access to these GPG keys. Please resolve the network connectivity issue and try the installation again. For additional assistance, please contact your hosting provider's technical support team with this error message. ================================================================================ EOF return $message; } 1;