Wednesday, February 23, 2022

[SOLVED] Running a PHP Script as a Daemon in Debian

Issue

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";

}

Solution

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



Answered By - chaosaffe
Answer Checked By - Clifford M. (WPSolving Volunteer)