196

I have written a Python script that checks a certain e-mail address and passes new e-mails to an external program. How can I get this script to execute 24/7, such as turning it into daemon or service in Linux. Would I also need a loop that never ends in the program, or can it be done by just having the code re executed multiple times?

Martin Thoma
  • 91,837
  • 114
  • 489
  • 768
adhanlon
  • 5,829
  • 11
  • 39
  • 41
  • 1
    See SO question: http://stackoverflow.com/questions/1423345/can-i-run-a-python-script-as-a-service – mjv Oct 21 '09 at 19:42
  • 3
    "checks a certain e-mail address and passes new e-mails to an external program" Isn't that what sendmail does? You can define mail alias to route a mailbox to a script. Why aren't you using mail aliases to do this? – S.Lott Oct 21 '09 at 19:54
  • 2
    On a modern linux which has `systemd` you can create a systemd service in `daemon` mode as described [here](https://unix.stackexchange.com/a/401080/15312). See also: https://www.freedesktop.org/software/systemd/man/systemd.service.html – ccpizza Sep 11 '18 at 23:22
  • If the linux system supports systemd, use the approach [outlined here](https://stackoverflow.com/questions/13069634/python-daemon-and-systemd-service). – gerardw Oct 31 '18 at 18:42

15 Answers15

101

You have two options here.

  1. Make a proper cron job that calls your script. Cron is a common name for a GNU/Linux daemon that periodically launches scripts according to a schedule you set. You add your script into a crontab or place a symlink to it into a special directory and the daemon handles the job of launching it in the background. You can read more at Wikipedia. There is a variety of different cron daemons, but your GNU/Linux system should have it already installed.

  2. Use some kind of python approach (a library, for example) for your script to be able to daemonize itself. Yes, it will require a simple event loop (where your events are timer triggering, possibly, provided by sleep function).

I wouldn't recommend you to choose 2., because you would be, in fact, repeating cron functionality. The Linux system paradigm is to let multiple simple tools interact and solve your problems. Unless there are additional reasons why you should make a daemon (in addition to trigger periodically), choose the other approach.

Also, if you use daemonize with a loop and a crash happens, no one will check the mail after that (as pointed out by Ivan Nevostruev in comments to this answer). While if the script is added as a cron job, it will just trigger again.

Davide Cannizzo
  • 1,974
  • 1
  • 19
  • 27
P Shved
  • 87,005
  • 15
  • 114
  • 161
  • 10
    +1 to the cronjob. I don't think the question specifies that it is checking a local mail account, so mail filters do not apply – John La Rooy Oct 21 '09 at 21:10
  • What happen does use a loop without termination in a Python program and then register it into `crontab` list will be? If I set up such `.py` for hourly, will it create many processes that will never be terminated? If so, I think this would quite like daemon. – Veck Hsiao Jan 14 '16 at 08:47
  • I can see that cron is an obvious solution if you check check for emails once a minute (which is the lowest time resolution for Cron). But what if I want to check for emails every 10 seconds? Should I write the Python script to run query 60 times, which means it ends after 50 seconds, and then let cron start the script again 10 seconds later? – Mads Skjern Mar 10 '16 at 10:29
  • I have not worked with daemons/services, but I was under the impression that it (OS/init/init.d/upstart or what it is called) takes care of restarting a daemon when/if it ends/crashes. – Mads Skjern Mar 10 '16 at 10:32
  • @VeckHsiao yes, crontab calls a script so many instances of your python script will be called with everyone its loop.... – Pipo May 24 '18 at 14:47
  • The comment about crashes is not relevant in a dockerized environment, where the orchestrator will spin up a new container after each en every crash (given that your script had pid 1). If the script stops working, I can see events in my cluster. If the cron stops working, I will not know until I read the logs – Bruno9779 Jun 14 '18 at 18:29
  • I’m writing a python script that needs check about 20 sensors about 10 times per minute each 24/7. Is cron still a good use? I suspect a daemon would be better. – Ryan Sep 02 '20 at 03:44
75

Here's a nice class that is taken from here:

#!/usr/bin/env python

import sys, os, time, atexit
from signal import SIGTERM

class Daemon:
        """
        A generic daemon class.

        Usage: subclass the Daemon class and override the run() method
        """
        def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
                self.stdin = stdin
                self.stdout = stdout
                self.stderr = stderr
                self.pidfile = pidfile

        def daemonize(self):
                """
                do the UNIX double-fork magic, see Stevens' "Advanced
                Programming in the UNIX Environment" for details (ISBN 0201563177)
                http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
                """
                try:
                        pid = os.fork()
                        if pid > 0:
                                # exit first parent
                                sys.exit(0)
                except OSError, e:
                        sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
                        sys.exit(1)

                # decouple from parent environment
                os.chdir("/")
                os.setsid()
                os.umask(0)

                # do second fork
                try:
                        pid = os.fork()
                        if pid > 0:
                                # exit from second parent
                                sys.exit(0)
                except OSError, e:
                        sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
                        sys.exit(1)

                # redirect standard file descriptors
                sys.stdout.flush()
                sys.stderr.flush()
                si = file(self.stdin, 'r')
                so = file(self.stdout, 'a+')
                se = file(self.stderr, 'a+', 0)
                os.dup2(si.fileno(), sys.stdin.fileno())
                os.dup2(so.fileno(), sys.stdout.fileno())
                os.dup2(se.fileno(), sys.stderr.fileno())

                # write pidfile
                atexit.register(self.delpid)
                pid = str(os.getpid())
                file(self.pidfile,'w+').write("%s\n" % pid)

        def delpid(self):
                os.remove(self.pidfile)

        def start(self):
                """
                Start the daemon
                """
                # Check for a pidfile to see if the daemon already runs
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if pid:
                        message = "pidfile %s already exist. Daemon already running?\n"
                        sys.stderr.write(message % self.pidfile)
                        sys.exit(1)

                # Start the daemon
                self.daemonize()
                self.run()

        def stop(self):
                """
                Stop the daemon
                """
                # Get the pid from the pidfile
                try:
                        pf = file(self.pidfile,'r')
                        pid = int(pf.read().strip())
                        pf.close()
                except IOError:
                        pid = None

                if not pid:
                        message = "pidfile %s does not exist. Daemon not running?\n"
                        sys.stderr.write(message % self.pidfile)
                        return # not an error in a restart

                # Try killing the daemon process       
                try:
                        while 1:
                                os.kill(pid, SIGTERM)
                                time.sleep(0.1)
                except OSError, err:
                        err = str(err)
                        if err.find("No such process") > 0:
                                if os.path.exists(self.pidfile):
                                        os.remove(self.pidfile)
                        else:
                                print str(err)
                                sys.exit(1)

        def restart(self):
                """
                Restart the daemon
                """
                self.stop()
                self.start()

        def run(self):
                """
                You should override this method when you subclass Daemon. It will be called after the process has been
                daemonized by start() or restart().
                """
Dane
  • 5
  • 2
the_drow
  • 17,134
  • 23
  • 116
  • 185
61

You should use the python-daemon library, it takes care of everything.

From PyPI: Library to implement a well-behaved Unix daemon process.

gonz
  • 4,746
  • 3
  • 36
  • 54
Prody
  • 5,042
  • 6
  • 41
  • 61
  • 4
    Ditto Jorge Vargas's comment. After looking at the code, it actually looks like quite a nice piece of code, but the complete lack of docs and examples makes it very difficult to use, which means most developers will rightfully ignore it for better documented alternatives. – Cerin Mar 16 '12 at 14:52
  • 1
    Seems not to work properly in Python 3.5: https://gist.github.com/MartinThoma/fa4deb2b4c71ffcd726b24b7ab581ae2 – Martin Thoma Dec 07 '17 at 07:49
  • Not working as expected. Would it be nice if it did though. – Harlin Apr 22 '19 at 02:48
  • Unix != Linux - could this be the trouble? – Dana May 06 '19 at 19:52
40

You can use fork() to detach your script from the tty and have it continue to run, like so:

import os, sys
fpid = os.fork()
if fpid!=0:
  # Running as daemon now. PID is fpid
  sys.exit(0)

Of course you also need to implement an endless loop, like

while 1:
  do_your_check()
  sleep(5)

Hope this get's you started.

jhwist
  • 13,623
  • 3
  • 37
  • 47
  • Hello, I've tried this and it works for me. But when I close the terminal or get out of the ssh session, the script also stops working!! – David Okwii Nov 21 '16 at 12:15
  • @DavidOkwii `nohup`/`disown` commands would detach process from console and it won't die. Or you could start it with init.d – pholat Sep 07 '17 at 12:32
20

Assuming that you would really want your loop to run 24/7 as a background service

For a solution that doesn't involve injecting your code with libraries, you can simply create a service template, since you are using linux:

[Unit]
Description = <Your service description here>
After = network.target # Assuming you want to start after network interfaces are made available
 
[Service]
Type = simple
ExecStart = python <Path of the script you want to run>
User = # User to run the script as
Group = # Group to run the script as
Restart = on-failure # Restart when there are errors
SyslogIdentifier = <Name of logs for the service>
RestartSec = 5
TimeoutStartSec = infinity
 
[Install]
WantedBy = multi-user.target # Make it accessible to other users

Place that file in your daemon service folder (usually /etc/systemd/system/), in a *.service file, and install it using the following systemctl commands (will likely require sudo privileges):

systemctl enable <service file name without .service extension>

systemctl daemon-reload

systemctl start <service file name without .service extension>

You can then check that your service is running by using the command:

systemctl | grep running
Heitor Castro
  • 433
  • 3
  • 8
  • this is my preferred solution as no deps if you have systemd but could you have shared the .service file as a code snippet instead of screenshot - haha? – Boog Sep 03 '20 at 23:46
  • Hi, this approach sounds straight forward. When I use this to run my python script in the background everything goes fine untill system reboots. It seems that startup functionality fails because of ordering cycle error. the error is: Job active_controller.service/start deleted to break ordering cycle starting with sysinit.target/start please help if you can. – PouJa Oct 01 '20 at 14:51
  • 1
    @PouJa please post a new question with the details. – Yawar Oct 04 '20 at 16:24
16

You can also make the python script run as a service using a shell script. First create a shell script to run the python script like this (scriptname arbitary name)

#!/bin/sh
script='/home/.. full path to script'
/usr/bin/python $script &

now make a file in /etc/init.d/scriptname

#! /bin/sh

PATH=/bin:/usr/bin:/sbin:/usr/sbin
DAEMON=/home/.. path to shell script scriptname created to run python script
PIDFILE=/var/run/scriptname.pid

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

case "$1" in
  start)
     log_daemon_msg "Starting feedparser"
     start_daemon -p $PIDFILE $DAEMON
     log_end_msg $?
   ;;
  stop)
     log_daemon_msg "Stopping feedparser"
     killproc -p $PIDFILE $DAEMON
     PID=`ps x |grep feed | head -1 | awk '{print $1}'`
     kill -9 $PID       
     log_end_msg $?
   ;;
  force-reload|restart)
     $0 stop
     $0 start
   ;;
  status)
     status_of_proc -p $PIDFILE $DAEMON atd && exit 0 || exit $?
   ;;
 *)
   echo "Usage: /etc/init.d/atd {start|stop|restart|force-reload|status}"
   exit 1
  ;;
