MMCT TEAM
Server IP : 103.53.40.154  /  Your IP : 18.222.164.176
Web Server : Apache
System : Linux md-in-35.webhostbox.net 4.19.286-203.ELK.el7.x86_64 #1 SMP Wed Jun 14 04:33:55 CDT 2023 x86_64
User : ppcad7no ( 715)
PHP Version : 8.2.25
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON
Directory (0755) :  /cpanel_installer/

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : //cpanel_installer/install_ubuntu
#!/usr/bin/perl
# cpanel - installd/install_ubuntu                 Copyright 2021 cPanel, L.L.C.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
package installd::install;

use strict;
use warnings;

use POSIX;
use Cwd          ();
use Getopt::Long ();

use lib '.';
use lib 'installd';    # mainly for use by automated testing that does not enter the install directory
require Common;

my $distro;
my $distro_version;
my $distro_arch;
my $aptcheck = 0;
my $force;

my $skip_apache     = -e '/root/skipapache' ? 1 : 0;
my $skip_repo_setup = 0;
my $skip_license_check;
my $collect_output = undef;

my $installstart;

my $original_pid = $$;

exit run(@ARGV) unless caller;

sub run {
    my (@args) = @_;
    my $ret = eval { script(@args) };
    if ($@) {
        chomp $@;
        print STDERR $@;
        return 1;
    }
    return $ret;
}

