magento cron schedule not working, no heartbeat - php

I have inherited a magento site and the cron schedule does not work. It was set in magento with */5**** and the same in cpanel. I installed AOE Scheduler and all the emails that had been stuck in the queue came through but now I just get a message "Last heartbeat is older than XX". If I run the scheduled tasks manually the emails come through but I get a "405 Error - Not Allowed" page and I find all my file permissions have been changed to 666 (the number of the beast). I have been setting my file permissions to d - 755 and f - 644 but depending on which forum you read this could be wrong. Please help, I have been on this for days.
This is my cron.php
<?php
// Change current directory to the directory of current script
chdir(dirname(__FILE__));
require 'app/Mage.php';
if (!Mage::isInstalled()) {
echo "Application is not installed yet, please complete install wizard first.";
exit;
}
// Only for urls
// Don't remove this
$_SERVER['SCRIPT_NAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_NAME']);
$_SERVER['SCRIPT_FILENAME'] = str_replace(basename(__FILE__), 'index.php', $_SERVER['SCRIPT_FILENAME']);
Mage::app('admin')->setUseSessionInUrl(false);
umask(0);
$disabledFuncs = explode(',', ini_get('disable_functions'));
$isShellDisabled = is_array($disabledFuncs) ? in_array('shell_exec', $disabledFuncs) : true;
$isShellDisabled = (stripos(PHP_OS, 'win') === false) ? $isShellDisabled : true;
try {
if (stripos(PHP_OS, 'win') === false) {
$options = getopt('m::');
if (isset($options['m'])) {
if ($options['m'] == 'always') {
$cronMode = 'always';
} elseif ($options['m'] == 'default') {
$cronMode = 'default';
} else {
Mage::throwException('Unrecognized cron mode was defined');
}
} else if (!$isShellDisabled) {
$fileName = basename(__FILE__);
$baseDir = dirname(__FILE__);
shell_exec("/bin/sh $baseDir/cron.sh $fileName -mdefault 1 > /dev/null 2>&1 &");
shell_exec("/bin/sh $baseDir/cron.sh $fileName -malways 1 > /dev/null 2>&1 &");
exit;
}
}
Mage::getConfig()->init()->loadEventObservers('crontab');
Mage::app()->addEventArea('crontab');
if ($isShellDisabled) {
Mage::dispatchEvent('always');
Mage::dispatchEvent('default');
} else {
Mage::dispatchEvent($cronMode);
}
} catch (Exception $e) {
Mage::printException($e);
exit(1);
}
This is my cron.sh
#!/bin/sh
# location of the php binary
if [ ! "$1" = "" ] ; then
CRONSCRIPT=$1
else
CRONSCRIPT=cron.php
fi
MODE=""
if [ ! "$2" = "" ] ; then
MODE=" $2"
fi
PHP_BIN=`which php`
# absolute path to magento installation
INSTALLDIR=`echo $0 | sed 's/cron\.sh//g'`
# prepend the intallation path if not given an absolute path
if [ "$INSTALLDIR" != "" -a "`expr index $CRONSCRIPT /`" != "1" ];then
if ! ps auxwww | grep "$INSTALLDIR$CRONSCRIPT$MODE" | grep -v grep 1>/dev/null 2>/dev/null ; then
$PHP_BIN $INSTALLDIR$CRONSCRIPT$MODE &
fi
else
if ! ps auxwww | grep "$CRONSCRIPT$MODE" | grep -v grep | grep -v cron.sh 1>/dev/null 2>/dev/null ; then
$PHP_BIN $CRONSCRIPT$MODE &
fi
fi

Related

PHP: proc_get_status exitcode is always -1 after pcntl_fork

I'm having troubles with using proc_get_status after a pcntl_fork in the parent process.
Here's an example with docker and PHP 7.4, but note the PHP version does not matter, the result is the same from at least PHP 7.2 to PHP 8.1.
Setup
Dockerfile
FROM php:7.4
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions pcntl
Run: docker build -t php-pcntl-7.4 .
test.php
<?php
if ($argv[1] ?? false) {
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();
if ($pid === -1) {
throw new \RuntimeException('fork failed');
}
if ($pid === 0) {
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
throw new \Exception('child should be ignored');
}
pcntl_waitpid($pid, $childStatusCode);
}
function runCommand(string $command): void
{
$pipes = [];
$proc = proc_open($command, [['pipe', 'r'],['pipe', 'w'],['pipe', 'w']], $pipes);
do {
if (isset($status)) {
usleep(300000);
}
$status = proc_get_status($proc);
} while ($status['running']);
echo 'Command: ' . $command . PHP_EOL;
echo 'last proc_get_status exitcode: ' . $status['exitcode'] . PHP_EOL;
echo PHP_EOL;
}
echo 'PHP version: ' . PHP_VERSION . PHP_EOL;
runCommand('/bin/echo test');
runCommand('/bin/false');
Test without running a fork before
Run: docker run -it --rm -v $PWD:/workdir -w /workdir php-pcntl-7.4 php test.php
Output:
PHP version: 7.4.28
Command: /bin/echo test
last proc_get_status exitcode: 0
Command: /bin/false
last proc_get_status exitcode: 1
Test with running a fork before
Run: docker run -it --rm -v $PWD:/workdir -w /workdir php-pcntl-7.4 php test.php 1
Output:
PHP version: 7.4.28
Command: /bin/echo test
last proc_get_status exitcode: -1
Command: /bin/false
last proc_get_status exitcode: -1
Does anyone have an idea of why the exitcode is always -1, and if there is a workaround ?