esac

exit 0

Now you can start and stop your python script using the command /etc/init.d/scriptname start or stop.

Kishore K
  • 1,887
  • 2
  • 14
  • 18
  • I just tried this, and it turns out this will start the process, but it will not be daemonized (i.e. it's still attached to the terminal). It would probably work fine if you ran update-rc.d and made it run on boot (I assume there's no terminal attached when these scripts are run), but it doesn't work if you invoke it manually. Seems like supervisord might be a better solution. – ryuusenshi May 23 '14 at 00:02
  • One could probably use `disown` somehow? – scribe Jan 15 '21 at 04:30
15

A simple and supported version is Daemonize.

Install it from Python Package Index (PyPI):

$ pip install daemonize

and then use like:

...
import os, sys
from daemonize import Daemonize
...
def main()
      # your code here

if __name__ == '__main__':
        myname=os.path.basename(sys.argv[0])
        pidfile='/tmp/%s' % myname       # any name
        daemon = Daemonize(app=myname,pid=pidfile, action=main)
        daemon.start()
Umesh .A Bhat
  • 587
  • 3
  • 13
  • 24
fcm
  • 952
  • 12
  • 25
  • 1
    is it restart when system get restarted? because when system got restart the process will be killed right? – ShivaPrasad Sep 12 '19 at 14:41
  • @ShivaPrasad u found the answer to it? – 1UC1F3R616 Jan 12 '20 at 18:37
  • restart after a system restart is not a demon function. use cron, systemctl, startup hooks or other tools to run your app on startup. – fcm Jan 15 '20 at 01:32
  • 1
    @Kush yes I wanted to restart after system restarts or use like commands I used systemd functions, If want to try check this https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-managing_services_with_systemd-unit_files – ShivaPrasad Jan 31 '20 at 09:11
  • @ShivaPrasad Thanks bro – 1UC1F3R616 Jan 31 '20 at 14:12
