PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/sharedrads/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/sharedrads/upgrade-check |
#!/opt/imh-python/bin/python3 # vim: set ts=4 sw=4 expandtab syntax=python: # the dash in this script's filename sets off invalid-name # pylint: disable=invalid-name """Change plan eligibility checker tool""" from pathlib import Path import sys import json import logging from argparse import ArgumentParser from cpapis import uapi, cpapi2, CpAPIError __version__ = "1.1.0" __date__ = "08 Nov 2021" logger = logging.getLogger('upgrade-check') def setup_logging(clevel=logging.INFO, flevel=logging.DEBUG, logfile=None): """ 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 if logfile: try: flog = logging.handlers.WatchedFileHandler(logfile) flog.setLevel(flevel) flog_format = logging.Formatter( "[%(asctime)s] %(name)s: %(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="Change plan eligibility checker tool") # fmt: off parser.set_defaults(user=None, showmail=False) parser.add_argument('user', metavar="USER", help="Username to check") parser.add_argument( '--plan', '-p', metavar="PLAN", help="Plan to check against" ) parser.add_argument( '--showmail', '-m', action='store_true', help="Show detailed email usage", ) 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=f"{__version__} ({__date__})", ) # fmt: on if show_help: parser.print_help() sys.exit(1) return parser.parse_args() def format_size(insize, rate=False, bits=False): """format human-readable file size and xfer rates""" onx = float(abs(insize)) for u in ['B', 'K', 'M', 'G', 'T', 'P']: if onx < 1024.0: tunit = u break onx /= 1024.0 suffix = "" if tunit != 'B': suffix = "iB" if rate: if bits: suffix = "bps" onx *= 8.0 else: suffix += "/sec" if tunit == 'B': ostr = "%3d %s%s" % (onx, tunit, suffix) else: ostr = f"{onx:3.01f} {tunit}{suffix}" return ostr def mkpct(ival, tot): """Make value/percent string""" try: po = f"{ival:d} ({(float(ival) / float(tot)) * 100.0:3.01f}%)" except Exception: po = "0 (-.-%)" return po def get_account_summary(username): """Call UAPI to get domain, DB, and other usage info""" try: ret = uapi('DomainInfo::list_domains', user=username, check=True) except CpAPIError as exc: logger.error(str(exc)) return None return ret['result']['data'] def get_db_count(username): """Get mySQL database count""" try: with open( Path('/var/cpanel/datastore', username, 'mysql-db-count'), encoding='ascii', ) as file: dcount = int(file.read().strip()) except Exception as e: logger.error("Failed to get DB count: %s", str(e)) return None return dcount def get_disk_usage(username): """Check filesystem quotas for usage""" try: ret = cpapi2('DiskUsage::fetchdiskusage', user=username) except CpAPIError as exc: logger.error(str(exc)) return None try: usage = int(ret['cpanelresult']['data'][0]['contained_usage'] / 2**20) except Exception as exc: logger.error("Failed to parse cpapi2 result: %s", exc) return None return usage def get_mail_accounts(username): try: with open( Path('/home', username, '.cpanel/email_accounts.json'), encoding='ascii', ) as file: eraw = file.read().strip() if len(eraw) > 1: ejson = json.loads(eraw) else: ejson = {} logger.debug( "email_accounts.json is empty. assuming 0 accounts." ) except Exception as exc: logger.error("failed to open email_accounts.json for user: %s", exc) return None accounts = [] for tdomain in ejson.keys(): if tdomain.startswith('_'): continue for taccount, tinfo in ejson[tdomain]['accounts'].items(): accounts.append( { 'suspended': tinfo['suspended_login'], 'domain': tdomain, 'user': taccount, 'email': f"{taccount}@{tdomain}", 'used': int(tinfo['diskused']), 'quota': int(tinfo.get('diskquota', 0)), } ) return accounts def print_accounts(accounts): """Print list of email accounts by size""" print("\n************ Email Accounts ************") print("{:40} {:>10} / {:>10}".format("Email", "DiskUsed", "Quota")) for taccount in accounts: print( "{:40} {:>10} / {:>10}".format( taccount['email'], format_size(taccount['used']), format_size(taccount['quota']), ) ) def get_plan_params(): """Read upgrade_params from file ./etc/upgrade_params.json""" ppath = Path(__file__).resolve().parent / 'etc/upgrade_params.json' try: with open(ppath, encoding='ascii') as f: params = json.load(f)['packages'] except Exception as e: logger.error("Failed to read account params from file: %s", str(e)) return None return params def get_account_stats(domlist, dbcount, maillist, acctsize): """Calculate account stats from gathered params""" if len(maillist) > 0: maxmailsize = int( max(x['used'] for x in maillist if not x['suspended']) / (1024 * 1024) ) else: maxmailsize = 0 account = { 'disk': acctsize, 'db': dbcount, 'addon': len(domlist['addon_domains']), 'parked': len(domlist['parked_domains']), 'sub': len(domlist['sub_domains']), 'mailacct': len([x for x in maillist if not x['suspended']]), 'maxmailsize': maxmailsize, } return account def check_plan(stat, plan): """Compare user stats versus plandata""" return ( _fits_plan(stat['disk'], plan['quota']) and _fits_plan(stat['db'], plan['maxdb']) and _fits_plan(stat['addon'], plan['maxaddon']) and _fits_plan(stat['addon'], plan['maxaddon']) and _fits_plan(stat['parked'], plan['maxpark']) and _fits_plan(stat['sub'], plan['maxsub']) and _fits_plan(stat['mailacct'], plan['maxpop']) and _fits_plan(stat['maxmailsize'], plan['max_emailacct_quota']) ) def _fits_plan(value, quota): if quota == -1: return True return value <= quota def check_one_plan(astat, plan): """Check a single plan and return yes/no""" try: plandata = PLANS[plan] except Exception: logger.error("Plan '%s' does not exist", plan) return None if check_plan(astat, plandata): print("yes") return True print("no") return False def yesno(ibool): """Colorize yes/no from true/false""" if ibool: return "\033[92mYES\033[0m" return "\033[91mNO\033[0m" def check_all_plans(username, astat): """Print a table of all compatible plans""" print(f"** Usage summary for user '{username}' ***") for tkey, tval in astat.items(): print(f"{tkey:16} {tval}") print("") print(f"** Plan compatibility for user '{username}' ***") for tplan, plandata in PLANS.items(): print(f"{tplan:30} {yesno(check_plan(astat, plandata))}") def main(): """Entry point""" setup_logging() args = parse_cli() domains = get_account_summary(args.user) dbcount = get_db_count(args.user) mail_accounts = get_mail_accounts(args.user) diskuse = get_disk_usage(args.user) if ( domains is None or dbcount is None or mail_accounts is None or diskuse is None ): logger.error( "Failed to gather data for user. Ensure username is " "correct or check account manually." ) sys.exit(2) astat = get_account_stats(domains, dbcount, mail_accounts, diskuse) if args.plan: check_one_plan(astat, args.plan) else: check_all_plans(args.user, astat) if mail_accounts is None: logger.error("Failed to get list of email accounts for user") else: if args.showmail: print_accounts(mail_accounts) PLANS = get_plan_params() if __name__ == '__main__': main()