Touch a file as sudo

How can I touch a file as sudo when using [Symfony's Filesystem][1]?
So far I have:
$fs = new Filesystem();
$confFile = sprintf('/etc/apache2/sites-available/%s.conf', $input->getArgument('name'));
$fs->touch($confFile);
But this code fails with error: Permission denied.
[1]: http://symfony.com/doc/current/components/filesystem/introduction.html
From the Symfony Filesystem component:
public function touch($files, $time = null, $atime = null)
{
foreach ($this->toIterator($files) as $file) {
$touch = $time ? #touch($file, $time, $atime) : #touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
}
}
}
As you see there, the touch() method is just a simple wrapper around PHP's buildin touch() function. If you need to run touch with elevated rights via sudo, you have to call it directly:
shell_exec('sudo touch ' . escapeshellarg($file));
you need to change the rights on /etc/apache2/sites-available/ to add write access to apache user
HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1`
sudo chmod +a "$HTTPDUSER allow delete,write,append,file_inherit,directory_inherit" /etc/apache2/sites-available/

Running a PHP Script as a Daemon in Debian

I am trying to start a php script as a daemon in Debian. I also would like it to start on boot as well.
I have been starting using /path/to/php /path/to/script/Insert.php & without issue, and can shell_exec("nohup /path/to/php /path/to/script/Insert.php >/dev/null &") as well. I have tried using the below script, but it does not take the script into an operational state.
Am copying the file to /etc/init.d/ and using update-rc.d without issues. I can use service congen-insert startto 'start' the script, but it doesn't seem to actually run, and it doesn't start doing any work.
What am I missing, or where have I gone wrong with the scripts?
I know there are several ways to work around this, but I am really just trying to understand what I am doing incorrectly or why what I am doing is not working.
Any help or suggestions is extremely appreciated! If there is anything else you need or anything I have missed in my description, please let me know so I can correct it.
Thanks in advance.
Service script
#! /bin/sh
### BEGIN INIT INFO
# Provides: congen-insert
# Required-Start: $local_fs $network
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: congen-insert
# Description: DB Insert Daemon
### END INIT INFO
NAME="congen-insert"
DESC=" DB Insert Daemon"
PIDFILE="/var/run/${NAME}.pid"
LOGFILE="/var/log/${NAME}.log"
DAEMON="/path/to/php"
DAEMON_OPTS="/path/to/script/Insert.php"
START_OPTS="--start --background --make-pidfile --pidfile ${PIDFILE} --exec ${DAEMON} ${DAEMON_OPTS}"
STOP_OPTS="--stop --pidfile ${PIDFILE}"
test -x $DAEMON || exit 0
set -e
case "$1" in
start)
echo -n "Starting ${DESC}: "
start-stop-daemon $START_OPTS >> $LOGFILE
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon $STOP_OPTS
echo "$NAME."
rm -f $PIDFILE
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon $STOP_OPTS
sleep 1
start-stop-daemon $START_OPTS >> $LOGFILE
echo "$NAME."
;;
status)
echo -n "Sorry, this isn't implemented yet"
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
Script I am trying to run:
const LoaderPath = __DIR__ . DIRECTORY_SEPARATOR . ".." .DIRECTORY_SEPARATOR . "includes.php";
require_once LoaderPath;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;
use requests\InsertRequest;
$connection = GetRabbitConnection();
$channel = $connection->channel();
$RedisClient = GetRedisClient();
DeclareQueues($connection, $RedisClient);
$MySQLHost = $RedisClient->get(MySQLHostKey);
$MySQLUser = $RedisClient->get(MySQLUserKey);
$MySQLPassword = $RedisClient->get(MySQLPasswordKey);
$MySQLDatabase = $RedisClient->get(MySQLDatabaseKey);
$InsertExchange = $RedisClient->get(Insert.":".Exchange);
$InsertQueue = $RedisClient->get(Insert.":".Queue);
$Prefetch = $RedisClient->get(Insert.":".Prefetch);
$RedisClient->disconnect();
$RedisClient = null;
$mysql= new mysqli($MySQLHost, $MySQLUser, $MySQLPassword, $MySQLDatabase);
$channel->basic_qos(0,$Prefetch,false);
$channel->basic_consume($InsertQueue, $InsertExchange, false, false, false, false, "callback");
echo "Consuming on Exchange $InsertExchange with Queue $InsertQueue\n";
while(true) {
$channel->wait();
}
$channel->close();
function callback(AMQPMessage $message){
global $mysql;
echo "Message received", "\n";
$InsertRequest = new InsertRequest($message->body);
echo "Running Insert Statement\n";
if (!$mysql->query($InsertRequest->SQL)){
echo "Error: ".$mysql->error;
}
/** #type AMQPChannel $channel */
$channel = $message->delivery_info['channel'];
$channel->basic_ack($message->delivery_info['delivery_tag']);
echo "Insert Complete\n";
}
The issue was in the redirection of the output. I also modified the php file with a header for bash so it does not show as multiple php processes in top, but shows the file name instead:
Revised Service Script:
#! /bin/sh
### BEGIN INIT INFO
# Provides: congen-insert
# Required-Start: $local_fs $network
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: congen-insert
# Description: ConGen DB Insert Daemon
### END INIT INFO
NAME="congen-insert"
DESC="DB Insert Process for ConGen"
PIDFILE="/var/run/${NAME}.pid"
LOGFILE="/var/log/${NAME}.log"
DAEMON="/var/congen/php/controllers/congen-insert"
DAEMON_OPTS="> /dev/null 2>&1"
START_OPTS="--start --background --make-pidfile --pidfile ${PIDFILE} --exec ${DAEMON} ${DAEMON_OPTS}"
STOP_OPTS="--stop --pidfile ${PIDFILE}"
test -x $DAEMON || exit 0
set -e
case "$1" in
start)
echo -n "Starting ${DESC}: "
start-stop-daemon $START_OPTS >> $LOGFILE
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon $STOP_OPTS
echo "$NAME."
rm -f $PIDFILE
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon $STOP_OPTS
sleep 1
start-stop-daemon $START_OPTS >> $LOGFILE
echo "$NAME."
;;
status)
echo -n "Sorry, this isn't implemented yet"
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
Revised PHP Script to run:
#!/php52/php-5.6.6/bin/php
<?php
const LoaderPath = __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "includes.php";
require_once LoaderPath;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;
use requests\InsertRequest;
$connection = GetRabbitConnection();
$channel = $connection->channel();
$RedisClient = GetRedisClient();
DeclareQueues($connection, $RedisClient);
$InsertExchange = $RedisClient->get(Insert.":".Exchange);
$InsertQueue = $RedisClient->get(Insert.":".Queue);
$Prefetch = $RedisClient->get(Insert.":".Prefetch);
$RedisClient->disconnect();
$RedisClient = null;
$mysql= ConnectionBuilder::GetMySQLi();
$channel->basic_qos(0,$Prefetch,false);
$channel->basic_consume($InsertQueue, $InsertExchange, false, false, false, false, "callback");
echo "Consuming on Exchange $InsertExchange with Queue $InsertQueue\n";
while(true) {
$channel->wait();
}
$channel->close();
function callback(AMQPMessage $message){
global $mysql;
echo "Message received", "\n";
$InsertRequest = new InsertRequest($message->body);
echo "Running Insert Statement\n";
if (!$mysql->query($InsertRequest->SQL)){
echo "Error: ".$mysql->error;
}
/** #type AMQPChannel $channel */
$channel = $message->delivery_info['channel'];
$channel->basic_ack($message->delivery_info['delivery_tag']);
echo "Insert Complete\n";
}
After adding the file to /etc/init.d/ and making both the php script and service script executable, I can start the service using service congen-insert start and use the rest of the commands just like any other init.d service.
It should be noted that I am redirecting the console to /dev/null, but you could also redirect to a file by replacing the /dev/null with a writable path.
An explanation of the 2>&1 quoted from another SO post "2 is the stream number for stderr (error messages), 1 is represents [sic] the stdout stream (the standard non-error output stream)." as such I am essentially redirecting stdout to /dev/null and redirecting stderr to stdout
Write a script using interactive shell commands to execute your php script as shown in the php from terminal example. This only works if PHP is compiled to include the --with-readline option
Setup a cron task (the linux task scheduler) to run this script as shown in the cron setup example.

PHP shell execution create dynamic command

Following Command search a file in a directory and zip it,it works well
$command = "cd {$root}/files && mkdir -p {$identifier} && zip -jFS -0 {$root}{$zipname} 2491/'test&.txt'";
exec($command);
But changing files as variable is not allowing shell to execute,the below code does not work
$container_name = "2491";
$files = Array ( '0' => 'test&.txt' ,'1' => 'test5.txt','2' => 'test6.txt');
$files = " " . $container_name . "/'" . implode("' " . $container_name . "/'", $files) . "'";
$files = str_replace('$', '\$', $files);
$command = "cd {$root}/files && mkdir -p {$identifier} && zip -jFS -0 {$root}{$zipname} {$files}";
exec($command);
$root,$identifier,$zipname not causing the problem,its $files What can be the issue?
Update
var_dump for $command before execution:
string(128) "cd /var/www/files && mkdir -p zip--1 && zip -jFS -0 /var/www/files/zip--1/1002_22-06022-06022-_content.zip 2491/'test&.txt'"
which if I execute as
exec("cd /var/www/files && mkdir -p zip--1 && zip -jFS -0 /var/www/files/zip--1/1002_22-06022-06022-_content.zip 2491/'test&.txt'");
runs perfectly
Error Reponse:
zip error: Nothing to do! (/var/www/files/zip--1/1002_22-06022-06022-_content.zip)
I got it fix, issue was with file name having '&' which was chanding to '&', thus breaking the command. To get it I passed through the file name with [htmlspecialchars_decode].1

Properly escaping SSH command in PHP

I have an SSH command that I'd like to execute with libssh2 in PHP:
sh -c '
rm -f /tmp/command.log
sleep 3 &
screen -p 0 -X stuff "\
script -a -c \"ls -l\" /tmp/command.log; kill -USR1 $!
"
wait
cat /tmp/command.log
'
I can't seem to escape it properly though, so SSH receives it exactly as above. I need to wrap it in double quotes so I can get PHP variables in there as well (ls -l will become $command).
I have tried:
"sh -c '
rm -f /tmp/command.log
sleep 3 &
screen -p 0 -X stuff \"\
script -a -c \\"ls -l\\" /tmp/command.log; kill -USR1 $!
\"
wait
cat /tmp/command.log
'"
as well as:
"sh -c '
rm -f /tmp/command.log
sleep 3 &
screen -p 0 -X stuff \"\
script -a -c \\\"ls -l\\\" /tmp/command.log; kill -USR1 $!
\"
wait
cat /tmp/command.log
'"
The first of which returns a PHP error and the second of which doesn't run the command.
The whole function (after the edit Morgan Wilde suggested):
function runShellCommand($command, $host, $user, $pass, $port){
if (!function_exists("ssh2_connect")) die("Fail: function ssh2_connect doesn't exist");
if(!($con = ssh2_connect($host, $port))){
return "Unable to establish connection. Is your server offline?";
} else {
if(!ssh2_auth_password($con, $user, $pass)) {
return "Failed to authenticate. Please ensure your server's password matches our records.";
} else {
$run = <<<HEREDOC
sh -c '
rm -f /tmp/command.log
sleep 3 &
screen -p 0 -X stuff "\
script -a -c \"touch /root/test234\" /tmp/command.log; kill -USR1 $!
"
wait
cat /tmp/command.log
'
HEREDOC;
if (!($stream = ssh2_exec($con, $run ))) {
return "Could not run command.";
} else {
stream_set_blocking($stream, true);
$data = "";
while ($buf = fread($stream,4096)) {
$data .= $buf;
}
fclose($stream);
if(empty($data)){
return "sh-4.1# $command\n\n";
} else {
return "sh-4.1# $command\n$data\n";
}
}
}
}
}
How about using the HEREDOC string quoting? I haven't tried it, but it works for other use cases.
$command = <<<HEREDOC
sh -c '
rm -f /tmp/command.log
sleep 3 &
screen -p 0 -X stuff "\
script -a -c \"ls -l\" /tmp/command.log; kill -USR1 $!
"
wait
cat /tmp/command.log
'
HEREDOC;
More on that here - http://php.net/manual/en/language.types.string.php
Try phpseclib, a pure PHP SSH implementation. eg.
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
exit('Login Failed');
}
echo $ssh->read('username#username:~$');
$ssh->write("rm -f /tmp/command.log\n");
echo $ssh->read('username#username:~$');
$ssh->write("sleep 3 &\n");
echo $ssh->read('username#username:~$');
$ssh->write("screen -p 0 -X stuff \"\
script -a -c \\\"ls -l\\\" /tmp/command.log; kill -USR1 $!
\"");
...
?>

Categories