[ SEA-GHOST MINI SHELL]
#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - ea_current_to_profile Copyright(c) 2022 cPanel, Inc.
# 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;
package ea_cpanel_tools::ea_current_to_profile;
use Cpanel::PackMan;
use Cpanel::Config::Httpd;
use Cpanel::JSON;
use Cpanel::Time::Local;
use Path::Tiny 'path';
our $ea4_profiles_dir = '/etc/cpanel/ea4/profiles';
our $_max_attempts = 20;
our $manifest_file = "/etc/cpanel/ea4/profiles/pkg-manifest.json";
our $ea4_metainfo_file = '/etc/cpanel/ea4/ea4-metainfo.json';
our %safe_to_ignore = (
'ea-brotli' => 1,
'ea-brotli-devel' => 1,
'ea-libargon2' => 1,
'ea-libargon2-devel' => 1,
'ea-libcurl' => 1,
'ea-libcurl-devel' => 1,
'ea-libnghttp2' => 1,
'ea-libxml2' => 1,
'ea-libzip' => 1,
'ea-nghttp2' => 1,
'ea-oniguruma' => 1,
'ea-oniguruma-devel' => 1,
'ea-openssl11' => 1,
'ea-openssl11-devel' => 1,
'ea-openssl11-libs' => 1,
'ea-openssl' => 1,
'ea-openssl-devel' => 1,
'ea-openssl-libs' => 1,
'ea-php73-libc-client' => 1,
'ea-php74-libc-client' => 1,
'ea-php80-libc-client' => 1,
'ea-php81-libc-client' => 1
);
our %os_type = (
"CentOS_7" => "rpm",
"CentOS_8" => "rpm",
"CentOS_9" => "rpm",
"xUbuntu_20.04" => "deb",
"xUbuntu_22.04" => "deb",
);
exit( script(@ARGV) ) unless caller();
sub script {
my (@args) = @_;
my $custom_json;
my $new_name;
my $target_os;
my $manifest_os;
my $modified = 0;
my $source_pkmgr = -x '/usr/bin/apt' ? 'deb' : 'rpm';
# We need to get OS aliases
my %ea4_metainfo = %{ Cpanel::JSON::LoadFile($ea4_metainfo_file) };
my %obs_project_aliases = %{ $ea4_metainfo{obs_project_aliases} };
die "obs_project_aliases is empty" if ( !%obs_project_aliases );
foreach my $arg (@args) {
if ( $arg eq "--help" ) {
my $os_string = join( ' ', keys %obs_project_aliases );
print <<USAGE;
$0 [--help] [--output=profile_file] [--target-os=os]
Take the current ea rpms and create a custom profile.
Profile is written to /etc/cpanel/ea4/profiles/custom
--output=profile_file, where the profile is written to.
Note: forcefully overwrites the profile_file if it already
exists.
--target-os=os [$os_string]
USAGE
exit 0;
}
elsif ( $arg =~ m/^--output=(.*)$/ ) {
$custom_json = $1;
my @path = split( /\//, $custom_json );
$new_name = $path[-1];
}
elsif ( $arg =~ m/^--target-os=(.*)$/ ) {
$target_os = $1;
die "Not a valid OS ($target_os)" if !exists $obs_project_aliases{$target_os};
$manifest_os = $obs_project_aliases{$target_os};
}
else {
die "Unknown argument “$arg”\n";
}
}
my @addl_prefixes = eval {
map { $_->basename } path("/etc/cpanel/ea4/additional-pkg-prefixes/")->children;
};
die "May only be run if you are using EasyApache 4" if ( !Cpanel::Config::Httpd::is_ea4() );
my @pkgs_have = Cpanel::PackMan->instance->list( state => "installed", 'prefix' => 'ea-' );
@pkgs_have = grep !/ea-profiles-cpanel/, @pkgs_have;
my $prefix_piped = "ea";
for my $prefix (@addl_prefixes) {
push @pkgs_have, Cpanel::PackMan->instance->list( state => "installed", 'prefix' => "$prefix-" );
for my $ig ( keys %safe_to_ignore ) {
$ig =~ s/^ea-/$prefix-/;
$safe_to_ignore{$ig} = 1;
}
$prefix_piped .= "|$prefix";
}
my $prefix_qr = qr/(?:$prefix_piped)/;
my $custom_dir = $ea4_profiles_dir . "/custom";
mkdir $custom_dir if !-d $custom_dir;
die "Cannot create $custom_dir" if !-d $custom_dir;
my @tags;
# this heuristic is fragile but at least attempts to create tags
foreach my $pkg (@pkgs_have) {
if ( $pkg =~ m/^($prefix_qr)-apache(\d)(\d)$/ ) { push( @tags, "Apache $2.$3 ($1)" ); }
if ( $pkg =~ m/^($prefix_qr)-php(\d)(\d)$/ ) { push( @tags, "PHP $2.$3 ($1)" ); }
if ( $pkg =~ m/^($prefix_qr)-php(\d)(\d)-opcache$/ ) { push( @tags, "PHP $2.$3 OpCache ($1)" ); }
if ( $pkg =~ m/^($prefix_qr)-apache(\d)(\d).mod.(mpm_.*)$/ ) { push( @tags, "MPM $2.$3 $4 ($1)" ); }
if ( $pkg =~ m/^($prefix_qr)-apache(\d)(\d).mod.(ruid.*)$/ ) { push( @tags, "$4 $2.$3 ($1)" ); }
}
my %extra_meta;
if ($target_os) {
$extra_meta{os_upgrade} = {
source_os => scalar( _get_src_os() ),
target_os => $target_os,
target_obs_project => $manifest_os,
dropped_pkgs => {}, # do it here so it is included even when there are no packages dropped
};
my %manifest = %{ Cpanel::JSON::LoadFile($manifest_file) };
my %experimental = map { _normalize_pkg($_) => 1 } @{ $manifest{'EA4-experimental'}->{$manifest_os} };
# merge the repos to make lookup easy
my %lookup;
foreach my $repo ( keys %manifest ) {
foreach my $pkg ( @{ $manifest{$repo}->{$manifest_os} } ) {
my $normalized_pkg = _normalize_pkg($pkg);
$lookup{$normalized_pkg} = 1;
}
}
my @output_pkgs;
my @removed_pkgs;
foreach my $pkg (@pkgs_have) {
my $npkg = _normalize_pkg($pkg);
if ( !exists $lookup{$npkg} || exists $experimental{$npkg} ) {
push( @removed_pkgs, $npkg );
$modified = 1;
}
else {
push( @output_pkgs, $pkg );
}
}
if (@removed_pkgs) {
my $flag = 0;
foreach my $pkg (@removed_pkgs) {
if ( !exists $safe_to_ignore{$pkg} && substr( $pkg, -10, 10 ) ne '-debuginfo' ) {
print "The following packages are not available on $target_os and have been removed from the profile\n" if !$flag;
print " $pkg";
print " (EA4-experimental packages are not preserved during OS upgrades)" if ( exists $experimental{$pkg} );
print "\n";
$flag = 1;
$extra_meta{os_upgrade}{dropped_pkgs}{$pkg} = exists $experimental{$pkg} ? "exp" : "reg";
}
}
print "\n" if $flag;
}
@pkgs_have = @output_pkgs if (@removed_pkgs);
}
if ( !defined $custom_json ) {
my $ts = Cpanel::Time::Local::localtime2timestamp();
$new_name = "Current EA4 State at " . $ts;
my $fs_ts = substr( $ts, 0, 19 );
$fs_ts =~ s/ /_/g;
$custom_json = $custom_dir . "/" . "current_state_at_" . $fs_ts . ".json";
if ( $modified && $target_os ) {
$new_name .= " modified for $target_os" if ( $modified && $target_os );
my $xtarget_os = "_modified_for_$target_os";
$xtarget_os =~ s/ /_/g;
$custom_json = $custom_dir . "/" . "current_state_at_" . $fs_ts . $xtarget_os . ".json";
}
}
my $custom_profile = {
name => $new_name,
desc => "Auto Generated profile",
version => "1.0",
pkgs => \@pkgs_have,
tags => \@tags,
%extra_meta,
};
path($custom_json)->spew( Cpanel::JSON::pretty_dump($custom_profile) );
die "Could not create a new custom json file" if ( !-f $custom_json );
print $custom_json;
return 0; # return is exit status
}
###############
#### helpers ##
###############
sub _get_src_os {
my $src_os = eval { require Cpanel::OS; Cpanel::OS::display_name(); };
$src_os //= `grep PRETTY_NAME /etc/os-release | sed 's/^PRETTY_NAME=//' | tr -d '"' | tr -d "'"`;
chomp $src_os;
$src_os ||= 'Unknown. Server does not have Cpanel::OS or /etc/os-release (or /etc/os-release does not have PRETTY_NAME=…)';
return $src_os;
}
sub _normalize_pkg {
my ($pkg) = @_;
$pkg =~ s/_/-/g;
return $pkg;
}
1;
SEA-GHOST - SHELL CODING BY SEA-GHOST