#!/usr/local/cpanel/3rdparty/bin/perl
# Copyright 2026 WebPros International, LLC
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
package scripts::upcp;
BEGIN {
unshift @INC, q{/usr/local/cpanel};
# if we are being called with a compile check flag ( perl -c ), skip the begin block
# so we don't actually call upcp.static when just checking syntax and such is OK
return if $^C;
# static never gets --use-checked and should pass all the begin block checks
return if $0 =~ /\.static$/;
# let the '--use-check' instance compiled
if ( grep { $_ eq '--use-check' } @ARGV ) {
no warnings;
# dynamic definition of the INIT block
eval "INIT { exit(0); }";
return;
}
system("$0 --use-check >/dev/null 2>&1");
# compilation is ok with '--use-check', we will continue the non static version
return if $? == 0;
my $static = $0 . ".static";
if ( -f $static ) {
print STDERR "We determined that $0 had compilation issues..\n";
print STDERR "Trying to exec $static " . join( ' ', @ARGV ) . "\n";
exec( $^X, $static, @ARGV );
}
}
use cPstrict;
no warnings; ## no critic qw(ProhibitNoWarnings)
use Try::Tiny;
use Cpanel::OS::All (); # PPI USE OK -- make sure Cpanel::OS is embedded
use Cpanel::HiRes ( preload => 'perl' );
use Cpanel::Env ();
use Cpanel::Update::Config ();
use Cpanel::Update::IsCron ();
use Cpanel::Update::Logger ();
use Cpanel::Update::Tiers ();
use Cpanel::FileUtils::TouchFile ();
use Cpanel::LoadFile ();
use Cpanel::LoadModule ();
use Cpanel::Usage ();
use Cpanel::UPID ();
use IO::Handle ();
use POSIX ();
use Cpanel::Unix::PID::Tiny ();
my $pidfile = '/var/run/upcp.pid';
my $lastlog = '/var/cpanel/updatelogs/last';
my $upcp_disallowed_path = '/root/.upcp_controlc_disallowed';
my $version_upgrade_file = '/usr/local/cpanel/upgrade_in_progress.txt';
our $logger; # Global for logger object.
our $logfile_path;
my $now;
my $forced = 0;
my $fromself = 0;
my $sync_requested = 0;
my $bg = 0;
my $security = 0;
# Expedited package update state, decided before the fork and acted on in the
# child. $package_update_repos is a repository => token map covering every
# repository to update, so they all go into one transaction. $packages_only
# records that the package update is the only reason this run exists, so the
# child can stop before the version-upgrade machinery.
my $security_tiers;
my $package_update_repos;
my $packages_only = 0;
my $from_version;
my $pbar_starting_point;
exit( upcp() || 0 ) unless caller();
sub usage {
print <<EOS;
Usage: scripts/upcp [--bg] [--cron] [--force] [--help] [--log=[path]] [--security] [--sync]
Updates cPanel & WHM.
Options:
--bg Runs upcp in the background. Output is only visible in the log.
--cron Follow WHM's Update Preferences (/etc/cpupdate.conf).
--force Force a reinstall even if the system is up to date.
--help Display this documentation.
--log=[path] Overrides the default log file.
--security Apply expedited security updates, independent of the normal
update schedule: a security release for the current major
version, and packages from any repository cPanel has flagged as
carrying a fix. Either can apply on its own. The version upgrade
honors SECURITY_UPDATES in /etc/cpupdate.conf; the package
update honors RPMUP. Runs hourly from cron; a silent no-op
unless one of them applies.
--sync Updates the server to the currently-installed version instead of downloading a newer version.
This will also run other maintenance items, such as package updates and repairs.
You cannot use the --sync argument with the --force argument.
EOS
exit 1; ## no critic(Cpanel::NoExitsFromSubroutines) -- /scripts/upcp needs refactor to use Getopt
}
sub upcp { ## no critic(Subroutines::ProhibitExcessComplexity) - preserve original code
Cpanel::Usage::wrap_options( \@ARGV, \&usage, {} ); #display usage information on --help
open( STDERR, ">&STDOUT" ) or die $!;
local $| = 1;
umask(0022);
$now = time();
setupenv();
unset_rlimits();
#############################################################################
# Record the arguments used when started, check for certain flags
my $update_is_available_exit_code = 42;
my @retain_argv = @ARGV;
foreach my $arg (@ARGV) {
if ( $arg =~ m/^--log=(.*)/ ) {
$logfile_path = $1;
}
elsif ( $arg eq '--fromself' ) {
$fromself = 1;
}
elsif ( $arg eq '--force' ) {
$forced = 1;
$ENV{'FORCEDCPUPDATE'} = 1;
}
elsif ( $arg eq '--sync' ) {
$sync_requested = 1;
}
elsif ( $arg eq '--bg' ) {
$bg = 1;
}
elsif ( $arg eq '--security' ) {
$security = 1;
}
}
# Expedited security mode: one cheap TIERS check up front, answering two
# questions off the same fetch -- is there a security version to upgrade to,
# and have any repositories published a new package token. On the common hourly
# no-op we exit immediately -- before any fork, hook, log, or updatenow
# call. Only when one of them applies do we fall through into the normal
# flow, which fires the pre/post hooks, runs updatenow (with --security
# passed through), and -- because a version change is critical -- runs
# scripts/maintenance just like a normal upcp. The one thing --security
# skips is the post-upgrade re-poll for a newer version (it is a single
# same-major hop, not a tier chase).
#
# A packages-only run wants none of that: it forks and logs as usual, does
# the package update, and stops (see $packages_only below).
if ( $security && !$fromself ) {
# Never expedite anything during a fresh install. Redundant with the
# guards inside Cpanel::Update::Tiers, but strictly cheaper: no object
# construction and no HTTP probe on a path we should never reach.
return 0 if $ENV{'CPANEL_BASE_INSTALL'};
$security_tiers = Cpanel::Update::Tiers->new( 'logger' => 'disabled' );
# check_for_security_version returns early -- without syncing -- when
# SECURITY_UPDATES=never, so drive the (single, conditional) fetch here.
# Otherwise a server with the security channel turned off would never
# observe the package update tokens.
$security_tiers->tiers_hash( 'security_check' => 1 );
my $version_update = security_update_available($security_tiers);
# Already filtered down to repositories this server actually has
# configured, so a bumped repository it does not carry costs it nothing
# from here on -- no fork, no log file, no failed transaction.
$package_update_repos = $security_tiers->check_for_package_update();
return 0 if !$version_update && !$package_update_repos;
$packages_only = ( $package_update_repos && !$version_update ) ? 1 : 0;
}
if ( $sync_requested && $forced ) {
print "FATAL: --force and --sync are mutually exclusive commands.\n";
print " Force is designed to update your installed version, regardless of whether it's already up to date.\n";
print " Sync is designed to update the version already installed, regardless of what is available.\n";
return 1;
}
if ( $> != 0 ) {
die "upcp must be run as root";
}
#############################################################################
# Make sure easyapache isn't already running
my $upid = Cpanel::Unix::PID::Tiny->new();
if ( $upid->is_pidfile_running('/var/run/easyapache.pid') ) {
print "EasyApache is currently running. Please wait for EasyApache to complete before running cPanel Update (upcp).\n";
return 1;
}
#############################################################################
# Make sure we aren't already running && make sure everyone knows we are running
my $curpid = $upid->get_pid_from_pidfile($pidfile) || 0;
# In --security mode, yield silently to any other running upcp (e.g. the
# daily --cron). Never print "already running", never kill it; we simply
# re-check next hour.
if ( $security && $curpid && $curpid != $$ && $upid->is_pidfile_running($pidfile) ) {
return 0;
}
if ( $curpid && $curpid != $$ && !$fromself && -e '/var/cpanel/upcpcheck' ) {
my $pidfile_mtime = ( stat($pidfile) )[9];
my $pidfile_age = ( time - $pidfile_mtime );
if ( $pidfile_age > 21600 ) { # Running for > 6 hours
_logger()->warning("previous PID ($curpid) has been running more than 6 hours. Killing processes.");
kill_upcp($curpid); # the pid_file_no_cleanup() will exit if it is still stuck after this
sleep 1; # Give the process group time to die.
}
elsif ( my $logpath = _determine_logfile_path_if_running($curpid) ) {
print _message_about_already_running( $curpid, $logpath ) . "\n";
return 1;
}
}
if ( $curpid && $curpid != $$ && !$upid->is_pidfile_running($pidfile) ) {
print "Stale PID file '$pidfile' (pid=$curpid)\n";
}
if ( !$fromself && !$upid->pid_file_no_cleanup($pidfile) ) {
print "process is already running\n";
return 1;
}
# to indicate re-entry into upcp
$pbar_starting_point = $fromself ? 17 : 0;
# record current version
$from_version = fetch_cpanel_version();
#############################################################################
# Set up the upcp log directory and files
setup_updatelogs();
#############################################################################
# Fork a child to the background. The child does all the heavy lifting and
# logs to a file; the parent just watches, reads, and parses the log file,
# displaying what it gets.
#
# Note that the parent reads the log in proper line-oriented -- and buffered!
# -- fashion. An earlier version of this script did raw sysread() calls here,
# and had to deal with all the mess that that entailed. The current approach
# reaps all the benefits of Perl's and Linux's significant file read
# optimizations without needing to re-invent any of them. The parent loop
# below becomes lean, mean, and even elegant.
#
# Note in particular that we do not need to explicitly deal with an
# end-of-file condition (other than avoiding using undefined data). For
# exiting the read loop we merely need to test that the child has expired,
# which in any case is the only situation that can cause an eof condition for
# us on the file the child is writing.
#
# Note, too, that the open() needs to be inside this loop, in case the child
# has not yet created the file.
if ( !$fromself ) {
# we need to be sure that log an pid are the current one when giving back the end
unlink $lastlog if $bg;
if ( my $updatepid = fork() ) {
$logfile_path ||= _determine_logfile_path_if_running($updatepid);
if ($logger) { # Close if logged about killing stale process.
$logger->{'brief'} = 1; # Don't be chatty about closing
$logger->close_log;
}
if ($bg) {
print _message_about_newly_started( $updatepid, $logfile_path ) . "\n";
my $progress;
select undef, undef, undef, .10;
while ( !-e $lastlog ) {
print '.';
select undef, undef, undef, .25;
$progress = 1;
}
print "\n" if $progress;
}
else {
monitor_upcp($updatepid);
}
return;
}
else {
$logfile_path ||= _determine_logfile_path_if_running($$);
}
}
local $0 = 'cPanel Update (upcp) - Slave';
open( my $RNULL, '<', '/dev/null' ) or die "Cannot open /dev/null: $!";
chdir '/';
_logger(); # Open the log file.
#############################################################################
# Set CPANEL_IS_CRON env var based on detection algorithm
my $cron_reason = set_cron_env();
$logger->info("Detected cron=$ENV{'CPANEL_IS_CRON'} ($cron_reason)");
my $set_cron_method = $ENV{'CPANEL_IS_CRON'} ? 'set_on' : 'set_off';
Cpanel::Update::IsCron->$set_cron_method();
my $openmax = POSIX::sysconf( POSIX::_SC_OPEN_MAX() );
if ( !$openmax ) { $openmax = 64; }
foreach my $i ( 0 .. $openmax ) { POSIX::close($i) unless $i == fileno( $logger->{'fh'} ); }
POSIX::setsid();
open( STDOUT, '>', '/dev/null' ) or warn $!;
open( STDERR, '>', '/dev/null' ) or warn $!;
$logger->update_pbar($pbar_starting_point);
##############################################################################
# Symlink /var/cpanel/updatelogs/last to the current log file
unlink $lastlog;
symlink( $logfile_path, $lastlog ) or $logger->error("Could not symlink $lastlog: $!");
#############################################################################
# now that we have sporked: update our pidfile and ensure it is removed
unlink $pidfile; # so that pid_file() won't see it as running.
if ( !$upid->pid_file($pidfile) ) { # re-verifies (i.e. upcp was not also started after the unlink() and here) and sets up cleanup of $pidfile for sporked proc
$logger->error("Could not update pidfile “$pidfile” with BG process: $!\n");
return 1;
}
# Assuming we didn't get re-executed from a upcp change after updatenow (!$fromself).
# If the file is still there from a failed run, remove it.
unlink($upcp_disallowed_path) if !$fromself && -f $upcp_disallowed_path;
# make sure that the pid file is going to be removed when killed by a signal
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
unlink $pidfile;
if ($logger) {
$logger->close_log;
$logger->open_log;
$logger->error("User hit ^C or killed the process ( pid file '$pidfile' removed ).");
$logger->close_log;
}
return;
};
#############################################################################
# Expedited package update: TIERS.json published a new token for one or more
# repositories. Done before any version upgrade and regardless of whether one
# is also due -- the duplicate work is a near no-op for the package manager,
# while skipping it in favor of the upgrade's maintenance pre-action would
# skip the cache clean, which is the entire point of this channel.
if ($package_update_repos) {
run_expedited_package_update( $logger, $security_tiers, $package_update_repos );
if ($packages_only) {
$logger->info( "\n\n\tExpedited package update completed\n\n", 1 );
$logger->info("A log of this update is available at $logfile_path\n\n");
$logger->update_pbar(100);
unlink $pidfile;
$logger->close_log();
return 0;
}
}
#############################################################################
# Get variables needed for update
my $gotSigALRM = 0;
my $connecttimeout = 30;
my $liveconnect = 0;
my $connectedhost = q{};
my @HOST_IPs = ();
## Case 46528: license checks moved to updatenow and Cpanel::Update::Blocker
$logger->debug("Done getting update config variables..");
$logger->increment_pbar;
#############################################################################
# Run the preupcp hook
if ( -x '/usr/local/cpanel/scripts/preupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/preupcp");
system '/usr/local/cpanel/scripts/preupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=pre';
}
$logger->increment_pbar();
#############################################################################
# Check mtime on ourselves before sync
# This is the target for a goto in the case that the remote TIERS file is
# changed sometime during the execution of this upcp run. It prevents the
# need for a new script argument and re-exec.
STARTOVER:
my $mtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
$logger->info( "mtime on upcp is $mtime (" . scalar( localtime($mtime) ) . ")" );
# * If no fromself arg is passed, it's either the first run from crontab or called manually.
# * --force is passed to updatenow, has no bearing on upcp itself.
# * Even if upcp is changed 3 times in a row during an update (fastest builds ever?), we
# would never actually update more than once unless the new upcp script changed the logic below
if ( !$fromself ) {
# run updatenow to sync everything
# updatenow expects --upcp to be passed or will error out
my @updatenow_args = ( '/usr/local/cpanel/scripts/updatenow', '--upcp', "--log=$logfile_path" );
# if --forced was received, pass it on to updatenow
if ($forced) { push( @updatenow_args, '--force' ); }
# if --sync was received, pass it on to updatenow. --force makes --sync meaningless.
if ( !$forced && $sync_requested ) { push( @updatenow_args, '--sync' ); }
# In --security mode updatenow makes the authoritative is_main/security
# decision and selects the current-major target.
if ($security) { push( @updatenow_args, '--security' ); }
# This is the point of no return, we are upgrading
# and its no longer abortable.
# set flag to disallow ^C during updatenow
Cpanel::FileUtils::TouchFile::touchfile($upcp_disallowed_path) or $logger->warn("Failed to create: $upcp_disallowed_path: $!");
# call updatenow, if we get a non-zero status, die.
my $exit_code = system(@updatenow_args);
$logger->increment_pbar(15);
if ( $exit_code != 0 ) {
my $signal = $exit_code % 256;
$exit_code = $exit_code >> 8;
analyze_and_report_error(
#success_msg => undef,
error_msg => "Running `@updatenow_args` failed, exited with code $exit_code (signal = $signal)",
type => 'upcp::UpdateNowFailed',
exit_status => $exit_code,
extra => [
'signal' => $signal,
'updatenow_args' => \@updatenow_args,
],
);
return ($exit_code);
}
# get the new mtime and compare it, if upcp changed, let's run ourselves again.
# this should be a fairly rare occasion.
my $newmtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
if ( $newmtime ne $mtime ) {
#----> Run our new self (and never come back).
$logger->info("New upcp detected, restarting ourself");
$logger->close_log();
exec '/usr/local/cpanel/scripts/upcp', @retain_argv, '--fromself', "--log=$logfile_path";
}
}
#############################################################################
# Run the maintenance script
my $last_logfile_position;
my $save_last_logfile_position = sub {
$last_logfile_position = int( qx{wc -l $logfile_path 2>/dev/null} || 0 );
};
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance script
my $exit_status;
my $version_change_happened = -e $version_upgrade_file;
if ($version_change_happened) {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--pre', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=30' );
}
else {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=95' );
}
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Pre Maintenance completed successfully",
error_msg => "Pre Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Run post-sync cleanup only if updatenow did a sync
# Formerly run after layer2 did a sync.
if ($version_change_happened) {
# post_sync pbar range: 30%-55%
$logger->close_log(); # Yield the log to post_sync_cleanup
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the post_sync_cleanup script
my $post_exit_status = system( '/usr/local/cpanel/scripts/post_sync_cleanup', '--log=' . $logfile_path, '--pbar-start=30', '--pbar-stop=55' );
$logger->open_log; # reopen the log to continue writing messages
analyze_and_report_error(
success_msg => "Post-sync cleanup completed successfully",
error_msg => "Post-sync cleanup has ended, however it did not exit cleanly. Please check the logs for an indication of what happened",
type => 'upcp::PostSyncCleanupFailed',
exit_status => $post_exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
unlink $version_upgrade_file;
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
# Maintenance pbar range: 55-95%
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance --post
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--post', '--log=' . $logfile_path, '--pbar-start=55', '--pbar-stop=95' );
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Post Maintenance completed successfully",
error_msg => "Post Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Check for new version... used when updating to next LTS version.
# Skipped for --security: an expedited security update is a single
# same-major hop, so we must not re-poll updatenow and chase a further
# version upgrade. Maintenance and hooks above still run regardless.
if ( !$security ) {
$logger->info("Polling updatenow to see if a newer version is available for upgrade");
$logger->close_log(); # Yield the log to updatenow
my $update_available = system( '/usr/local/cpanel/scripts/updatenow', "--log=$logfile_path", '--checkremoteversion' );
$logger->open_log; # reopen the log to continue writing messages
if ( !$sync_requested && $update_available && ( $update_available >> 8 ) == $update_is_available_exit_code ) {
$logger->info("\n\n/!\\ - Next LTS version available, restarting upcp and updating system. /!\\\n\n");
$fromself = 0;
goto STARTOVER;
}
}
}
else {
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
}
#############################################################################
# Run the post upcp hook
$logger->update_pbar(95);
if ( -x '/usr/local/cpanel/scripts/postupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/postupcp");
system '/usr/local/cpanel/scripts/postupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=post';
}
close($RNULL);
#############################################################################
# All done.
#############################################################################
$logger->update_pbar(100);
$logger->info( "\n\n\tcPanel update completed\n\n", 1 );
$logger->info("A log of this update is available at $logfile_path\n\n");
# this happens on exit so it shouldn't be necessary
$logger->info("Removing upcp pidfile");
unlink $pidfile if -f $pidfile || $logger->warn("Could not delete pidfile $pidfile : $!");
my $update_blocks_fname = '/var/cpanel/update_blocks.config';
if ( -s $update_blocks_fname ) {
$logger->warning("NOTE: A system upgrade was not possible due to the following blockers:\n");
if ( open( my $blocks_fh, '<', $update_blocks_fname ) ) {
while ( my $line = readline $blocks_fh ) {
my ( $level, $message ) = split /,/, $line, 2;
# Not using the level in the log, cause the logger can emit additional messages
# on some of the levels used (fatal emits an 'email message', etc)
# Remove URL from log output. Make sure message is defined.
if ($message) {
$message =~ s/<a.*?>//ig;
$message =~ s{</a>}{}ig;
}
$logger->warning( uc("[$level]") . " - $message" );
}
}
else {
$logger->warning("Unable to open blocks file! Please review '/var/cpanel/update_blocks.config' manually.");
}
}
else {
$logger->info("\n\nCompleted all updates\n\n");
}
$logger->close_log();
return 0;
}
#############################################################################
######[ Subroutines ]########################################################
#############################################################################
sub analyze_and_report_error {
my %info = @_;
my $type = $info{type} or die;
my $exit_status = $info{exit_status};
if ( $exit_status == 0 ) {
if ( defined $info{success_msg} ) {
$logger->info( $info{success_msg} );
}
return;
}
my $msg = $info{error_msg} or die;
my @extra;
if ( ref $info{extra} ) {
@extra = @{ $info{extra} };
}
my $logfile_content = Cpanel::LoadFile::loadfile_r($logfile_path);
# add events to the end of the error log
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::Logs::ErrorEvents") } ) ) {
my ($events) = Cpanel::Logs::ErrorEvents::extract_events_from_log( log => $logfile_content, after_line => $info{last_logfile_position} );
if ( $events && ref $events && scalar @$events ) {
my $events_str = join ', ', map { qq["$_"] } @$events;
$events_str = qq[The following events were logged: ${events_str}.];
$msg =~ s{(Please check)}{${events_str} $1} or $msg .= ' ' . $events_str;
}
}
$logger->error( $msg, 1 );
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::$type") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => $type,
'application' => $type,
'constructor_args' => [
'exit_code' => $exit_status,
'events_after_line' => $info{last_logfile_position},
@extra,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => $logfile_content, 'number_of_preview_lines' => 25 } ]
]
);
}
elsif (
!try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => 'cPanel & WHM update failure (upcp)',
'message' => $msg,
);
}
)
) {
$logger->error('Failed to send contact message');
}
return 1;
}
#############################################################################
sub kill_upcp {
my $pid = shift or die;
my $status = shift || 'hanging';
my $msg = shift || "/usr/local/cpanel/scripts/upcp was running as pid '$pid' for longer than 6 hours. cPanel will kill this process and run a new upcp in its place.";
# Attempt to notify admin of the kill.
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::upcp::Killed") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => 'upcp::Killed',
'application' => 'upcp::Killed',
'constructor_args' => [
'upcp_path' => '/usr/local/cpanel/scripts/upcp',
'pid' => $pid,
'status' => $status,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => Cpanel::LoadFile::loadfile_r($logfile_path), 'number_of_preview_lines' => 25 } ]
]
);
}
else {
try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => "cPanel update $status",
'message' => $msg,
);
}
);
}
print "Sending kill signal to process group for $pid\n";
kill -1, $pid; # Kill the process group
for ( 1 .. 60 ) {
print "Waiting for processes to die\n";
waitpid( $pid, POSIX::WNOHANG() );
last if ( !kill( 0, $pid ) );
sleep 1;
}
if ( kill( 0, $pid ) ) {
print "Could not kill upcp nicely. Doing kill -9 $pid\n";
kill 9, $pid;
}
else {
print "Done!\n";
}
return;
}
#############################################################################
sub setupenv {
Cpanel::Env::clean_env();
delete $ENV{'DOCUMENT_ROOT'};
delete $ENV{'SERVER_SOFTWARE'};
if ( $ENV{'WHM50'} ) {
$ENV{'GATEWAY_INTERFACE'} = 'CGI/1.1';
}
( $ENV{'USER'}, $ENV{'HOME'} ) = ( getpwuid($>) )[ 0, 7 ];
$ENV{'PATH'} .= ':/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/bin';
$ENV{'LANG'} = 'C';
$ENV{'LC_ALL'} = 'C';
}
sub unset_rlimits {
# This is required if upcp started running from a pre-1132
eval {
local $SIG{__DIE__};
require Cpanel::Rlimit;
Cpanel::Rlimit::set_rlimit_to_infinity();
};
}
#############################################################################
sub setup_updatelogs {
return if ( -d '/var/cpanel/updatelogs' );
unlink('/var/cpanel/updatelogs');
mkdir( '/var/cpanel/updatelogs', 0700 );
}
# Updates the repositories TIERS.json has flagged, as one transaction, and
# stamps their tokens only once it has succeeded. $repos is the whole
# repository => token map from check_for_package_update.
sub run_expedited_package_update {
my ( $logger, $tiers, $repos ) = @_;
my @repos = sort keys %$repos;
my $named = join ', ', @repos;
# CPANEL_IS_CRON is already set for this run, so this honors RPMUP=manual for
# the hourly cron and treats a hand run as the manual run it is.
# load() returns a flattened hash in list context, which an argument
# position is, so it has to be put in a scalar first for is_permitted to
# see a hashref at all.
my $update_conf = Cpanel::Update::Config::load();
if ( !Cpanel::Update::Config::is_permitted( 'RPMUP', $update_conf ) ) {
# Leave the tokens unrecorded: they stay pending, so re-enabling
# operating system package updates picks them up on the next run rather
# than having silently missed them.
$logger->info("An expedited package update is available for $named, but operating system package updates are disabled. Skipping the package update.");
return 0;
}
$logger->info("An expedited package update is available. Updating packages from $named.");
# Contained deliberately. Loading Cpanel::Update::Packages pulls in the
# package manager stack, so a broken or half-synced tree can make this throw
# rather than return false. Either way the tokens are left unstamped and the
# next hourly check retries, by which point the daily update has likely
# repaired whatever was wrong -- and a version upgrade due in this same run
# must not be abandoned over a failed package update.
my $updated = try {
# The one deferred load in this script, and the only reason
# Cpanel::Update::Packages is a module rather than a sub in here.
# Loading it loads Cpanel::SysPkgs, which unconditionally pulls in all
# three package manager backends -- ~110 modules, among them the XS
# Proc::FastSpawn. upcp runs hourly on every server just to ask whether
# there is anything to do, and the answer is almost always no, so the
# load waits until we know there is. The split line keeps
# Cpanel::ModuleDeps from seeing it, which is what keeps the stack out
# of scripts/upcp.static as well as out of the hourly run.
require # Hide Depend -- not needed in upcp.static
Cpanel::Update::Packages;
Cpanel::Update::Packages::update_repos( 'repos' => \@repos, 'clean' => 1, 'logger' => $logger );
}
catch {
$logger->warning("The expedited package update could not run: $_");
0;
};
if ( !$updated ) {
$logger->warning('The expedited package update failed. The system will try again on the next update check.');
return 0;
}
# Stamp the whole set at once, and only once the update has succeeded, so a
# failure is retried on the next check instead of being silently marked done.
$tiers->record_packages_updated($repos);
return 1;
}
# Cheap up-front check for --security mode: is an expedited security update
# available for the current major version? A single Cpanel::Update::Tiers
# lookup (which self-syncs TIERS.json when its cache is stale and fails
# soft) lets the hourly cron exit immediately on the common no-op without
# spinning up updatenow. The authoritative decision and target selection
# still happen in Cpanel::Update::Now under --security when we proceed.
sub security_update_available {
# Optionally share the caller's object (and therefore its single, already
# paid for TIERS.json fetch); construct one when called on its own.
my $tiers = shift;
my $installed = fetch_cpanel_version() or return 0;
# Cpanel::Update::Tiers owns the entire skip decision: the
# SECURITY_UPDATES=never config gate, the fresh-install guard, the
# explicit-pin opt-out, is_main resolution, the security flag, and the
# != installed check. tiers_hash() self-syncs when its cache is stale
# and fails soft.
$tiers ||= Cpanel::Update::Tiers->new( 'logger' => 'disabled' );
return defined $tiers->check_for_security_version($installed) ? 1 : 0;
}
sub set_cron_env {
# Do not override the env var if set.
return 'env var CPANEL_IS_CRON was present before this process started.' if ( defined $ENV{'CPANEL_IS_CRON'} );
if ( grep { $_ eq '--cron' } @ARGV ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'cron mode set from command line';
}
if ( $ARGV[0] eq 'manual' ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'manual flag passed on command line';
}
if ($forced) {
$ENV{'CPANEL_IS_CRON'} = 0;
return '--force passed on command line';
}
if ( -t STDOUT ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'Terminal detected';
}
if ( $ENV{'SSH_CLIENT'} ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'SSH connection detected';
}
# cron sets TERM=dumb
if ( $ENV{'TERM'} eq 'dumb' ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'TERM detected as set to dumb';
}
# Check if parent is whostmgr
if ( readlink( '/proc/' . getppid() . '/exe' ) =~ m/whostmgrd/ ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'parent process is whostmgrd';
}
# Default to cron enabled.
$ENV{'CPANEL_IS_CRON'} = 1;
return 'default';
}
#############################################################################
sub fetch_cpanel_version {
my $version;
my $version_file = '/usr/local/cpanel/version';
return if !-f $version_file;
my $fh;
local $/ = undef;
return if !open $fh, '<', $version_file;
$version = <$fh>;
close $fh;
$version =~ s/^\s+|\s+$//gs;
return $version;
}
#############################################################################
sub monitor_upcp {
my $updatepid = shift or die;
$0 = 'cPanel Update (upcp) - Master';
$SIG{INT} = $SIG{TERM} = sub {
print "User hit ^C\n";
if ( -f $upcp_disallowed_path ) {
print "Not allowing upcp slave to be killed during updatenow, just killing monitoring process.\n";
exit;
}
print "killing upcp\n";
kill_upcp( $updatepid, "aborted", "/usr/local/cpanel/scripts/upcp was aborted by the user hitting Ctrl-C." );
exit;
};
$SIG{HUP} = sub {
print "SIGHUP detected; closing monitoring process.\n";
print "The upcp slave has not been affected\n";
exit;
};
# Wait till the file shows up.
until ( -e $logfile_path ) {
select undef, undef, undef, .25; # sleep just a bit
}
# Wait till we're allowed to open it.
my $fh;
until ( defined $fh && fileno $fh ) {
$fh = IO::Handle->new();
if ( !open $fh, '<', $logfile_path ) {
undef $fh;
select undef, undef, undef, .25; # sleep just a bit
next;
}
}
# Read the file until the pid dies.
my $child_done = 0;
while (1) {
# Read all the available lines.
while (1) {
my $line = <$fh>;
last if ( !defined $line || $line eq '' );
print $line;
}
# Once the child is history, we need to do yet one more final read,
# on the off chance (however remote) that she has written one last
# hurrah after we last checked. Hence the following.
last if $child_done; # from prev. pass
$child_done = 1 if -1 == waitpid( $updatepid, 1 ); # and loop back for one more read
select undef, undef, undef, .25; # Yield idle time to the cpu
}
close $fh if $fh;
exit;
}
sub _logger {
return $logger if $logger;
$logger = Cpanel::Update::Logger->new( { 'logfile' => $logfile_path, 'stdout' => 1, 'log_level' => 'info' } );
# do not set the pbar in the constructor to do not display the 0 % in bg mode
$logger->{pbar} = $pbar_starting_point;
return $logger;
}
sub _determine_logfile_path_if_running ($pid) {
my $upid = Cpanel::UPID::get($pid);
return $upid ? "/var/cpanel/updatelogs/update.$upid.log" : undef;
}
#----------------------------------------------------------------------
# HANDLE WITH CARE!! This string is parsed
# in at least one place. (cf. Cpanel::Update::Start)
sub _message_about_newly_started ( $updatepid, $logfile_path ) {
return "upcp is going into background mode (PID $updatepid). You can follow “$logfile_path” to watch its progress.";
}
#----------------------------------------------------------------------
# HANDLE WITH CARE!! This string is parsed
# in at least one place. (cf. Cpanel::Update::Start)
sub _message_about_already_running ( $curpid, $logpath ) {
return "cPanel Update (upcp) is already running. Please wait for the previous upcp (PID $curpid, log file “$logpath”) to complete, then try again. You can use the command 'ps --pid $curpid' to check if the process is running. You may wish to use '--force'.";
}
1;
Rosenblum TV: Video training, virtual workshops, classes, tutorials
RE-INVENTING THE TELEVISION NEWS BUSINESS*
A revolution in video storytelling
Creating entirely new & cost-effective production methods
From the world leaders in video production training and the creators of Character Driven Storytelling™
*and every other business that uses video
WHAT WE DO
Over the past 35 years, we have designed, built or restructured some of the most powerful news and journalism companies in the world.
We replace the traditional TV news ‘crew’ with one highly trained journalist, working alone with nothing but an iPhone.
No more TV news ‘crews’, no editors and no field producers.
This is television news done the way newspaper journalism is done – one reporter with their electronic pad and pencil.
In doing this, we can cut the cost of production by as much as 75% while increasing ratings and audience engagement.
In the place of conventional TV news ‘packages’ – ie, reporter stand up, interview, b-roll, man on the street, we marry great journalism with Netflix and Hollywood storytelling.
It’s a combination that works.
We have taken most of our clients to #1 in their respective markets.
And it’s not just for news. Any company, any profit, any NGO and anyone else who is online needs to tell their story in compelling yet cost-effective video. We can teach you to do that. Either in person or virtually.
EXAMPLES OF WHAT WE CAN TEACH YOUR STAFF TO PRODUCE
ITAY HOD
Itay Hod, MMJ with KPIX/CBS in San Francisco, took the 5-Day Intensive Video Storytelling Bootcamp in 2018.
Because he works alone, with only an iPhone, he was able to embed himself with a homeless family.
Here’s the story he produced in a one-day turn.
KIET DO
Kiet Do, an MMJ with KPIX/CBS in San Francisco, took the 5-Day Intensive Video Storytelling Bootcamp in 2021.
Here is a story he produced, all on his own, with only an iPhone and in a one-day turn.
TAYLOR SCHAUB
Taylor Schaub, an MMJ with Spectrum News 1 in LA, took the 5-Day Intensive Video Storytelling Bootcamp in 2023.
Here is a story he turned in only one day, using only an iPhone. It was the first video story he ever did and it was nominated for an Emmy.
THE BOOTCAMP
How do we convert stations and whole networks to working in this way?
Since 1988, we have run intensive 5-Day Video Storytelling Bootcamps
We have done these all over the world.
These are hands-on bootcamps, and participants learn an entirely new way of creating TV news stories.
-We shoot at a 3:1 ratio or lower, so turnaround times are fast.
-We go directly from camera to timelilne and edit – no written scripts. We work in the medium of pictures and sound.
-We are entirely character-driven.
-We are driven by pictures and real events.
-We are focused almost entirely on ’the viewer experience’.
Since 1988, more than 70,000 journalists around the world have taken our bootcamps, either in person on virtualy.
We have started to work with CBS News, bringing our ideas of character-driven storytelling to one of the most successful and biggest networks in the United States. Since beginning to work with them ratings have climbed and more importantly, audience engagement is through the ceiling.
We started New York Times Television in 1990 and it was the first paper to be brought into the world of TV. It quickly became one of the most successful non-fiction production companies in the United States. The series and documentaries we produced won many awards including multiple Emmys.
We have been working with the BBC since the year 2000 helping to convert their national news network to our visual storytelling technique. Most recently we have trained teams from their sports, documentaries, and comedy divisions to make character-driven stories using only smartphones.
For the past five years we have worked with Spectrum News to introduce and train their journalists on visual, character driven storytelling using smartphones helping to create a different kind of local news for their network of 24-Hour News Stations across the United States.
In 2006, we were approached by the United Nations. Rather than rely on news outlets, it would be much easier to train the field operatives to produce their own stories. We spent two years working with the UN, training more than 100 of their staff in bootcamps in Geneva and Nairobi.
We trained 50 print reporters at the paper to shoot and tell their own stories, in conjunction with their print work. We built a TV newsroom in their existing print newsroom – you could not ask for a better set and they began to live stream their stories in conjunction with their print work.
We spent two years with McGraw Hill, training more than 150 of their staffers, making them completely video literate. McGraw/Hill media properties we transit included Business Week, Aviation Week, (what was the name of the architecture magazine), and JD Power and Associates.
In 1990, we were approached by The Voice of America, the official broadcasting agency for the United States Government. When we met with VOA, they were only a short wave radio broadcaster, but working with them, we took them into television, launching VOA-TV.
British based Oyster Yachts makes some of the finest yachts in the world. Like every other company, they had to find a way to feed the never-ending video demands of social media – sites like Instagram and TikTok. We trained the Oyster staff to tell their own stories, using only iPhones.
We were approached by SEPA, the Scottish Environmental Protection Agency because they had to continually find a way to ‘feed the media beast’. The result was that SEPA was able to tell their own stories, whenever they wanted, and at almost no additional cost.
Michael Rosenblum has been writing about the media since 1988. His work and ideas have appeared in The Guardian, The Huffington Post, Ilkeston Life and many other publications.
He has been blogging regularly for the past 35 years on this subject. Having taught media studies at Columbia University, NYU and now the University of Oxford, he is considered an expert on this subject.
Continue reading this post or look back at previous posts.