Mini Shell

Direktori : /opt/imh-python/lib/python3.9/site-packages/ngxconf/
Upload File :
Current File : //opt/imh-python/lib/python3.9/site-packages/ngxconf/cli.py

#!/opt/imh-python/bin/python3.9
# vim: set ts=4 sw=4 expandtab syntax=python:
"""

ngxconf.cli
Command-line functions & CLI entry-point

Copyright (c) 2017-2020 InMotion Hosting, Inc.
http://www.inmotionhosting.com/

@author J. Hipps <jacobh@inmotionhosting.com>

"""

import sys
import os
import time
import logging
import logging.handlers
from argparse import ArgumentParser, Action

import yaml
from yaml import CDumper, CLoader

from ngxconf import builder, fpm, control
from ngxconf.util import parse_user_default_conf, parse_profiles, get_profile, gconf, excepthook
from ngxconf import __version__, __date__

logger = logging.getLogger('ngxconf')

class SafeStoreAction(Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        super(SafeStoreAction, self).__init__(option_strings, dest, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        is_safe = False
        if 'SUDO_UID' in os.environ:
            if os.environ['SUDO_UID'] == '0':
                is_safe = True
            else:
                is_safe = False
        else:
            is_safe = True
        if is_safe:
            setattr(namespace, self.dest, values)
        else:
            print("ERROR: Attempted to set dangerous option as a non-root sudoer! Aborting.")
            sys.exit(249)


def setup_logging(clevel=logging.INFO, flevel=logging.DEBUG, logfile='/var/log/ngxconf.log'):
    """
    Setup logging
    """
    logger.setLevel(logging.DEBUG)

    # Console
    con = logging.StreamHandler()
    con.setLevel(clevel)
    con_format = logging.Formatter("%(levelname)s: %(message)s")
    con.setFormatter(con_format)
    logger.addHandler(con)

    # File
    try:
        flog = logging.handlers.WatchedFileHandler(logfile)
        flog.setLevel(flevel)
        flog_format = logging.Formatter("[%(asctime)s] %(name)s (%(process)d): %(levelname)s: %(message)s")
        flog.setFormatter(flog_format)
        logger.addHandler(flog)
    except Exception as e:
        logger.warning("Failed to open logfile: %s", str(e))

def parse_cli(show_help=False):
    """
    Parse CLI arguments
    """
    parser = ArgumentParser(description="Nginx user configuration builder for cPanel")
    parser.set_defaults(rebuildall=False, force=False, reload=False, user=None, outfile=None, defaults=False,
                        skipcheck=False, loglevel=logging.INFO, logfile='/var/log/ngxconf.log', nofpm=False,
                        nohttpd=False)

    parser.add_argument('--rebuildall', '-R', action='store_true',
                        help="Rebuild configuration for all users whose configuration has changed")
    parser.add_argument('--force', '-F', action='store_true',
                        help="Force rebuild even if the configuration has not changed")
    parser.add_argument('--reload', '-r', action='store_true',
                        help="Sends a SIGUSR2 signal to Nginx master process to reload the running config")
    parser.add_argument('--user', '-u', action='store', metavar="USER",
                        help="Specify user. Configuration will only be built for this user")
    parser.add_argument('--defaults', action='store_true',
                        help="Replace all of a user's config files with defaults")
    parser.add_argument('--skipcheck', action='store_true',
                        help="Skip Nginx config validation")
    parser.add_argument('--nofpm', action='store_true', help="Skip PHP-FPM rebuild and reload")
    parser.add_argument('--skipfpmbuild', action='store_true', help="Do not trigger /scripts/php_fpm_config --rebuild")
    parser.add_argument('--nohttpd', action='store_true', help="Skip Apache HTTPd reload")
    parser.add_argument('--skiphttpdbuild', action='store_true', help="Do not trigger /scripts/rebuildhttpdconf")
    parser.add_argument('--fork', action='store_true', help="Fork into the background; suppress all messages to stderr")
    parser.add_argument('--showconf', action='store_true', help="Output combined global configuration")
    parser.add_argument('--showprofile', action='store', metavar="PROF_ID",
                        help="Output combined profile by ID")
    parser.add_argument('--outfile', '-f', action='store', metavar="PATH",
                        help="Specify output file when building configuration for a single user")
    parser.add_argument('--logfile', '-l', action=SafeStoreAction, metavar="PATH",
                        help="Log output file")
    parser.add_argument('--debug', '-d', dest='loglevel', action='store_const', const=logging.DEBUG,
                        help="Enable debug output")
    parser.add_argument('--version', '-v', action='version', version="%s (%s)" % (__version__, __date__))

    if show_help:
        parser.print_help()
        sys.exit(1)

    return parser.parse_args()

def do_fork():
    """
    When --fork is used, ngxconf will fork into the background
    to complete its task
    """
    logger.info("Forking into background...")

    try:
        # first fork
        pid = os.fork()
    except Exception as e:
        logger.error("os.fork() failed, aborting: %s", str(e))
        sys.exit(251)

    if (pid == 0):
        # become parent of session & process group
        os.setsid()
        try:
            # second fork
            pid = os.fork()
        except Exception as e:
            logger.error("os.fork() [2] failed, aborting: %s", str(e))
            sys.exit(251)
        if pid:
            # ... and kill the other parent
            os._exit(0)

        logger.debug("Forked into background. PID: %d", os.getpid())

        try:
            # Redirect stdout & stderr to /dev/null
            sys.stdout.flush()
            sys.stdout = open(os.devnull, 'w')
            sys.stderr.flush()
            sys.stderr = open(os.devnull, 'w')

            # Redirect console logger to /dev/null
            logger.handlers[0].stream = open(os.devnull, 'w')
        except Exception as e:
            logger.error("Failed to redirect output streams: %s", str(e))

    else:
        # otherwise, kill the parent; _exit() so we don't mess with any
        # open file handles or streams
        logger.info("Background job running. Exiting main process (PID %d)", os.getpid())
        os._exit(0)

def _main():
    """
    Entry point
    """
    sys.excepthook = excepthook

    args = parse_cli()

    # NGX-37: suppress all console messages by default when using --fork
    #         to prevent cPanel programs from failing (since they try to parse
    #         JSON via stderr for some stupid reason)
    if args.fork and args.loglevel == logging.INFO:
        args.loglevel = logging.FATAL

    setup_logging(clevel=args.loglevel, flevel=logging.DEBUG, logfile=args.logfile)
    start_run = time.time()

    # Parse global config and additional user config files
    if gconf.apply_user_default_config:
        udefs = parse_user_default_conf()
    else:
        udefs = None
    parse_profiles()

    if args.showconf:
        print("---")
        print(yaml.dump(gconf._conf, Dumper=CDumper, default_flow_style=False))
        return 0
    elif args.showprofile:
        tprofile = get_profile(args.showprofile)
        if tprofile is not None:
            print("---")
            print(yaml.dump(tprofile, Dumper=CDumper, default_flow_style=False))
            return 0
        else:
            logger.error("Unable to locate profile: '%s'", args.showprofile)
            return 2
    elif args.user:
        if args.rebuildall:
            logger.error("Options --rebuildall and --user are mutually-exclusive")
            sys.exit(1)
        if args.fork:
            do_fork()
        changes = builder.rebuild_user(args.user, force=args.force, outfile=args.outfile,
                                       defaults=args.defaults, skip_fpm=args.nofpm, userdef=udefs)
    elif args.rebuildall:
        if args.defaults and not args.force:
            logger.error("CAUTION: Use of --defaults with --rebuildall will obliterate all user-defined settings "
                         "for all users! Use of --force is required. Aborting.")
            sys.exit(1)
        if args.fork:
            do_fork()
        changes = builder.rebuild_all(force=args.force, defaults=args.defaults, skip_fpm=args.nofpm, userdef=udefs)
    else:
        parse_cli(show_help=True)
        return 1

    # Check if services need to be reloaded
    retval = 0
    if not changes:
        retval = 2
    else:
        # Rebuild PHP-FPM config/pools if changes were made,
        # then rebuild/reload Apache
        if args.force or len(changes['fpm']) > 0:
            single_user = False if args.rebuildall else True
            if gconf.fpm_management == 'ngxconf':
                fpm.commit(args.force, single_user=args.user)
                if not args.skiphttpdbuild:
                    control.cp_rebuild_httpd_conf(reload=not args.nohttpd)
            elif gconf.fpm_management == 'cpanel' and not args.skipfpmbuild:
                control.rebuild_phpfpm()
                if args.reload:
                    control.restart_phpfpm()
                if not args.skiphttpdbuild:
                    control.cp_rebuild_httpd_conf(reload=not args.nohttpd)

        # Check/reload Nginx if changes were made
        if args.force or len(changes['nginx']) > 0:
            if not args.skipcheck:
                if not control.check_nginx():
                    retval = 3
            # Reload will only trigger if --reload is used
            if args.reload:
                control.reload_nginx()

    tot_run = time.time() - start_run
    logger.info("Finished run in %02.02f seconds", tot_run)
    return retval

if __name__ == '__main__':
    sys.exit(_main())

Zerion Mini Shell 1.0