12

cron is clearly a great choice for many purposes. However it doesn't create a service or daemon as you requested in the OP. cron just runs jobs periodically (meaning the job starts and stops), and no more often than once / minute. There are issues with cron -- for example, if a prior instance of your script is still running the next time the cron schedule comes around and launches a new instance, is that OK? cron doesn't handle dependencies; it just tries to start a job when the schedule says to.

If you find a situation where you truly need a daemon (a process that never stops running), take a look at supervisord. It provides a simple way to wrapper a normal, non-daemonized script or program and make it operate like a daemon. This is a much better way than creating a native Python daemon.

Chris Johnson
  • 17,500
  • 5
  • 69
  • 74
  • /tmp/lock files are great for avoiding multiple runs of a script. I'd suggest this approach if you're ok using a bash file, or creating the lock file within the python script. just duckduckgo "bash lock file example" – beep_check Oct 05 '20 at 23:29
11

how about using $nohup command on linux?

I use it for running my commands on my Bluehost server.

Please advice if I am wrong.

Udo Held
  • 11,486
  • 11
  • 63
  • 86
faisal00813
  • 392
  • 5
  • 10
6

If you are using terminal(ssh or something) and you want to keep a long-time script working after you log out from the terminal, you can try this:

screen

apt-get install screen

create a virtual terminal inside( namely abc): screen -dmS abc

