It is quite easy to turn a perl script into a daemon on Linux that you can manage with systemctl.

Original script

examples/daemon/original.pl

#!/usr/bin/env perl
use strict;
use warnings;

main();

sub main {
    my $logfile = 'process.log';
    my $continue = 1;

    $SIG{INT} = sub {
        logger('INT received');
        $continue = 0;
    };

    while ($continue) {
        logger('');
        sleep 1;
    }
}

sub logger {
    my ($text) = @_;

    if (open my $fh, '>>', 'process.log') {
        print $fh scalar localtime();
        print $fh " $text\n";
    }
}

This is a very simple script that has an infinite loop writing to a log file every second. If you press Ctr-C then the script will capture that signal (the INT signal), set the $continue flag to 0 and the loop will stop and then the program as well. This is a graceful termination of the process.

How can we convert this something that we can run as a service?

HUP and TERM instead of INT

Instead of expecting an INT signal which is sent when your press Ctrl-C while starting at a ru8nning process we are expecting two other signals. the TERM signal will be received when we try to "stop" the service and when we try to "restart" it.

The HUP is received when we try to "reload" the service. Most service would re-read their configuration file when they are reloaded. We'll do that too.

The application:

examples/daemon/local.pl

#!/usr/bin/env perl
use strict;
use warnings;

use File::Basename qw(dirname);
use File::Spec::Functions qw(catfile);

main();

my $code;

sub main {
    $code = read_config();
    logger('starting');
    my $continue = 1;

    $SIG{TERM} = sub {
        logger('TERM received');
        $continue = 0;
    };

    $SIG{HUP} = sub {
        logger('HUP received');
        $code = read_config();
    };

    while ($continue) {
        logger('');
        sleep 1;
    }
}

sub logger {
    my ($text) = @_;

    if (open my $fh, '>>', 'process.log') {
        print $fh scalar localtime();
        print $fh " $code - $text\n";
    }
}

sub read_config {
    my $config_file = 'config.txt';
    open my $fh, '<', $config_file or die "Could not open '$config_file'\n";
    my $code = <$fh>;
    chomp $code;
    return $code;
}

The code to create the dameon is the following, using Daemon::Control

examples/daemon/daemon.pl

use warnings;
use strict;
use Daemon::Control;
use File::Basename qw(dirname);
use File::Spec::Functions qw(catfile);

my $dir = dirname(__FILE__);

exit Daemon::Control->new(
    name        => "My Daemon",
    lsb_start   => '$syslog $remote_fs',
    lsb_stop    => '$syslog',
    lsb_sdesc   => 'My Daemon Short',
    lsb_desc    => 'My Daemon controls the My Daemon daemon.',
    path        => $dir,

    program     => catfile($dir, 'local.pl'),
    program_args => [ ],

    pid_file    => '/tmp/mydaemon.pid',
    stderr_file => '/tmp/mydaemon.out',
    stdout_file => '/tmp/mydaemon.out',

    fork        => 2,

)->run;

This version is assumed to be located in the same directory as the script itself.

$ perl daemon.pl
Syntax: daemon.pl [start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]

Starting the process:

$ perl daemon.pl start

You can chenge the content of "config.txt" while the service is running and then execute

$ perl daemon.pl reload

It will re-read the configuration file and continue running.

examples/daemon/local_process.log

Mon Feb 15 12:42:56 2021 one - starting
Mon Feb 15 12:42:56 2021 one -
Mon Feb 15 12:42:57 2021 one -
Mon Feb 15 12:42:58 2021 one - HUP received
Mon Feb 15 12:42:59 2021 two -
Mon Feb 15 12:43:00 2021 two -
Mon Feb 15 12:43:01 2021 two - TERM received
Mon Feb 15 12:43:02 2021 two - starting
Mon Feb 15 12:43:03 2021 two -
Mon Feb 15 12:43:04 2021 two -
Mon Feb 15 12:43:05 2021 two -TERM received
$ perl daemon.pl stop

System-wide version