/usr/lib/imh-ultrastack-common/venv/lib/python3.13/site-packages/ngxconf
NameSizeModeActions
__pycache__/-0755rm
builder.py318650644editdlrm
cli.py99970644editdlrm
control.py74070644editdlrm
cpfpm.py99180644editdlrm
fpm.py156450644editdlrm
masters.py67570644editdlrm
util.py132480644editdlrm
__init__.py66500644editdlrm
Edit: /usr/lib/imh-ultrastack-common/venv/lib/python3.13/site-packages/ngxconf/builder.py (31865B)
# vim: set ts=4 sw=4 expandtab syntax=python: """ ngxconf.builder Configuration builder functions Copyright (c) 2017-2020 InMotion Hosting, Inc. http://www.inmotionhosting.com/ @author J. Hipps """ import os import sys import codecs import re import pwd import json import logging import signal from glob import glob import yaml from yaml import CLoader import arrow import pem import netifaces from ngxconf import __version__, __date__, default_conf, default_header from ngxconf import fpm, cpfpm from ngxconf.util import load_template, gconf, dict_strip_mtime, cp_get_package_data, cp_get_userinfo, get_profile, NGXDumper logger = logging.getLogger('ngxconf') udefs = None def rebuild_all(force=False, defaults=False, skip_fpm=False, userdef=None): """ Rebuild configuration for all users """ global udefs udefs = userdef ngx_changes = [] fpm_changes = [] # Register signal handler & check for/register lock signal.signal(signal.SIGINT, sigint_handler) signal.signal(signal.SIGUSR1, sigusr_handler) check_lock(kill_previous=True) # Build default/catch-all server block, if enabled if gconf.enable_catchall: build_default() # Enumerate cPanel users try: ulist = os.listdir("/var/cpanel/users") logger.debug("Enumerated %d users", len(ulist)) except Exception as e: logger.error("Failed to enumerate users from /var/cpanel/users: %s", str(e)) return None # Load template t_server = load_template('user_server') # Rebuild config for each user for tuser in ulist: # Skip invalid users if tuser in ['system']: logger.debug("Skipping invalid user '%s'", tuser) continue nchg = rebuild_user(tuser, force=force, template=t_server, defaults=defaults, skip_fpm=skip_fpm) if nchg: ngx_changes += nchg['nginx'] fpm_changes += nchg['fpm'] # Purge config files for users who no longer exist clist = set() try: for tfile in os.listdir("/etc/nginx/vhosts"): clist.add(tfile.split('__')[0]) except Exception as e: logger.error("Failed to enumerate vhost includes: %s", str(e)) nlist = clist - set(ulist) for tuser in nlist: try: tglob = glob("/etc/nginx/vhosts/%s__*.conf" % (tuser)) except Exception as e: logger.error("Failed to enumerate vhost includes for %s: %s", tuser, str(e)) continue for tfile in tglob: try: os.unlink(tfile) logger.debug("Removed stale file: %s", tfile) fpm_changes.append(tfile.split('__', 1)[1].replace('_', '.')) except Exception as e: logger.error("Failed to remove stale Nginx include %s: %s", tfile, str(e)) continue # Finish up: clear lock & return number of changes logger.info("Rebuilt configuration for %d users -- CHANGED: Nginx: %d / FPM: %d", len(ulist), len(ngx_changes), len(fpm_changes)) clear_lock() return {'nginx': ngx_changes, 'fpm': fpm_changes} def rebuild_user(user, force=False, outfile=None, template=None, defaults=False, skip_fpm=False, userdef=None): """ Rebuild configuration for single user If @defaults is True, reverts all user configs to defaults """ global udefs if userdef: udefs = userdef # Parse cPanel userdata udata = parse_userdata(user) if udata is None: logger.error("Rebuild of user '%s' failed", user) return None # Parse user info file uinfo = cp_get_userinfo(user) if uinfo is None: logger.error("update_user: aborting due to invalid user info") return None # Parse user configuration (creating default configs if the don't exist) uconf = parse_userconf(user, udata, uinfo, defaults=defaults, force=force) # Update php-fpm configuration if gconf.enable_fpm and not skip_fpm: if gconf.fpm_management.lower() == 'ngxconf': fpm_updates = fpm.update_user(user, udata, uconf, uinfo, force=force) elif gconf.fpm_management.lower() == 'cpanel': vlist = [x for x in udata.keys() if not x.startswith('_') and not uconf.get(x, {}).get('_exclude', False)] fpm_updates = cpfpm.update_fpm(user, vlist, force=force) else: logger.error("FPM CONFIGURATION NOT BUILT: Unrecognized `fpm_system` selection '%s'", gconf.fpm_system) fpm_updates = [] if len(fpm_updates) > 0: logger.info("PHP-FPM configuration updated for %s [%s]", user, ', '.join(fpm_updates)) else: fpm_updates = [] # Get mtimes of last nginx conf builds utime = get_last_nconf_updates(user) # Load template if template is None: t_server = load_template('user_server') else: t_server = template # Ensure user cache zones exist try: cache_path = '%s/%s' % (gconf.cache_base_path, user) os.stat(cache_path) except: try: os.mkdir(cache_path) except Exception as e: logger.error("Unable to create user cache zone directory: %s", str(e)) # Ensure user cache zone perms/ownership try: ngx_pwd = pwd.getpwnam(gconf.nginx_user) os.chmod(cache_path, 0o0775) os.chown(cache_path, ngx_pwd.pw_uid, ngx_pwd.pw_gid) except Exception as e: logger.error("Unable to set user cache zone perms/ownership: %s", str(e)) # Build Nginx config for each domain, if it has changed (or force=True) # tconf = user config, tdat = cpanel userdata, tdom = domain ngx_updates = [] for tdom, tdat in udata.items(): if tdom.startswith('_'): continue tconf = uconf.get(tdom) if tconf: # Check for user-defined domain includes uinclude = None if gconf.allow_user_includes: try: incpath = '/home/%s/.imh/nginx/%s.inc.conf' % (user, tdom.replace('.', '_')) if os.path.exists(incpath): with open(incpath, 'r') as f: uinclude = f.read() logger.debug("Read %d lines from user include %s", len(uinclude.splitlines()), incpath) else: logger.debug("No user include found at %s", incpath) except Exception as e: logger.error("Failed to read user include file at [%s]: %s", incpath, str(e)) # Check to see if the userdata _SSL file was changed try: ssl_uconf_changed = utime.get(tdom, 0) < udata[tdom]['_ssl']['_mtime'] except: ssl_uconf_changed = False try: if tconf.get('_exclude') is True: logger.debug("Excluding %s:%s", user, tdom) try: outconf = "/etc/nginx/vhosts/%s__%s.conf" % (user, tdom.replace('.', '_')) os.unlink(outconf) except: pass elif force or (utime.get(tdom, 0) < udata[tdom]['_mtime']) \ or (utime.get(tdom, 0) < uconf[tdom]['_mtime']) \ or (utime.get(tdom, 0) < int(os.stat(f"/var/cpanel/users/{user}").st_mtime)): # render user nginx config from template b_main = (tdom == udata['_main']['main_domain']) ngx_updates.append(tdom) if outfile: render_config(user, tdom, tconf, tdat, t_server, is_main=b_main, basepath=outfile, userinc=uinclude) else: render_config(user, tdom, tconf, tdat, t_server, is_main=b_main, userinc=uinclude) else: logger.debug("Skipping %s:%s -- up-to-date", user, tdom) except Exception as e: logger.error("Render for %s:%s failed: %s", user, tdom, str(e)) else: logger.warning("Skipping rebuild for %s:%s due to invalid data", user, tdom) # Remove config files for any domains that have been removed for tdom in utime.keys(): if not udata.get(tdom): tpath = "/etc/nginx/vhosts/%s__%s.conf" % (user, tdom.replace('.', '_')) try: os.unlink(tpath) logger.debug("Removed stale file: %s", tpath) fpm_updates.append(tdom) except Exception as e: logger.error("Unable to remove stale config %s: %s", tpath, str(e)) return {'nginx': ngx_updates, 'fpm': fpm_updates} def build_default(outconf='/etc/nginx/conf.d/default.conf', use_ipv6=False): """ Build default.conf from template """ template = load_template('default_server') # get list of IPs iplist = [] ilraw = map(lambda x: map(lambda y: y.get('addr'), netifaces.ifaddresses(x).get(netifaces.AF_INET, [])), netifaces.interfaces()) if use_ipv6: ilraw += map(lambda x: map(lambda y: y.get('addr'), netifaces.ifaddresses(x).get(netifaces.AF_INET6, [])), netifaces.interfaces()) for tip in ilraw: iplist += tip # filter out unwanted IPs iplist = list(set([x for x in iplist if not re.match(r'^(127\.0\.0\.|::1|fe80|fd00)', x)])) logger.debug("Detected %d IPs on the server: %s", len(iplist), ', '.join(iplist)) # determine main IP try: with open('/var/cpanel/mainip', 'r') as f: main_ip = f.read().strip() except Exception as e: logger.error("Failed to read main IP from /var/cpanel/mainip: %s", str(e)) main_ip = iplist[0] logger.debug("Got main IP: %s", main_ip) # write system service ssl pem/key dconf = {'ssl_certificate': None, 'ssl_key': None} cpath = '/var/cpanel/ssl/cpanel/mycpanel.pem' dconf['ssl_certificate'], dconf['ssl_key'] = create_cert_from_combined('mycpanel', cpath) # render with Jinja2 try: ttext = template.render(tstamp=arrow.now().format(), ips=iplist, dconf=dconf, tls_cipher_list=gconf.tls_cipher_list, main_ip=main_ip, ngxconf_ver="%s (%s)" % (__version__, __date__)) except Exception as e: logger.error("Failed to render default config: %s", str(e)) return False # write new file try: with open(outconf, 'w') as f: f.write(ttext) logger.debug("Rendered default config to %s", outconf) except Exception as e: logger.error("Failed to render default config to file %s: %s", outconf, str(e)) return False return True def render_config(user, userdom, userconf, userdata, template, is_main=False, basepath='/etc/nginx/vhosts', userinc=None): """ Compile template using Jinja2 """ outconf = "%s/%s__%s.conf" % (basepath, user, userdom.replace('.', '_')) # create list of cPanel domains cpdomains = {'cpanel': [], 'webmail': []} try: for talias in userdata['serveralias'].split(): if not talias.startswith("www.") and not talias.startswith("mail."): cpdomains['cpanel'].append("cpanel." + talias) cpdomains['webmail'].append("webmail." + talias) except Exception as e: logger.error("Failed to create list of cPanel subdomains for %s: %s", userdom, str(e)) cpdomains = {'cpanel': None, 'webmail': None} # render with Jinja2 try: ttext = template.render(tstamp=arrow.now().format(), domain=userdom, cache_name=user, is_main=is_main, cache_key_format=gconf.cache_key_format, user=user, uconf=userconf, http_ip=gconf.http_listen_interface, cp_domains=cpdomains, rl_whitelist=gconf.use_ratelimit_whitelist, restrict_purge=gconf.restrict_purge, tls_cipher_list=gconf.tls_cipher_list, udata=userdata, user_include=userinc, cpanel_host_redirect=gconf.cpanel_host_redirect, manage_tls_options=gconf.manage_tls_options, ngxconf_ver="%s (%s)" % (__version__, __date__), proxy_cpanel=gconf.proxy_cpanel) except Exception as e: logger.error("Failed to render template for %s:%s: %s", user, userdom, str(e)) return False # write new include file try: with codecs.open(outconf, 'w', 'utf-8', errors='ignore') as f: f.write(ttext) logger.debug("Rendered config for %s:%s to %s", user, userdom, outconf) except Exception as e: logger.error("Failed to render template to file %s: %s", outconf, str(e)) return False return True def parse_userconf(user, udata, uinfo, defaults=False, force=False): """ Parse Nginx user config files Default configuration will be created if missing """ global udefs idir = '/home/%s/.imh' % (user) udir = '/home/%s/.imh/nginx' % (user) # Check for config dir try: os.stat(udir) except: try: os.makedirs(udir, mode=0o0775) logger.debug("Created directory %s", udir) except Exception as e: logger.error("Unable to create directory %s: %s", udir, str(e)) return False if udata is None: logger.error("No userdata associated with '%s' account; skipping", udata) return None # Get user UID/GID try: u_pwd = pwd.getpwnam(user) except Exception as e: logger.error("Failed to translate username '%s' to UID/GID: %s", user, str(e)) return None # Ensure ~/.imh dir perms/ownership try: os.chmod(idir, 0o0775) os.chown(idir, u_pwd.pw_uid, u_pwd.pw_gid) except Exception as e: logger.warning("Unable to set permissions/ownership of %s: %s", udir, str(e)) # Ensure nginx dir perms/ownership try: os.chmod(udir, 0o0775) os.chown(udir, u_pwd.pw_uid, u_pwd.pw_gid) except Exception as e: logger.warning("Unable to set permissions/ownership of %s: %s", udir, str(e)) package_data = cp_get_package_data(uinfo.get('PLAN')) tprofile = package_data.get('IMH_NGX_PROFILE') ucdata = {} for tdom, tdat in udata.items(): if tdom.startswith('_'): continue tfile = '%s/%s.yml' % (udir, tdom.replace('.', '_')) # Check if file exists; if not, create with default contents try: ustat = os.stat(tfile) except Exception: ustat = None # Also revert back to defaults if @defaults is True if ustat is None or defaults: logger.debug("Copying default userconf to %s", tfile) create_default_userconf(user, tfile, tdat, profile=tprofile) try: ustat = os.stat(tfile) except Exception as e: logger.error("Userconf not found (creation failed) %s: %s", tfile, str(e)) continue try: with open(tfile, 'r') as f: ucdata[tdom] = {} ucdata[tdom].update(default_conf) ucdata[tdom].update(yaml.load(f, Loader=CLoader)) ucdata[tdom]['_mtime'] = int(ustat.st_mtime) except Exception as e: logger.error("Failed to load userconf for %s: %s", tdom, str(e)) logger.warning("Reverting user config to defaults due to parsing errors: %s", tfile) create_default_userconf(user, tfile, tdat, profile=tprofile) try: with open(tfile, 'r') as f: ucdata[tdom] = yaml.load(f, Loader=CLoader) ucdata[tdom]['_mtime'] = int(os.stat(tfile).st_mtime) except Exception as e: ucdata[tdom] = None logger.error("Default userconf creation failed %s: %s", tfile, str(e)) continue # Check if user_default_override_local is enabled override_update = False if gconf.user_default_override_local and udefs: preup = json.dumps(ucdata[tdom]) ucdata[tdom].update(udefs) postup = json.dumps(ucdata[tdom]) if postup != preup: logger.info("Applied user overrides to %s; domain config updated", tdom) override_update = True else: logger.debug("User overrides already applied to %s; no change detected", tdom) # Set override to disable nginx cache if package_data.get('IMH_DISABLE_CACHE') == "1": if ucdata[tdom]['pass_all'] != True: logger.info("Applied 'disable cache' user overrides to %s; domain config updated", tdom) ucdata[tdom]['pass_all'] = True override_update = True else: logger.debug("User overrides 'disable cache' already applied to %s; no change detected", tdom) # Check if SSL options are up-to-date ssl_update = False if tdat.get('_ssl'): try: certpath = "%s/%s.pem" % (gconf.chained_cert_path, tdom.replace('-', '--').replace('.', '-')) mtime_cert = int(os.stat(certpath).st_mtime) except: mtime_cert = 0 if force or tdat['_ssl']['_mtime'] > ucdata[tdom]['_mtime'] or \ tdat['_ssl']['_mtime'] > mtime_cert: # Update SSL cert combpath = '/var/cpanel/ssl/apache_tls/%s/combined' % (tdat['servername']) if os.path.exists(combpath): newcert, newkey = create_cert_from_combined(tdat['servername'], combpath) else: newcert = create_chained_cert(tdat['servername'], tdat['_ssl'].get('sslcertificatefile'), tdat['_ssl'].get('sslcacertificatefile')) newkey = tdat['_ssl'].get('sslcertificatekeyfile') if newcert is not None and newkey is not None: ucdata[tdom]['ssl_enabled'] = True ucdata[tdom]['ssl_certificate'] = newcert ucdata[tdom]['ssl_certificate_key'] = newkey ucdata[tdom]['proxy_proto'] = "https" ssl_update = True else: ucdata[tdom]['ssl_enabled'] = False ucdata[tdom]['proxy_proto'] = "http" ucdata[tdom]['ssl_certificate'] = None ucdata[tdom]['ssl_certificate_key'] = None logger.warning("Invalid SSL vhost defined for %s", user) else: if ucdata[tdom].get('ssl_enabled'): # Remove old chained cert try: os.unlink(ucdata[tdom]['ssl_certificate']) logger.info("Removed unused chained certificate: %s", ucdata[tdom]['ssl_certificate']) except Exception as e: logger.warning("Failed to removed chained certificate %s: %s", ucdata[tdom]['ssl_certificate'], str(e)) ucdata[tdom]['ssl_enabled'] = False ucdata[tdom]['proxy_proto'] = "http" ucdata[tdom]['ssl_certificate'] = None ucdata[tdom]['ssl_certificate_key'] = None ssl_update = True # Write updated userconf YAML file if SSL or override params have changed if ssl_update or override_update: if ssl_update: logger.info("Updated ssl_enabled status for %s -> %s", tdom, ucdata[tdom]['ssl_enabled']) try: with open(tfile, 'w') as f: f.write(default_header.strip() + '\n') yaml.dump(dict_strip_mtime(ucdata[tdom]), stream=f, Dumper=NGXDumper, default_flow_style=False) logger.debug("Updated SSL and/or override parameters in %s", tfile) except Exception as e: logger.error("Unable to write updated YAML file: %s", str(e)) # Ensure correct file permissions try: os.chmod(tfile, 0o0664) os.chown(tfile, u_pwd.pw_uid, u_pwd.pw_gid) except Exception as e: logger.warning("Unable to set permissions/ownership of %s: %s", tfile, str(e)) # Grab new mtime so that Nginx conf will be updated during the template rendering try: udata[tdom]['_mtime'] = int(os.stat(tfile).st_mtime) except Exception as e: logger.error("Failed to stat updated userconf %s: %s", tfile, str(e)) logger.debug("Parsed userconf for %s", user) return ucdata def create_default_userconf(user, ufile, udata, profile=None): """ Create YAML file containing default user config """ global udefs # Setup default base config based on chosen profile profile_name = profile cdata = None if profile is not None: cdata = get_profile(profile) if cdata is None: cdata = {} cdata.update(default_conf) profile_name = "(builtin_default)" if udefs: cdata.update(udefs) if udata.get('_ssl'): combpath = '/var/cpanel/ssl/apache_tls/%s/combined' % (udata['servername']) if os.path.exists(combpath): newcert, newkey = create_cert_from_combined(udata['servername'], combpath) else: newcert = create_chained_cert(udata['servername'], udata['_ssl'].get('sslcertificatefile'), udata['_ssl'].get('sslcacertificatefile')) newkey = udata['_ssl'].get('sslcertificatekeyfile') if newcert is not None and newkey is not None: cdata['ssl_enabled'] = True cdata['ssl_certificate'] = newcert cdata['ssl_certificate_key'] = newkey cdata['proxy_proto'] = "https" else: logger.warning("Invalid SSL vhost defined for %s", user) # Write userconf YAML file try: with open(ufile, 'w') as f: f.write(default_header.strip() + '\n') yaml.dump(dict_strip_mtime(cdata), stream=f, Dumper=NGXDumper, default_flow_style=False) except Exception as e: # On VZ containers root can't create, chown, or write to a user:user file if quota is exceeded for user # To work around this, remove the user:user file and recreate it as root:root (which ngxconf can read/write) if "Disk quota exceeded" in str(e): logger.error('Disk quota exceeded. Unable to write to %s', ufile) os.unlink(ufile) with open(ufile, 'w') as f: f.write(default_header.strip() + '\n') yaml.dump(dict_strip_mtime(cdata), stream=f, Dumper=NGXDumper, default_flow_style=False) logger.debug("Wrote default configuration for %s from profile %s as root:root", user, profile_name) return True logger.error("Unable to write default userconf %s: %s", ufile, str(e)) return False # Get user UID/GID try: u_pwd = pwd.getpwnam(user) except Exception as e: logger.error("Failed to translate username '%s' to UID/GID: %s", user, str(e)) return None # Ensure correct file permissions try: os.chmod(ufile, 0o0664) os.chown(ufile, u_pwd.pw_uid, u_pwd.pw_gid) except Exception as e: logger.warning("Unable to set permissions/ownership of %s: %s", ufile, str(e)) logger.debug("Wrote default configuration for %s from profile %s", user, profile_name) return True def create_cert_from_combined(domain, cpath): """ Create a chained certificate from cPanel combined TLS data """ chained = "%s/%s.pem" % (gconf.chained_cert_path, domain.replace('-', '--').replace('.', '-')) keypath = "%s/%s.key" % (gconf.chained_cert_path, domain.replace('-', '--').replace('.', '-')) ckey = "" certdata = "" # Parse combined PEM certificate try: clist = pem.parse_file(cpath) except Exception as e: logger.error("Failed to parse combined cert for %s at %s: %s", domain, cpath, str(e)) return (None, None) for tpem in clist: if isinstance(tpem, pem.Key): ckey = str(tpem) elif isinstance(tpem, pem.Certificate): certdata += str(tpem) else: logger.warning("Found unexpected PEM component of type <%s> in %s", type(tpem), cpath) if not len(ckey) or not len(certdata): logger.error("Missing certificate and/or keys from combined TLS store at %s", cpath) return (None, None) # Write chained cert try: with open(chained, 'w') as f: f.write(certdata) logger.debug("Wrote chained certificate from cP 68+ combined format: %s", chained) except Exception as e: logger.error("Failed to write chained certificate for %s: %s", domain, str(e)) return (None, None) # Write key try: with open(keypath, 'w') as f: f.write(ckey) os.chmod(keypath, 0o0600) logger.debug("Wrote keyfile from cP 68+ combined format: %s", keypath) except Exception as e: logger.error("Failed to write keyfile for %s: %s", domain, str(e)) return (None, None) return (chained, keypath) def create_chained_cert(domain, certpath, capath, outfile=None): """ Created a chained certificate consisting of the certificate + CA bundle """ if outfile is None: chained = "%s/%s.pem" % (gconf.chained_cert_path, domain.replace('-', '--').replace('.', '-')) else: chained = outfile # Read certificate try: with open(certpath, 'r') as f: certdata = f.read().strip() except Exception as e: logger.error("Failed to read certificate file for %s: %s", domain, str(e)) return None # Read intermediates (cabundle) if capath is not None: try: with open(capath, 'r') as f: cadata = f.read().strip() except Exception as e: logger.error("Failed to read CA bundle for %s: %s", domain, str(e)) cadata = "" else: logger.warning("No CA bundle defined for %s", domain) cadata = "" # Write chained cert try: with open(chained, 'w') as f: f.write(certdata + '\n' + cadata) logger.debug("Wrote chained certificate: %s", chained) except Exception as e: logger.error("Failed to write chained certificate for %s: %s", domain, str(e)) return None return chained def parse_userdata(user): """ Parse cPanel userdata for @user """ main = parse_userdata_file(user, 'main') if main is None: logger.warning("Abort parsing userdata for user '%s'", user) return None domlist = [main['main_domain']] + main['sub_domains'] udata = {'_main': main, '_suspended': check_suspended(user)} for tdom in domlist: tdata = parse_userdata_file(user, tdom) tssl = parse_userdata_file(user, tdom, check_ssl=True) if tdata is None: continue if tssl: try: tdata['_ssl'] = tssl except Exception as e: logger.error("Failed to extract SSL information for %s:%s - %s", user, tdom, str(e)) tdata['_ssl'] = None else: tdata['_ssl'] = None udata[tdom] = {**tdata, '_suspended': udata['_suspended']} return udata def parse_userdata_file(user, ufile, check_ssl=False, prefix='/var/cpanel/userdata'): """ Parse single cPanel userdata file; injects [_mtime] field from stat result """ datfile = "%s/%s/%s" % (prefix, user, ufile) if check_ssl: datfile += "_SSL" try: os.stat(datfile) except: return None try: with open(datfile, 'r') as f: ddata = yaml.load(f, Loader=CLoader) ddata['_mtime'] = int(os.stat(datfile).st_mtime) return ddata except Exception as e: logger.error("Failed to parse userdata file %s: %s", datfile, str(e)) return None def check_suspended(user): """ Check if @user is suspended; if so, return a normalized reason string, otherwise, return None """ suspath = "/var/cpanel/suspended/%s" % (user) try: os.stat(suspath) with open(suspath, 'r') as f: rreason = f.read().strip() except: return None try: rd = re.match(r'^((?:(?P[a-z]+):(?P.+))|(?:\[PP2 Auto\] - Reason: (?P.+)))$', rreason).groupdict() return (rd['reasonA'] if rd['reasonA'] else rd['reasonB']).strip().lower() except: logger.warning("Failed to parse suspension reason from %s", suspath) return 'unknown' def get_last_nconf_updates(user, basedir='/etc/nginx/vhosts'): """ Return a dict of mtime (int) for each nginx config file """ nconf = {} for tpath in glob("%s/%s__*.conf" % (basedir, user)): # extract domain from filename tfile = os.path.basename(tpath) try: tdom = tfile.replace('.conf', '').split('__', 1)[1].replace('_', '.') except: logger.error("Failed to parse domain from vhost config: %s", tfile) continue # grab the mtime try: nconf[tdom] = int(os.stat(tpath).st_mtime) except Exception as e: logger.error("Failed to stat vhost config %s: %s", tpath, str(e)) continue return nconf def check_lock(lockfile='/var/run/ngxconf.pid', kill_previous=False): """ Check if lockfile exists; if not, create one If lockfile DOES exist and @kill_previous is True, kills the previous ngxconf runner and create a new lockfile """ try: os.stat(lockfile) with open(lockfile, 'r') as f: old_pid = int(f.read()) os.kill(old_pid, 0) logger.warning("ngxconf is already running with PID %d", old_pid) if kill_previous: logger.warning("Halting running instance with SIGUSR1. This ngxconf will take over.") os.kill(old_pid, signal.SIGUSR1) else: logger.error("Aborting due to ngxconf already running (%d)", old_pid) sys.exit(10) except: pass # Write new lock file with current PID tpid = str(os.getpid()) with open(lockfile, 'w') as f: f.write(tpid) logger.debug("Wrote lock file %s (PID %s)", lockfile, tpid) def clear_lock(lockfile='/var/run/ngxconf.pid'): """ Remove lock/pidfile """ try: os.unlink(lockfile) logger.debug("Lock file removed: %s", lockfile) return True except Exception as e: logger.warning("Failed to remove lockfile %s: %s", lockfile, str(e)) return False def sigusr_handler(sig, frame): # pylint: disable=unused-argument """ Signal handler for SIGUSR1 """ logger.error("Received signal from another running ngxconf. Aborting.") sys.exit(10) def sigint_handler(sig, frame): # pylint: disable=unused-argument """ Signal handler for SIGINT (interrupt) """ logger.error("Interrupted.") clear_lock() sys.exit(11)