sub script {
    my (@args) = @_;

    local $ENV{'CPANEL_BASE_INSTALL'} = 1;
    local $ENV{'LANG'}                = 'C';
    delete $ENV{'LANGUAGE'};

    local $| = 1;
    umask 022;

    if ( open my $fh, '<', $Common::lock_file ) {
        print "The system detected an installer lock file: ($Common::lock_file)\n";
        print "Make certain that an installer is not already running.\n\n";
        print "You can remove this file and re-run the cPanel installation process after you are certain that another installation is not already in progress.\n\n";
        my $pid = <$fh>;
        if ($pid) {
            chomp $pid;
            print `ps auxwww |grep $pid`;    ## no critic(ProhibitQxAndBackticks)
        }
        else {
            print "Warning: The system could not find pid information in the $Common::lock_file file.\n";
        }
        return 1;
    }

    # Create the lock file.
    if ( open my $fh, '>', $Common::lock_file ) {
        print {$fh} "$$\n";

        close $fh;
    }
    else {
        Common::FATAL("Unable to write lock file $Common::lock_file");

        return 1;
    }

    $original_pid = $$;

    END {
        return if $$ != $original_pid;

        # Required for testing.
        return if $INC{'Test/More.pm'};

        Common::cleanup_lock_file_and_gpg_homedir();
    }

    # Open the install logs for append.
    $installstart = Common::open_logs();

    # Determine local distro and version. Fail if unsupported.
    ( $distro, $distro_version, $distro_arch ) = check_system_support();
    my $options = {};
    Getopt::Long::GetOptionsFromArray(
        \@args,
        'force'            => \$force,
        'skipapache'       => \$skip_apache,
        'skipreposetup'    => \$skip_repo_setup,
        'skiplicensecheck' => \$skip_license_check,
    );

    # Validate hostname is FQDN.
    Common::check_hostname();

    # Do the clean install check pause right after
    # network manager so they see the warning since
    # this will pause for 5 seconds
    Common::clean_install_check();

    # Validate that various files are in place.
    Common::check_files();

    # Bootstrap checks.
    Common::INFO("Running health checks prior to start.");
    disable_systemd_resolved_if_enabled();
    Common::check_resolv_conf();

    # Unset the kernel flag that prevents even root from accessing other users files in /tmp
    Common::ssystem( "sysctl", "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);
    }

    # 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);

    # Make sure we import the GPG keys before anything tries to `apt update`
    mkdir '/var/cpanel/';
    Common::fetch_gpg_key_once();

    # This package is needed for File::FcntlLock, used by the wrapper script for all other packages
    apt_nohang_ssystem( 'apt', 'install', '-y', 'libfile-fcntllock-perl' );

    update_apt();
    check_apt_works();

    my $ensure_pkgs_installed;
    if ( $ensure_pkgs_installed = fork() ) {

        #parent
    }
    else {
        $collect_output = '';

        # Must run before bootstrap to install wget if needed
        local $@;
        eval { ensure_pkgs_installed( $distro, $distro_version ); };
        print $collect_output;
        undef $collect_output;
        die if $@;

        exit(0);
    }
    {
        # While the ensure is running in the background
        # we show the message warning that they need a clean
        # server
        local $SIG{'INT'} = sub {
            kill( 'TERM', $ensure_pkgs_installed );
            Common::WARN("Install terminated by user input");
            exit(0);
        };
        Common::warn_clean_server_needed();
        local $?;
        waitpid( $ensure_pkgs_installed, 0 );
        if ( $? != 0 ) {
            die "ensure_pkgs_installed failed: $?";
        }

    }

    # Assure minimum setup: wget & co
    Common::bootstrap( $distro, $distro_version );

    # Remove known packages that will conflict with what we want to install
    disable_software();

    my $install_version = Common::get_cpanel_version();

    # Install base distro required RPMS and setup YUM
    my $lts = Common::get_lts_version();
    Common::INFO("Installing cPanel & WHM major version ${lts}.");

    do_clock_update( $distro, $distro_version );

    # Start nscd if its not running since it will improve package install time
    Common::ssystem("ps -U nscd -h 2>/dev/null || /sbin/service nscd start");

    # Copy custom cpanel.config into place if provided
    Common::setup_custom_cpanel_config();
    create_config_files($options);

    # Look for conditions that require tier manipulation or require us to block the install.
    check_for_install_version_blockers( $distro, $distro_version, $distro_arch, $force );

    # bootstrap the cPanel perl ecosystem
    # We don't fork here, like we do on rhel derivatives, due to the fail-on-lock nature of apt/dpkg + requirements of fix-cpanel-perl

    $collect_output = '';
    local $@;
    eval { Common::bootstrap_cpanel_perl($install_version); };
    print $collect_output;
    undef $collect_output;
    die if $@;

    # Needs wget, working network connection.
    Common::check_if_we_can_get_to_httpupdate();

    # Make sure the OS is relatively clean.
    Common::check_no_mysql();

    # Check that we're in runlevel 3 or 5.
    check_runlevel($distro_version);

    my $installer_dir = Cwd::getcwd();

    # Do this after sanity checks so that we fail before creating the touch file.
    Common::DEBUG("Parsing command line arguments.");
    Common::get_install_type(@args);    # Set DNSONLY if need be.

    Common::check_license_conflict() unless $skip_license_check;    # need dnsonly file to be set

    # TODO: Get rid of these files and replace them with /var/cpanel/dnsonly
    # Disable services by touching files.
    if ( Common::is_dnsonly() ) {
        my @dnsonlydisable = qw( cpdavd );
        foreach my $dis_service (@dnsonlydisable) {
            Common::touch( '/etc/' . $dis_service . 'disable' );
        }
    }

    # by default, selinux tools (selinux-utils) are not installed on Ubuntu Server, but we
    # can leave this here just in case.
    # Set selinux to permissive mode for installation.
    if ( -e '/usr/sbin/setenforce' ) {
        Common::ssystem( '/usr/sbin/setenforce', '0', { ignore_errors => 1 } );
    }

    # Start background rpm download only after disable_software
    # since it does rpm -e
    my $background_rpm_download_pid = background_download_packages_used_during_initial_install($distro_version);

    # Now software is installed, call rdate in case it couldn't be called earlier in the bootstrap script.
    update_system_clock( $distro, $distro_version );

    Common::create_feature_showcase_dir();

    Common::create_slash_scripts_symlink();

    # Install cpanel files and directories. TERMINATE if failure.
    Common::updatenow(
        'skipapache'    => $skip_apache     ? 1 : 0,
        'skipreposetup' => $skip_repo_setup ? 1 : 0
    );

    chmod( 0700, '/usr/local/cpanel/scripts/cpanel_initial_install' );

    system( '/usr/local/cpanel/scripts/cpanel_initial_install', '--skipapache', $skip_apache, '--skipreposetup', $skip_repo_setup, '--installstart', $installstart );

    if ( $? >> 8 != 0 ) {
        kill 'TERM', $background_rpm_download_pid if $background_rpm_download_pid;
        Common::FATAL('The system failed to run the /usr/local/cpanel/scripts/cpanel_initial_install script.');

        return 1;
    }

    # Cleanup before exiting
    waitpid( $background_rpm_download_pid, 0 );
    return 0;
}

