--- /dev/null
+#!/usr/bin/env python
+
+import os
+from sys import exit,argv,stdout,stderr
+from signal import signal,Signals,SIGINT,SIGTERM
+from psutil import process_iter,Process,TimeoutExpired
+from time import sleep,strftime,gmtime
+from getpass import getuser
+
+class pydaemon:
+ def __init__(self,name):
+ self.name=name
+ self.user=getuser()
+ uid=os.getuid()
+ if uid > 0:uid='user/%s/'%uid
+ else: uid = ''
+ self.pid_filename='/var/run/%s%s.pid'%(uid,self.name)
+ self.debug=5
+ self.loop_timer=1
+ self.exit_timer=5
+
+ def echo(self,message,**kws):
+ d={'flush':False,'end':'\n','sep':' ','file':stdout,'level':0}
+ for key in kws:d[key]=kws[key]
+ if self.debug >= d['level']:
+ if d['level'] > 4:d['file']=stderr
+ if self.debug > 1:
+ print ('%s %s: '%(strftime('%Y-%m-%d %H:%M:%S',gmtime()),self.name),end='',file=d['file'])
+ d['flush']=True;d['end']='\n'
+ print(message,end=d['end'],sep=d['sep'],file=d['file'],flush=d['flush'])
+
+ def exit(self,sig,frame):
+ self.echo('Starting exit...',level=2)
+ sig_name=str(Signals(sig))[8:]
+ self.echo('%s: Received %s, stopping gracefully ... ' %(self.name,sig_name),end='',level=1,flush=True)
+ self.clean_up()
+ self.echo('done.',level=1)
+ exit(0)
+
+ def start(self):
+ self.echo('Starting start',level=2)
+ signal(SIGTERM,self.exit)
+ try:
+ pid_file=open(self.pid_filename,'x')
+ except FileExistsError:
+ self.echo('PID file already exists.',level=2)
+ if self.alive(self.readpid()):self.echo('%s: Daemon already running, exiting!'%self.name,level=-1);exit(1)
+ self.echo('%s: Removing stale PID file: %s'%(self.name,self.pid_filename),level=1)
+ os.remove(self.pid_filename)
+ self.start()
+ self.echo('Writing to PID file %s'%self.pid_filename,level=2)
+ pid_file.write(str(os.getpid()))
+ pid_file.close()
+ self.echo('Starting main_loop',level=2)
+ while True:
+ self.main_loop()
+ sleep(self.loop_timer)
+
+ def main_loop(self):
+ self.echo('Daemon running as PID: %s'%os.getpid(),level=2)
+ sleep(15)
+
+ def clean_up(self):
+ return
+
+ def readpid(self):
+ self.echo('Reading from pid file: %s'%self.pid_filename,level=2)
+ pf=open(self.pid_filename,'r');rv=pf.read();pf.close()
+ return int(rv)
+
+ def getpid(self):
+ try:
+ rv=self.readpid()
+ except ValueError or FileNotFoundError:
+ self.echo('PID file missing or corrupt: %s'%self.pid_filename,level=2)
+ rv=0;match=Process().cmdline()[:2];match.append('start')
+ self.echo('Gleaning PID from running processes ... ',level=2)
+ for ps in process_iter():
+ if ps.cmdline()==match and ps.username()==self.user:
+ rv=ps.pid
+ self.echo('Gleaned PID as: %s'%rv,level=2)
+ break
+ if not rv:self.echo('Gleaning PID from running processes ... ',level=2)
+ return int(rv)
+
+ def simple_exit(self,sig,*frame):
+ exit(0)
+
+ def stop(self):
+ self.echo ('Stopping %s ... '%self.name, end='', flush=True)
+ try:
+ child=Process(self.getpid())
+ child.terminate()
+ except:
+ self.echo ('daemon not running, stop failed!')
+ try:os.remove(self.pid_filename)
+ except FileNotFoundError:pass
+ exit(1)
+ try:
+ child.wait(timeout=self.exit_timer)
+ except TimeoutExpired:
+ self.echo ('timeout waiting for clean exit, killing PID:%s ...'%child.pid,end='',flush=True)
+ child.kill()
+ try:os.remove(self.pid_filename)
+ except FileNotFoundError:pass
+ self.echo('stopped.')
+ exit(0)
+
+ def alive(self,pid=None):
+ try:
+ if pid: return Process(pid).is_running()
+ else: return Process(self.getpid()).is_running()
+ except:
+ return False
+
+ def status(self):
+ signal(SIGINT,self.simple_exit)
+ try: interval=argv[2]
+ except:interval=0
+ run=True
+ while run:
+ if self.alive(): isRunning='Yes, PID:%s'%self.getpid()
+ else: isRunning='Not running...'
+ self.echo(' Running : %s'%isRunning)
+ sleep(int(interval))
+ run=interval
+
+
+#import your_deps
+
+class daemon(pydaemon):
+ def __init__(self,name):
+ super().__init__(name)
+ self.debug=-1 # -1:suppress all output 0:default 1:daemon prints to terminal 2:time stamped debugging logs
+ self.loop_timer=1 # seconds between main_loop() runs
+ self.exit_timer=5 # seconds clean_up() needs to finish
+ #initialization code goes here
+
+ def main_loop(self):
+ #main code goes here
+ pass
+
+ def clean_up(self):
+ #cleanup code goes here
+ pass
+
+ #def your_function(self,arg):
+ #check 'your_function' doesn't conflict with any pydaemon.function names
+ #endef
+
+ def status(self):
+ if self.alive():self.echo('%s status: Running'%self.name);exit(0)
+ else: self.echo('%s status: Stopped'%self.name);exit(1)
+
+def usage(name):
+ print('Usage: %s start|status|stop|test'%name)
+ exit(0)
+
+name=argv[0][argv[0].rfind('/')+1:]
+
+def run_main():
+ try:
+ action=argv[1]
+ except IndexError:
+ usage(name)
+ if action == 'start':
+ if os.fork():exit(0)
+ main=daemon(name)
+ main.start()
+ elif action == 'test': #don't daemonise
+ main=daemon(name)
+ main.start()
+ elif action == 'stop':
+ main=daemon(name)
+ main.stop()
+ elif action == 'status':
+ main=daemon(name)
+ main.status()
+ else:
+ usage(name)
+
+if __name__=='__main__':
+ run_main()