diff --git a/.gitignore b/.gitignore index d04c6fe..d5e3f60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ packages venv .idea -__pycache__ \ No newline at end of file +__pycache__ +errlog.txt \ No newline at end of file diff --git a/daemon/SimpleDaemon.py b/daemon/SimpleDaemon.py new file mode 100644 index 0000000..adea5f8 --- /dev/null +++ b/daemon/SimpleDaemon.py @@ -0,0 +1,207 @@ +"""Generic linux daemon base class for python 3.x.""" + +import sys, os, time, atexit, signal, logging + + +class Daemon: + """A generic daemon class. + + Usage: subclass the daemon class and override the run() method.""" + + def __init__(self, pidfile, error_log_file='/dev/null'): + self.logging = logging + self.logging.basicConfig(filename=error_log_file, filemode='w', + format='%(name)s - %(levelname)s - %(message)s\n') + self.logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + self.error_log_file = error_log_file + self.pidfile = pidfile + self.commands = {} + + def __enter__(self): + self.base_commands() + self.reg_command() + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def daemonize(self): + """Deamonize class. UNIX double fork mechanism.""" + + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as err: + sys.stderr.write('fork #1 failed: {0}\n'.format(err)) + self.logging.error('fork #1 failed: {0}\n'.format(err)) + 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 as err: + sys.stderr.write('fork #2 failed: {0}\n'.format(err)) + self.logging.error('fork #2 failed: {0}\n'.format(err)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(os.devnull, 'r') + so = open(os.devnull, 'a+') + se = open(os.devnull, 'a+') + + 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()) + with open(self.pidfile, 'w+') as f: + f.write(pid + '\n') + + def delpid(self): + os.remove(self.pidfile) + + def start(self): + """Start the daemon.""" + self.logging.info("Start") + + # Check for a pidfile to see if the daemon already runs + try: + with open(self.pidfile, 'r') as pf: + + pid = int(pf.read().strip()) + except IOError: + pid = None + + if pid: + message = "pidfile {0} already exist. " + \ + "{warning}Daemon already running?{end}\n".format(warning=Bcolors.WARNING, end=Bcolors.ENDC) + sys.stderr.write(message.format(self.pidfile)) + self.logging.error(message.format(self.pidfile)) + sys.exit(1) + + # Start the daemon + print("{green}The process {name} has been successfully launched{end}".format(name=self.pidfile, green=Bcolors.OKGREEN, end=Bcolors.ENDC)) + self.daemonize() + self.run() + + def stop(self): + """Stop the daemon.""" + self.logging.info("Stop") + + # Get the pid from the pidfile + try: + with open(self.pidfile, 'r') as pf: + pid = int(pf.read().strip()) + except IOError: + pid = None + + if not pid: + message = "pidfile {0} does not exist. " + \ + "Daemon not running?\n" + sys.stderr.write(message.format(self.pidfile)) + self.logging.error(message.format(self.pidfile)) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGTERM) + time.sleep(0.1) + except OSError as err: + e = str(err.args) + if e.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print(str(err.args)) + sys.exit(1) + + def restart(self): + """Restart the daemon.""" + self.logging.info("Restart") + self.stop() + self.start() + + def status(self): + print("Status") + try: + with open(self.pidfile, 'r') as pf: + + pid = int(pf.read().strip()) + except IOError: + pid = None + + if pid: + print("Process started, name {name} | pid {pid}".format(pid=pid, name=self.pidfile)) + else: + print("Process is not running") + + def console_stdout(self): + sys.stdout = sys.__stdout__ + print(123) + + def process_command(self): + if len(sys.argv) > 1: + command = sys.argv[1] + handler = self.get_command_handler(command) + if handler: + handler() + else: + print("Unknown command: %s" % command) + else: + print("usage: %s start|stop|restart|status" % sys.argv[0]) + sys.exit(2) + + def base_commands(self): + self.add_command('start', self.start) + self.add_command('stop', self.stop) + self.add_command('restart', self.restart) + self.add_command('status', self.status) + self.add_command('console_stdout', self.console_stdout) + + def add_command(self, command, handler): + if command not in self.commands: + self.commands[command] = handler + + def get_command_handler(self, command): + if command in self.commands: + return self.commands[command] + + return None + + def reg_command(self): + pass + + 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().""" + + +class Bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' diff --git a/daemon/__init__.py b/daemon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py index 5596b44..aef7004 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,16 @@ -# This is a sample Python script. - -# Press Shift+F10 to execute it or replace it with your code. -# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. +import uvicorn +from server import app +from daemon.SimpleDaemon import Daemon -def print_hi(name): - # Use a breakpoint in the code line below to debug your script. - print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. +class CdnServerDaemon(Daemon): + def run(self): + uvicorn.run("server:app", port=5000, log_level="info") # Press the green button in the gutter to run the script. if __name__ == '__main__': - print_hi('PyCharm') + with CdnServerDaemon('/tmp/daemon-cdn-server.pid', error_log_file='errlog.txt') as daemon: + daemon.process_command() # See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/server.py b/server.py index 9b5e71a..5aefc01 100644 --- a/server.py +++ b/server.py @@ -1,4 +1,5 @@ from typing import Union, Annotated +import uvicorn from fastapi import FastAPI, File, UploadFile, Form, Response, status @@ -47,4 +48,8 @@ async def create_upload_file(file: UploadFile | None, package: Annotated[str, Fo finally: file.file.close() - return {"message": f"Successfully uploaded {file.filename}"} \ No newline at end of file + return {"message": f"Successfully uploaded {file.filename}"} + + +if __name__ == "__main__": + uvicorn.run("server:app", port=5000, log_level="info") \ No newline at end of file