# no-op on ubuntu currently, but saving since it could easily be useful in the future
sub create_config_files {
    my ($options) = @_;
    return;
}

sub apt_nohang_ssystem {
    my @cmd = @_;

    $aptcheck = 1;
    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 ) {
            Common::FATAL("apt failed $failcount times. The installation process cannot continue.");
        }
    }
    $aptcheck = 0;
    return;
}

sub get_distro_release_pkg {

    if ( !-f '/etc/debian_version' ) {
        Common::invalid_system("The system could not detect a valid release file for this distribution");
    }
    chomp( my $apt_pkg = `dpkg-query -S /etc/debian_version` );    ## no critic(ProhibitQxAndBackticks)
    $apt_pkg =~ s/\:\s+.+//;
    return $apt_pkg;
}

sub check_system_support {

    # TODO: add back in some logic to check the actual system based on installed package(s) rather than trusting the text file
    # Some of these variables are unused *as of now*. However! some of these values may be useful for 'filling in the blanks' when the RPM check we do fails to provide all required info.
    # For now, only the $system and $machine variables are used, $machine only when Amazon Linux is detected. See https://metacpan.org/pod/POSIX#uname for more info.
    my ( $system, $nodename, $release, $version, $machine ) = POSIX::uname();

    if ( $system =~ m/linux/i ) {

        my $distro_arch = get_system_arch();
        my $release_pkg = get_distro_release_pkg();
        my ( $distro_name, $distro_vers, $distro_prettyname ) = get_distro_vers();

        $release_pkg or Common::invalid_system("Apt packages do not manage release file.");

        Common::INFO("$distro_prettyname (Linux) detected!");

        Common::INFO("Checking RAM now...");
        my $total_memory = Common::get_total_memory();
        my $min_memory   = 768;

        if ( $total_memory < $min_memory ) {
            Common::ERROR("cPanel, L.L.C. requires a minimum of $min_memory MB of RAM for your operating system.");
            Common::FATAL("Increase the server's total amount of RAM, and then reinstall cPanel & WHM.");
        }

        return ( $distro_name, $distro_vers, $distro_arch );
    }
    else {
        Common::invalid_system("Could not detect version for operating system");
    }

    Common::invalid_system("Unknown or unsupported operating system: $system");
    return;
}

sub check_runlevel {
    my ($distro_version) = @_;

    `systemctl is-active multi-user.target >/dev/null 2>&1`;    ## no critic(ProhibitQxAndBackticks)
    return if $? == 0;
    if ($force) {
        Common::WARN('The installation process detected that the multi-user.target is not active (boot is probably not finished).');
        Common::WARN('The multi-user.target must be active. Proceeding anyway because --force was specified!');
    }
    else {
        Common::ERROR('The installation process detected that the multi-user.target is not active (boot is probably not finished).');
        Common::FATAL('The multi-user.target must be active before the installation can continue.');
    }

    # From `man runlevel` :
    #       Table 1. Mapping between runlevels and systemd targets
    #       ┌─────────┬───────────────────┐
    #       │Runlevel │ Target            │
    #       ├─────────┼───────────────────┤
    #       │0        │ poweroff.target   │
    #       ├─────────┼───────────────────┤
    #       │1        │ rescue.target     │
    #       ├─────────┼───────────────────┤
    #       │2, 3, 4  │ multi-user.target │
    #       ├─────────┼───────────────────┤
    #       │5        │ graphical.target  │
    #       ├─────────┼───────────────────┤
    #       │6        │ reboot.target     │
    #       └─────────┴───────────────────┘

    my $runlevel = `runlevel`;    ## no critic(ProhibitQxAndBackticks)
    chomp $runlevel;
    my ( $prev, $curr ) = split /\s+/, $runlevel;

    my $message;

    # currently we allow runlevel 3 or 5, as 5 is the default even on Ubuntu Server, just with no X installed or running
    # runlevel can also return unknown
    if    ( !defined $curr )           { $message = "The installation process could not determine the server's current runlevel."; }
    elsif ( $curr != 3 || $curr != 5 ) { $message = "The installation process detected that the server was in runlevel $curr."; }
    else                               { return; }

    # the system claims to be in an unsupported runlevel.
    if ($force) {
        Common::WARN($message);
        Common::WARN('The server must be in runlevel 3. Proceeding anyway because --force was specified!');
        return;
    }
    else {
        Common::ERROR("The installation process detected that the server was in runlevel $curr.");
        Common::FATAL('The server must be in runlevel 3 before the installation can continue.');
    }

    Common::FATAL('Runlevel logic failed. This should never happen. The installer cannot continue.');
    return;
}