now we connect to abc: screen -r abc

So, now we can run python script: python keep_sending_mails.py

from now on, you can directly close your terminal, however, the python script will keep running rather than being shut down

Since this keep_sending_mails.py's PID is a child process of the virtual screen rather than the terminal(ssh)

If you want to go back check your script running status, you can use screen -r abc again

Microos
  • 1,618
  • 3
  • 15
  • 32
4

Ubuntu has a very simple way to manage a service. For python the difference is that ALL the dependencies (packages) have to be in the same directory, where the main file is run from.

I just manage to create such a service to provide weather info to my clients. Steps:

  • Create your python application project as you normally do.

  • Install all dependencies locally like: sudo pip3 install package_name -t .

  • Create your command line variables and handle them in code (if you need any)

  • Create the service file. Something (minimalist) like:

      [Unit]
      Description=1Droid Weather meddleware provider
    
      [Service]
      Restart=always
      User=root
      WorkingDirectory=/home/ubuntu/weather
      ExecStart=/usr/bin/python3 /home/ubuntu/weather/main.py httpport=9570  provider=OWMap
    
      [Install]
      WantedBy=multi-user.target
    
  • Save the file as myweather.service (for example)

  • Make sure that your app runs if started in the current directory

      python3  main.py httpport=9570  provider=OWMap
    
  • The service file produced above and named myweather.service (important to have the extension .service) will be treated by the system as the name of your service. That is the name that you will use to interact with your service.

  • Copy the service file:

      sudo cp myweather.service /lib/systemd/system/myweather.service
    
  • Refresh demon registry:

      sudo systemctl daemon-reload
    
  • Stop the service (if it was running)

      sudo service myweatherr stop
    
  • Start the service:

      sudo service myweather start
    
  • Check the status (log file with where your print statements go):

      tail -f /var/log/syslog
    
  • Or check the status with:

      sudo service myweather status
    
  • Back to the start with another iteration if needed

This service is now running and even if you log out it will not be affected. And YES if the host is shutdown and restarted this service will be restarted...information for my mobile android app...

