A service or in Unix/Linux terms a daemon is a program that continuously runs in the background. Sometimes it listens to some port or some events and based on the input does something. Sometimes it uses the internal clock of the computer to execute some functions periodically.

There are many well known services that either run on your computer by default or you can install them. The former might be the service waiting for you to type in your username and password on the terminal to log in, or waiting for an ssh connection, or running your cron jobs. The latter might be a web-server, a database server. (Things that are called servers are usually installed as services so they would run in the background.)

Services are usually configured to start when the computer boots up to be able to provide service even without the login of any user.

How can we create a service / daemon?

The following file is just a very simple script that will print the timestamp in a file every 1 second. There is noting special about it.

examples/daemon/timestamp.py

import datetime
import time
import sys

param = ""

if len(sys.argv) > 1:
  param = sys.argv[1]

while True:
   with open("/tmp/timestamp.log", "a") as fh:
       fh.write(param + ' ' + str(datetime.datetime.now()) + "\n")
   time.sleep(1)

You can run the script on the command line:

python examples/daemon/timestamp.py

and then in a separate terminal you can observe the file growing by running the following command:

tail -f /tmp/timestamp.log

In order to stop the script you need to switch back to the first terminal and press Crtl-C.

Then you can try to run the script with some parameter. e.g.:

python examples/daemon/timestamp.py hello

And switch to the 2nd terminal to observe the output.

The service / daemon configuration file

In order to turn this into a service we need to create a service configuration file:

examples/daemon/timestamp.service

[Unit]
Description=Demo Service

[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/bin/python3 /home/gabor/work/code-maven.com/examples/daemon/timestamp.py "my daemon"
Restart=always
WorkingDirectory=/tmp
Nice=19
LimitNOFILE=16384

[Install]
WantedBy=multi-user.target

The most important field here is:

ExecStart

Create a symbolic link from the system-wide location of all the services in /usr/lib/systemd/system/ to the place where our service configuration file is. (Alternatively you could also copy the timestamp.service file.

sudo ln -s /home/gabor/work/code-maven.com/examples/daemon/timestamp.service  /usr/lib/systemd/system/timestamp.service

Then we need to tell the daemon-service no reload all the configuration files. We need to do this every time we add a new service or change the configuration file of a service.

sudo systemctl daemon-reload

Then we can start the service

sudo systemctl start timestamp.service

At this time we should be able to use the tail command to observe how the service works and the file grows one line every second.

tail -f /tmp/timestamp.log

Troubleshooting pitfalls, potential issues and fixes

If you the timestamp.log file does not grow you it means we had some issues.

You can observe the status of the service by running the following command:

sudo systemctl status timestamp.service

You can also look at the log in the syslog file:

less /var/log/syslog

While writing this article I had two issues:

Issue one: typo in command

At first I had a typo in the command on the ExecStart line. That was easy to fix.

Issue two: rights on the log file

Even after the typo was fixed I still did not see the log file growing.

sudo systemctl status timestamp.service

got me this output:

× timestamp.service - Demo Service
     Loaded: loaded (/lib/systemd/system/timestamp.service; disabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sun 2022-08-21 09:35:46 IDT; 7s ago
    Process: 2046747 ExecStart=/usr/bin/python3 /home/gabor/work/code-maven.com/examples/daemon/timestamp.py mydaemon (code=exited, status=1/FAILURE)
   Main PID: 2046747 (code=exited, status=1/FAILURE)
        CPU: 119ms

Aug 21 09:35:46 code-maven systemd[1]: timestamp.service: Scheduled restart job, restart counter is at 5.
Aug 21 09:35:46 code-maven systemd[1]: Stopped Demo Service.
Aug 21 09:35:46 code-maven systemd[1]: timestamp.service: Start request repeated too quickly.
Aug 21 09:35:46 code-maven systemd[1]: timestamp.service: Failed with result 'exit-code'.
Aug 21 09:35:46 code-maven systemd[1]: Failed to start Demo Service.

Then I looked at the syslog:

less /var/log/syslog

and saw this:

Aug 21 09:37:44 code-maven python3[2047136]:   File "/home/gabor/work/code-maven.com/examples/daemon/timestamp.py", line 11, in <module>
Aug 21 09:37:44 code-maven python3[2047136]:     with open("/tmp/timestamp.log", "a") as fh:
Aug 21 09:37:44 code-maven python3[2047136]: PermissionError: [Errno 13] Permission denied: '/tmp/timestamp.log'

Apparently the daemon that is currently configured as user root does not have permissions to open the log file for writing.

$ ls -l /tmp/timestamp.log
-rw-rw-r-- 1 gabor gabor 504 Aug 21 09:35 /tmp/timestamp.log

At this point I could have changed the configuration file of the service to run as user gabor, I could have changed the right on the file so everyone can write to it.

I decided to get rid of the file and let the daemon create it by itself:

$ rm -f /tmp/timestamp.log

Then I started the daemon again. This time it started to work.

$ sudo systemctl status timestamp.service

● timestamp.service - Demo Service
     Loaded: loaded (/lib/systemd/system/timestamp.service; disabled; vendor preset: enabled)
     Active: active (running) since Sun 2022-08-21 09:54:13 IDT; 3s ago
   Main PID: 2049558 (python3)
      Tasks: 1 (limit: 18981)
     Memory: 3.5M
        CPU: 26ms
     CGroup: /system.slice/timestamp.service
             └─2049558 /usr/bin/python3 /home/gabor/work/code-maven.com/examples/daemon/timestamp.py "my daemon"

Aug 21 09:54:13 code-maven systemd[1]: Started Demo Service.

I can observe the file as well. Now it is owned by user root:

$ ls -l /tmp/timestamp.log
-rw-r--r-- 1 root root 4508 Aug 21 09:54 /tmp/timestamp.log

Stop the service

sudo systemctl stop timestamp.service

Restart the service

sudo systemctl restart timestamp.service

Conclusion

It is very easy to convert a script to a daemon, but one needs to be careful with the rights on files and also the environment that is different for a daemon and for a script that runs on the command line.

We only scratched the surface of the daemonization. There are tons of other options you can deal with, but this should already get you started.