sub ensure_pkgs_installed {
    my ( $distro, $distro_version ) = @_;

    Common::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/;

    if (@packages_to_install) {
        apt_nohang_ssystem( './apt-get-wait', '-y', 'install', @packages_to_install );
    }

    return;
}

sub disable_software {
    my @remove_pkgs = qw(
      dovecot-core
      dovecot-imapd
      dovecot-lmtpd
      dovecot-pop3d
      exim4
      mysql-server
      mysql-server-8.0
      mysql-server-core-8.0
      mysql-common
      mysql-client
      mysql-client-8.0
      mysql-client-core-8.0
      portreserve
      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
    );

    Common::INFO('Ensuring that prelink is disabled...');

    if ( open( my $fh, '+<', '/etc/sysconfig/prelink' ) ) {
        my @lines = map { my $s = $_; $s =~ s/^(PRELINKING=)yes(.*)$/$1no$2/; $s } <$fh>;
        seek( $fh, 0, 0 );
        print {$fh} @lines;
        truncate( $fh, tell($fh) );
    }

    Common::INFO('Ensuring that conflicting services are not installed...');
    Common::ssystem( './dpkg-wait', '-r', '--force-depends', @remove_pkgs, { ignore_errors => 1 } );

    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 {
    Common::ssystem("./apt-get-wait update");
    return;
}

sub check_apt_works {
    local $ENV{'LC_ALL'} = 'C';
    my $out = `apt info libc6 2>&1`;    ## no critic(ProhibitQxAndBackticks)
    return if ( $out =~ m/Installed\-Size:\s/ );

    Common::ERROR( q{Your operating system's packagge 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. } );
    Common::FATAL("\n\n");
    return;
}

# This code is somewhat of a duplication of the code for updatenow that blocks updates based on configuration
# settings. It needs to be here also because of the bootstrap level nature for when this needs to run.
sub check_for_install_version_blockers {
    my ( $distro, $distro_version, $distro_arch, $force ) = @_;

    my $lts_version = Common::get_lts_version();
    my $tier        = Common::get_cpanel_tier();
    $lts_version or Common::FATAL("The system could not determine the target version from your tier: $tier");

    if ( $lts_version < 69 ) {
        Common::FATAL("You cannot install versions of cPanel & WHM prior to cPanel & WHM version 70.");
    }

    if ( $distro =~ m/bsd/i ) {
        Common::FATAL("cPanel & WHM does not support BSD.");
    }

    if ( $distro_arch ne 'x86_64' ) {
        Common::FATAL("cPanel &WHM only support 64-bit versions of Ubuntu.");
    }

    if ( $lts_version < 97 ) {
        Common::FATAL("cPanel & WHM only supports Ubuntu on version 98 or greater.");
    }

    my $staging_dir = Common::get_staging_dir();
    if ( $staging_dir ne '' && $staging_dir ne '/usr/local/cpanel' ) {
        Common::FATAL("STAGING_DIR must be set to /usr/local/cpanel during installs.");
    }

    # pull in cpanel.config settings or return if the file's not there (defaults will assert)
    return if ( !-e '/var/cpanel/cpanel.config' );
    my $cpanel_config = Common::read_config('/var/cpanel/cpanel.config');

    if ( $cpanel_config->{'mysql-version'} ) {

        if ( ( $lts_version == 97 || $lts_version == 98 ) && $cpanel_config->{'mysql-version'} ne '8.0' ) {
            Common::FATAL('The mysql-version value in /var/cpanel/cpanel.config is either invalid or references an unsupported MySQL/MariaDB version.  cPanel & WHM version 98 only supports MySQL version 8.0 on Ubuntu.');
        }

        # The only supported installable versions are:
        # 5.7, 8.0, 10.2, 10.3 10.5
        # The following will recommend a specific version in error output:
        # 5.5, 5.6, 10.0, 10.1
        my $supported_versions = qr{^(?:
            5\.[5-7]
            | 8(?:\.0)?
            | 10(?:\.[0-3,5])?
        )$}x;
        unless ( $cpanel_config->{'mysql-version'} =~ m/$supported_versions/ ) {
            Common::FATAL('The mysql-version value in /var/cpanel/cpanel.config is either invalid or references an unsupported MySQL/MariaDB version.');
        }

        my %db_id                  = Common::get_db_identifiers( $cpanel_config->{'mysql-version'} );
        my $min_allowed_db_version = get_min_allowed_db_version( $lts_version, $distro_version, $db_id{'plain'} );
        unless ( $cpanel_config->{'mysql-version'} >= $min_allowed_db_version ) {
            Common::FATAL("You must set $db_id{'stylized'} to version $min_allowed_db_version or higher in the /var/cpanel/cpanel.config file for cPanel & WHM version $lts_version.");
        }
    }

    if ( defined $cpanel_config->{'mailserver'} && $cpanel_config->{'mailserver'} =~ m/^courier$/i ) {
        Common::FATAL("You must use 'dovecot' or 'disabled' for the mailserver in the /var/cpanel/cpanel.config file for cPanel & WHM version $lts_version.");
    }

    return;
}

sub update_system_clock {
    my ( $distro, $distro_version ) = @_;

    my @time_sync_call =
        -x '/usr/sbin/rdate' ? ( '/usr/sbin/rdate', '-s', 'rdate.cpanel.net' )
      : -x '/usr/bin/rdate'  ? ( '/usr/bin/rdate', '-s', 'rdate.cpanel.net' )
      : -x '/bin/rdate'      ? ( '/bin/rdate', '-s', 'rdate.cpanel.net' )
      :                        ();

    # Complain if we don't have an rdate binary.
    if ( !@time_sync_call ) {
        Common::ERROR("The system could not set the system clock because the rdate binary is missing.");
        return;
    }

    # Set the clock
    my $was = time();
    Common::ssystem(@time_sync_call);
    my $now = time();
    Common::INFO( "The system set the clock to: " . localtime($now) );
    my $change = $now - $was;

    # Adjust the start time if it shifted more than 10 seconds.
    if ( abs($change) > 10 ) {
        Common::WARN("The system changed the clock by $change seconds.");
        $installstart += $change;
        Common::WARN( "The system adjusted the starting time to " . localtime($installstart) . "." );
    }
    else {
        Common::INFO("The system changed the clock by $change seconds.");
    }

    return 1;
}

sub do_clock_update {
    my ( $distro, $distro_version ) = @_;

    # Sync the clock.
    if ( !update_system_clock( $distro, $distro_version ) ) {
        WARN( "The current system time is set to: " . `date` );    ## no critic(ProhibitQxAndBackticks)
        WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        WARN("The installation process could not verify the system time. The utility to set time from a remote host, rdate or chrony, is not installed.");
        WARN("If your system time is incorrect by more than a few hours, source compilations will subtly fail.");
        WARN("This issue may result in an overall installation failure.");
        WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }
    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 $distro_version = shift || die "distro_version required";

    my @python2_packages                  = qw{python python-devel python-docs python-setuptools };
    my @sysup_packages_to_install         = qw{quota expat libexpat1-dev};
    my @ea4_packages_to_install           = qw{elinks libssh2-1 libssh2-1-dev libvpx6 libvpx-dev libwww-perl libkrb5-dev libcompress-raw-bzip2-perl libcompress-raw-zlib-perl autoconf automake};
    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 );

    if ( my $pid = fork() ) {

        # Parent
        return $pid;
    }
    else {
        $collect_output = '';
        local $@;
        eval { apt_nohang_ssystem( './apt-get-wait', '--download-only', '-y', 'install', @packages_to_install ); };
        print $collect_output;
        undef $collect_output;
        die if $@;

        exit(0);
    }
    die "Failed to fork to create background package download: $!";
}

# Copied from Cpanel::RpmUtils::Parse
sub parse_rpm {
    my ($name_with_version) = @_;
    my @name_version_parts = split( m{-}, $name_with_version );

    my $release = pop @name_version_parts;    # 1.192.el6  (glibc-common-2.12)
    my $version = pop @name_version_parts;    # 2.12  (glibc-common)

    my $name = join( '-', @name_version_parts );
    $name =~ s/^\d+://;                       # TODO/YAGNI: include epoch (or lack thereof) in results?

    return {
        'release' => $release,
        'version' => $version,
        'name'    => $name
    };
}

sub get_min_allowed_db_version {
    my ( $cpversion, $osver, $type ) = @_;

    # If OS Version is 8 or higher, enforce MySQL 8/Maria 10.3 as default.
    # Otherwise, use 5.5, 5.6 or 5.7 as is outlined in our version matrix.
    $type ||= 'mysql';
    my %at_least_req_combos = (
        'mariadb' => [
            {
                'osver' => 8,
                'cpver' => 91,
                'dbver' => 10.3,
            },
            {
                'osver' => 6,
                'cpver' => 48,
                'dbver' => 10.2,    # MariaDB 10.2 reaches EOL May 23, 2022
            },
        ],
        'mysql' => [
            {
                'osver' => 8,
                'cpver' => 91,
                'dbver' => 8.0,
            },
            {
                'osver' => 6,
                'cpver' => 80,
                'dbver' => 5.6,
            },
            {
                'osver' => 6,
                'cpver' => 48,
                'dbver' => 5.5,
            },
        ],
    );

    # Choose the possible combo with the cpversion "closest" to passed in.
    # Sorting the grep result will enable this rather straightforwardly.
    my ($winning_combo) = sort { $b->{'cpver'} <=> $a->{'cpver'} } grep {
        my $hr = $_;
        $osver >= $hr->{'osver'} && $cpversion >= $hr->{'cpver'}
    } @{ $at_least_req_combos{$type} };

    return $winning_combo->{'dbver'};
}

sub max {
    my ( $a, $b ) = @_;

    return $a > $b ? $a : $b;
}

sub version_cmp {
    my ( $a, $b ) = @_;

    my @parts_a = split /\./, $a;
    my @parts_b = split /\./, $b;

    my $len = max( scalar @parts_a, scalar @parts_b );

    for ( my $i = 0; $i < $len; $i++ ) {
        my $part_a = $parts_a[$i];
        my $part_b = $parts_b[$i];

        return 1  unless defined $part_b;
        return -1 unless defined $part_a;

        my $result = $part_a <=> $part_b;

        return $result unless $result == 0;
    }

    return 0;
}

sub version_lt {
    my ( $a, $b ) = @_;
    return version_cmp( $a, $b ) < 0 ? 1 : 0;
}

sub version_gte {
    my ( $a, $b ) = @_;
    return version_cmp( $a, $b ) > -1 ? 1 : 0;
}

sub get_system_arch {
    chomp( my $system_arch = `uname -m` );    ## no critic(ProhibitQxAndBackticks)
    if ( !$system_arch ) {
        chomp( $system_arch = `dpkg --print-architecture` );    ## no critic(ProhibitQxAndBackticks)
    }
    if ( !$system_arch ) {
        Common::FATAL('Unable to determine system architecture!');
    }
    return $system_arch;
}

sub get_distro_vers {
    my ( $distro_name, $distro_vers, $distro_prettyname );
    if ( open( my $os_vers_fh, '<', '/etc/os-release' ) ) {
        while ( my $line = <$os_vers_fh> ) {
            chomp $line;
            my ( $name, $val ) = split( /=/, $line );
            if ( $name eq 'NAME' )        { $distro_name       = $val }
            if ( $name eq 'VERSION_ID' )  { $distro_vers       = $val }
            if ( $name eq 'PRETTY_NAME' ) { $distro_prettyname = $val }
        }
        close($os_vers_fh);
        if ( !$distro_name || !$distro_vers || !$distro_prettyname ) {
            Common::FATAL('Could not get expected information from /etc/os-release, this indicates an improper setup.');
        }
    }
    else {
        Common::FATAL('Could not open /etc/os-release, this indicates an improper setup.');
    }
    return ( $distro_name, $distro_vers, $distro_prettyname );
}

sub disable_systemd_resolved_if_enabled {
    print "Disabling systemd-resolved if it is enabled...";
    my $needs_action = `systemctl list-unit-files | grep systemd-resolved`;
    Common::ssystem(qw{systemctl disable --now systemd-resolved}) if $needs_action;

    # Remove the symlink and put a viable one in place
    if ( -l '/etc/resolv.conf' ) {
        unlink '/etc/resolv.conf';
        if ( open( my $fh, '>', '/etc/resolv.conf' ) ) {
            print $fh "nameserver 1.1.1.1\nnameserver 8.8.8.8\n";
            close($fh);
        }
        else {
            Common::WARN( 'Could not create new /etc/resolv.conf : ' . $! );
        }
    }
}

1;

MMCT - 2023