Gogu CelMare
  • 455
  • 3
  • 14
3

First, read up on mail aliases. A mail alias will do this inside the mail system without you having to fool around with daemons or services or anything of the sort.

You can write a simple script that will be executed by sendmail each time a mail message is sent to a specific mailbox.

See http://www.feep.net/sendmail/tutorial/intro/aliases.html

If you really want to write a needlessly complex server, you can do this.

nohup python myscript.py &

That's all it takes. Your script simply loops and sleeps.

import time
def do_the_work():
    # one round of polling -- checking email, whatever.
while True:
    time.sleep( 600 ) # 10 min.
    try:
        do_the_work()
    except:
        pass
S.Lott
  • 359,791
  • 75
  • 487
  • 757
  • 6
    The problem here is that `do_the_work()` can crash the script and noone run it again – Ivan Nevostruev Oct 21 '09 at 19:51
  • if the function do_the_work() crashes, it would be called again after 10 minutes, since only the one function call raises an error. But instead of crashing the loop just the `try` part fails and the `except:` part will be called instead (in this case nothing) but the loop will continue and keep trying to call the function. – sarbot May 06 '18 at 17:07
3

I would recommend this solution. You need to inherit and override method run.

import sys
import os
from signal import SIGTERM
from abc import ABCMeta, abstractmethod



class Daemon(object):
    __metaclass__ = ABCMeta


    def __init__(self, pidfile):
        self._pidfile = pidfile


    @abstractmethod
    def run(self):
        pass


    def _daemonize(self):
        # decouple threads
        pid = os.fork()

        # stop first thread
        if pid > 0:
            sys.exit(0)

        # write pid into a pidfile
        with open(self._pidfile, 'w') as f:
            print >> f, os.getpid()


    def start(self):
        # if daemon is started throw an error
        if os.path.exists(self._pidfile):
            raise Exception("Daemon is already started")

        # create and switch to daemon thread
        self._daemonize()

        # run the body of the daemon
        self.run()


    def stop(self):
        # check the pidfile existing
        if os.path.exists(self._pidfile):
            # read pid from the file
            with open(self._pidfile, 'r') as f:
                pid = int(f.read().strip())

            # remove the pidfile
            os.remove(self._pidfile)

            # kill daemon
            os.kill(pid, SIGTERM)

        else:
            raise Exception("Daemon is not started")


    def restart(self):
        self.stop()
        self.start()
Fomalhaut
  • 5,473
  • 6
  • 25
  • 62
2

to creating some thing that is running like service you can use this thing :

The first thing that you must do is installing the Cement framework: Cement frame work is a CLI frame work that you can deploy your application on it.

command line interface of the app :

interface.py

 from cement.core.foundation import CementApp
 from cement.core.controller import CementBaseController, expose
 from YourApp import yourApp

 class Meta:
    label = 'base'
    description = "your application description"
    arguments = [
        (['-r' , '--run'],
          dict(action='store_true', help='Run your application')),
        (['-v', '--version'],
          dict(action='version', version="Your app version")),
        ]
        (['-s', '--stop'],
          dict(action='store_true', help="Stop your application")),
        ]

    @expose(hide=True)
    def default(self):
        if self.app.pargs.run:
            #Start to running the your app from there !
            YourApp.yourApp()
        if self.app.pargs.stop:
            #Stop your application
            YourApp.yourApp.stop()

 class App(CementApp):
       class Meta:
       label = 'Uptime'
       base_controller = 'base'
       handlers = [MyBaseController]

 with App() as app:
       app.run()

YourApp.py class:

 import threading

 class yourApp:
     def __init__:
        self.loger = log_exception.exception_loger()
        thread = threading.Thread(target=self.start, args=())
        thread.daemon = True
        thread.start()

     def start(self):
        #Do every thing you want
        pass
     def stop(self):
        #Do some things to stop your application

Keep in mind that your app must run on a thread to be daemon

To run the app just do this in command line

python interface.py --help

1

Use whatever service manager your system offers - for example under Ubuntu use upstart. This will handle all the details for you such as start on boot, restart on crash, etc.

Richard
  • 1,569
  • 15
  • 17