"""deploy gunicorn""" import os import subprocess # import glob # import tinnus import re from pathlib import Path import filing from nitpo import Nitpo class Deploy(Nitpo): def __init__(self, do_verbose=False): super().__init__() if not self.has_conf('files', 'socket'): print("I need a socket file configure") quit() self.user = os.getlogin() self.lib_fudi = os.path.dirname(os.path.abspath(filing.__file__)) self.nitpo_fudi = os.path.dirname(self.lib_fudi) self.bin_fudi = self.nitpo_fudi + '/bin' self.js_fudi = self.nitpo_fudi + '/contrib/js' self.socket_bin = self.nitpo_fudi + '/contrib/bin/socket' self.home_fudi = os.environ['HOME'] if not self.has_conf('files', 'socket'): print("I need a Ф[files][socket]") self.socket_fufi = self.conf['files']['socket'] if not self.has_conf('files', 'guniconf'): print("I need a gunicorn configuration file defined.") quit() self.guniconf_fufi = self.conf['files']['guniconf'] if not self.has_conf('web', 'pathstart'): print("I need a web path start Ф[web][pathstart]") quit() self.pathstart = self.conf['web']['pathstart'] self.log_dir = self.conf['folders']['web_log'] ## this is not used yet. systemd_fudi = '/usr/lib/systemd/system' name = self.const['name'] self.NAME = name.upper() self.service_outfufi = systemd_fudi + '/' + name + '.service' self.socket_outfufi = systemd_fudi + '/' + name + '.socket' self.bar = 80 * '#' self.text = {} self.verbose = do_verbose def get_socket_text(self): name = self.const['name'] text = '' text += f"[Unit]\nDescription={name} socket\n\n[Socket]\n" text += f"ListenStream={self.socket_fufi}\n" text += "SocketMode=0777\n" text += "SocketUser=ernad\n\n" text += "[Install]\nWantedBy=sockets.target\n" self.socket_text = text return text def get_service_text(self, do_root=True): name = self.const['name'] text = '' text += f"[Unit]\nDescription={name} service\n" text += f"Requires={name}.socket\nAfter=network.target\n\n" text += f'[Service]\nEnvironment="PYTHONPATH={self.lib_fudi}"\n' text += "Type=notify\n" if do_root: text += f"User={self.user}\nGroup={self.user}\n" text += "ExecStart=/usr/bin/gunicorn" #text += " --capture-output" #text += f" --log-file {self.log_dir}/out" #text += f" --error-logfile {self.log_dir}/err" #text += f" --access-logfile {self.log_dir}/log" text += f" -c {self.guniconf_fufi} server:app\n" text += 'ExecReload=/bin/kill -s HUP $MAINPID\n' text += "KillMode=mixed\nTimeoutStopSec=5\nPrivateTmp=true\n\n" text += "[Install]\nWantedBy=sockets.target\n" self.service_text = text return text def user_write(self, kind): """for user space, not used""" name = self.const['name'] systemd_fudi = self.home_fudi + '/.config/systemd/user' if not os.path.isdir(systemd_fudi): Path(systemd_fudi).mkdir(parents=True, exist_ok=True) if kind == 'service': text = self.get_service_text(do_root=False) elif kind == 'socket': text = self.get_socket_text() else: raise Exception("kind must be 'service' or 'socket'") systemd_fufi = systemd_fudi + f'/{name}.' + kind filing.srite(systemd_fufi, text, do_verbose=True) def say_apache(self): name = self.const['name'] socket = f"unix:{self.socket_fufi}|http://127.0.1.1/" a = '\n## insert into the apache configuration: \n' a += self.bar + '\n' a += f"ProxyPass /{name} {socket}\n" a += f"ProxyPassReverse {self.pathstart} {socket}\n" a += self.bar + '\n\n' a += '## if you make use of the javascript, you may need\n' a += self.bar + '\n' a += f"Alias /js/{name}.js {self.js_fudi}/web.js\n" a += self.bar print(a) def say_systemd(self): name = self.const['name'] user = self.user NAME = self.NAME t = f'\n## create {self.socket_outfufi} with\n' t += self.bar + '\n' t += self.get_socket_text() t += self.bar + '\n' t += f'\n## create {self.service_outfufi} with\n' t += self.bar + '\n' t += self.get_service_text() t += self.bar + '\n\n' t += '## Run\n' t += f'systemctl daemon-reload ; systemctl enable --now {name}.socket' t += f'\nsystemctl stop {name}.socket' t += f' ; systemctl start {name}.socket' + '\n\n' t += "## Don't delete the socket file. If you do, root has to stop and" t += "\n## start the nitpo.socket using systemd for you. The socket\n" t += "## may also disappear when guniconf is given bad application\n" t += f"## code. Therefore it is usefule to allow {user} to restart\n" t += f"## the unit {name}.socket. Using visudo, create the file\n" t += f"## /etc/sudoers.d/{name}\n" t += self.bar + '\n' t += f"User_Alias {NAME} = {user}\n" t += f"Cmnd_Alias {NAME}START = /bin/systemctl restart {name}.socket\n" t += f"NITPO ALL= (root) NOPASSWD: {NAME}START\n" t += self.bar + '\n' t += f"## This will allow {user} to make use of {self.socket_bin}\n" print(t) def systemd_socket_fufi(self): """read the socket file that systemd set""" if not os.path.isfile(self.socket_outfufi): return None socket_text = filing.sread(self.socket_outfufi) ## it makes no sense to compile this matches = re.search(r"\nListenStream\s*=\s*(\S+)", socket_text) if matches is None: raise Exception("deploy does not see the socket in\n" + socket_text) return matches.group(1) def systemd_guniconf_fufi(self): """read the current gunicorn configuration file that systemd sets.""" if not os.path.isfile(self.service_outfufi): return None service_text = filing.sread(self.service_outfufi) ## it makes no sense to compile this s = 'ExecStart=/usr/bin/gunicorn' matches = re.search(s + r".* +(.+.py)", service_text) if matches is None: raise Exception("deploy does not see the gunicorn config in\n" + service_text) return matches.group(1)