#!/usr/bin/python3
'''
Copyright(c)eenos.com
Eenos auto-update tool
'''
import os
import sys
try:
    import datetime
    import yaml
    import logging
    import pwd
    from pprint import pprint
    from logging.handlers import RotatingFileHandler
    import subprocess
    import tempfile
    import configparser
except Exception as e:
    print(str(e))
    print("Error loading python module in system python")
    print("Auto Update Failed")
    sys.exit(0)

# Just some shell colors
class scolor:
	pink = '\033[95m'
	blue = '\033[94m'
	green = '\033[92m'
	warning = '\033[93m'
	fail = '\033[91m'
	end = '\033[0m'
	bold = '\033[1m'
	underline = '\033[4m'


formatter = logging.Formatter('[%(asctime)s +%(msecs)04d] - %(name)s - %(message)s',datefmt='%Y-%m-%d %H:%M:%S')
cformatter = logging.Formatter('[%(asctime)s +%(msecs)04d] - %(message)s',datefmt='%Y-%m-%d %H:%M:%S')
uformatter = logging.Formatter('[%(asctime)s +%(msecs)04d] - %(message)s',datefmt='%Y-%m-%d %H:%M:%S')

def get_console_handler():
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(cformatter)
    return console_handler

def get_file_handler(logfile):
    uid=pwd.getpwnam("eenos").pw_uid
    logpath='/var/log/eenos/'+logfile 
    file_handler=RotatingFileHandler(logpath,maxBytes=0,backupCount=10)  
    file_handler.doRollover()
    file_handler.setFormatter(formatter)    
    if os.path.isfile(logpath):
        os.chown(logpath,uid,uid)
    return file_handler

def get_logger(logfile):
    if not os.path.exists('/var/log/eenos'):
        os.makedirs('/var/log/eenos')
        os.chmod('/var/log/eenos',0o755)
    logger= logging.getLogger()
    logger.setLevel(logging.INFO)
    if sys.stdin.isatty():
        logger.addHandler(get_console_handler())
    logger.addHandler(get_file_handler(logfile))
    logger.propagate = False
    return logger

def readyaml(datafile,log): 
    if os.path.isfile(datafile):  
        with open(datafile, 'r') as stream:
            try:
                data_loaded = yaml.load(stream, Loader=yaml.FullLoader)
            except ValueError as e:
                log(e)
                data_loaded={}
    else:
        log("Datafile not found : "+datafile)
        data_loaded={}  
    return data_loaded

def readplain(dfile):
    if os.path.isfile(dfile):
        data=list()
        try:
            pre=[]
            with open(dfile,'r') as f:
                lines=f.readlines()
            for l in lines:
                d=l.strip()
                pre.append(d)
            data=list(dict.fromkeys(pre))
        except Exception as e :            
            data=[]
    else:
        data=list()    
    return data 


# The backup configuration file
backup_conf = '/var/eenos/preferences/autoupdate.yaml'

# Just for fun
def banner():
    now = datetime.datetime.now()
    sys.stdout.write("Copyright (C) 2023-"+str(now.year)+", Eenos.com \n")
    sys.stdout.write("Eenos High Performance Hosting Control Panel\n")
    sys.stdout.write("Eenos auto update tool\n")
    sys.stdout.write(scolor.green)
    sys.stdout.write('''
 _  _       _   __ 
|_ |_ |\ | / \ (_  
|_ |_ | \| \_/ __) 
''')
    sys.stdout.write(scolor.end+"\n")
    
def  have_update(log):
    conf=backup_conf
    result=False
    if os.path.isfile(conf) and os.stat(conf).st_size > 0:
        settings=readyaml(conf,log)
        if bool(settings) and 'status' in settings:
            if settings['status'] == 'on':
                result=True
            else:
                result=False
        else:
            result=False
    else:
        result=False
    return result    

class commands:
    def runandreturn(cmd):
        try:
            op = subprocess.Popen([cmd],stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True,text=True)
            res=op.communicate()    
            res = list(filter(None, res))
            final=list()   
            for i in res:
                x=i.split('\n')
                for q in x:
                    final.append(q) 
            final = list(filter(None, final))
        except :
            final=list()
        return final

    def live(cmd,log):
        process = subprocess.Popen([cmd],stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True,text=True)
        while True:
            op=process.stdout.readline().strip()        
            if process.poll() is not None:
                break
            if op:
                log(op)        
        rc = process.poll()
        return rc

def get_os_depre(osfile):
    data=readplain(osfile)
    fdata={}
    if bool(data):
        for d in data:
            x=d.split('=')
            fdata[x[0]]=x[1]
    return fdata

def read_confgdata(configfile):
    data=dict()    
    if os.path.isfile(configfile):
        try:
            parser = configparser.ConfigParser()
            with open(configfile,'r') as stream:
                parser.read_string("[top]\n" + stream.read()) 
            data=dict(parser['top'])
        except Exception as e:
            log(str(e))
            data=dict()
            pass
    return data


def get_os():
    data=dict()
    os_versions='/etc/os-release'
    configs=read_confgdata(os_versions)        
    if  bool(configs):
        try:
            soanmes=configs[str('PRETTY_NAME').lower()]
            if str(soanmes).startswith('"'):
                soanmes=soanmes[1:]
            if str(soanmes).endswith('"'):
                soanmes=soanmes[:-1]            
            if soanmes.startswith(('Ubuntu')):
                data["os"]='Ubuntu'
                ubuname=configs[str('UBUNTU_CODENAME').lower()]
                if str(ubuname).startswith('"'):
                    ubuname=ubuname[1:]
                if str(ubuname).endswith('"'):
                    ubuname=ubuname[:-1]
                data["name"]=ubuname
                data["pretty_name"]=soanmes
            elif soanmes.startswith(('Red','Rocky','AlmaLinux','CloudLinux')):
                data["os"]='RHEL'
                rhname=configs[str('ID').lower()]
                if str(rhname).startswith('"'):
                    rhname=rhname[1:]
                if str(rhname).endswith('"'):
                    rhname=rhname[:-1]
                data["name"]=rhname
                data["pretty_name"]=soanmes
        except Exception as e:
            print(str(e))
            data=dict()
            pass    
    return data

def  ubuntu_os_package_update(log):
    cmd="/usr/bin/apt-get -y update"
    log(cmd)
    os.system(cmd)
    # commands.live(cmd,log)
    log("---- The following OS components are available for update -------- ")
    cmd2='/usr/bin/apt list --upgradable'
    log(cmd2)
    os.system(cmd2)
    # commands.live(cmd2,log)
    log("------end of package list -------")
    log("Running upgrade ... ")
    cmd3="/usr/bin/apt-get -y upgrade"
    os.system(cmd3)
    #commands.live(cmd3,log)
    if os.path.isfile('/etc/apt/apt.conf.d/99needrestart'):
        try:
            os.unlink('/etc/apt/apt.conf.d/99needrestart')
        except Exception as e:
            log(str(e))
            pass

def  rhel_os_package_update(log):
    log("Updating Operating System components")
    cmd="/usr/bin/dnf -y update"
    log(cmd)
    os.system(cmd)
    #commands.live(cmd,log)
   


def license_update(log):
    log("Updating license for Eenos")    
    lcmd='/usr/local/eenos/bin/eenoslicense -u'
    commands.live(lcmd,log)
    lcmd='/usr/local/eenos/bin/eenoslicense -c'
    commands.live(lcmd,log)


def secure_web(log):
    if os.path.isfile('/usr/local/eenos/bin/secure-eenos-web.sh'):
        log("Securing Eenos web forcefully ...")
        cmd='/usr/local/eenos/bin/secure-eenos-web.sh'
        # Remove comment after testing
        os.system(cmd)
        # commands.live(cmd,log)

def ubuntu_eenos_web_update(this_tire,target_tire,available_tires,log):
    cpkg='eenos-web-'+this_tire
    tpkg='eenos-web-'+target_tire
    if target_tire in available_tires:
        if cpkg == tpkg:
            cmd='/usr/bin/apt-get -y install '+cpkg
            log("Updating eenos web package  from [%s] to [%s] - [%s]" %(this_tire,target_tire,cpkg))
            log(cmd)
            # Remove comment after testing
            os.system(cmd)
            # commands.live(cmd,log)
        else:
            log("Updating Eenos web package  from [%s] to [%s] - [%s]" %(this_tire,target_tire,tpkg))
            rcmd='/usr/bin/apt-get -y --purge remove '+cpkg
            log(rcmd)
            # Remove comment after testing
            os.system(rcmd)
            # commands.live(rcmd,log)
            icmd='/usr/bin/apt-get -y install '+tpkg
            log(icmd)
            # Remove comment after testing
            os.system(icmd)
            # commands.live(icmd,log)
        secure_web(log)
    else:
        log("The selected target is not available")

def rhel_eenos_web_update(this_tire,target_tire,available_tires,log):
    cpkg='eenos-web-'+this_tire
    tpkg='eenos-web-'+target_tire
    if target_tire in available_tires:
        if cpkg == tpkg:
            cmd='/usr/bin/dnf -y install '+cpkg
            log("Updating Eenos web package  from [%s] to [%s] - [%s]" %(this_tire,target_tire,cpkg))
            log(cmd)
            # Remove comment after testing
            os.system(cmd)
            # commands.live(cmd,log)
        else:
            log("Updating Eenos web package  from [%s] to [%s] - [%s]" %(this_tire,target_tire,tpkg))
            rcmd='/usr/bin/dnf -y remove '+cpkg
            log(rcmd)
            # Remove comment after testing
            os.system(rcmd)
            # commands.live(rcmd,log)
            icmd='/usr/bin/dnf -y install '+tpkg
            log(icmd)
            # Remove comment after testing
            os.system(icmd)
            # commands.live(icmd,log)
        secure_web(log)
    else:
        log("The selected target is not available")


def eenos_pip_update(this_tire,target_tire,available_tires,log):
    ctmp=str(tempfile.gettempdir())
    tmdir='/root/tmp'
    if not os.path.isdir('/root/tmp'):
        try:
            os.makedirs('/root/tmp')
        except Exception as e:
            log(str(e))
            pass
    os.environ['TMPDIR']=tmdir
    cmd1='/usr/local/eenos/bin/pip3 --no-color --no-cache-dir install --upgrade pip setuptools==67.2.0 wheel'
    log("Updating pip module")
    log(cmd1)    
    # Remove comment after testing
    os.system(cmd1)
    #commands.live(cmd1,log)
    if target_tire in available_tires:
        cmd3='/usr/local/eenos/bin/pip3 --no-color --no-cache-dir install --upgrade --extra-index-url https://pip.eenos.com/'+target_tire+'/ eenos'
    else:
        cmd3='/usr/local/eenos/bin/pip3 --no-color --no-cache-dir install --upgrade --extra-index-url https://pip.eenos.com/release/ eenos'
    log("Installing Eenos core pips  from [%s] to [%s].." %(this_tire,target_tire))
    log(cmd3)
    # Remove comment after testing
    os.system(cmd3)
    # commands.live(cmd3,log)
    os.environ['TMPDIR']=ctmp 
    

def ubuntu_eenoscore_update(log):
    log("Updating Eenos core packgaes")
    cmd="/usr/bin/apt-get -y install eenoscore"
    # Remove comment after testing
    os.system(cmd)
    # commands.live(cmd,log)
  
def rhel_eenoscore_update(log):
    log("Updating Eenos core packgaes")
    cmd="/usr/bin/dnf -y install eenoscore"
    # Remove comment after testing
    os.system(cmd)
    # commands.live(cmd,log)
   
def post_update(from_version,to_version):
    if os.path.isfile('/usr/local/eenos/bin/eenos_post_update'):
        log("Performing Post Updates")
        cmd='/usr/local/eenos/bin/eenos_post_update --from='+from_version+' --to='+to_version
        os.system(cmd)

def report(old_version,old_tire,log):
    current=commands.runandreturn('/usr/local/eenos/scripts/eenosversion')        
    new_version=current[0].split(':')[1]
    new_tire=current[1].split(':')[1]
    post_update(old_version,new_version)
    log("Eenos update completed from [ %s (%s) ] to [ %s (%s) ]" %(old_version,old_tire,new_version,new_tire))
    log("Done.")

def ubuntu_update(this_tire,target_tire,available_tires,settings,thisversion,log):
    if 'osupdate' in settings and settings['osupdate'] == 'on':
        log("Starting Operating Systems Package Update ..")        
        ubuntu_os_package_update(log)
    eenos_pip_update(this_tire,target_tire,available_tires,log)
    ubuntu_eenos_web_update(this_tire,target_tire,available_tires,log)
    ubuntu_eenoscore_update(log)
    license_update(log)
    report(thisversion,this_tire,log)

def rhel_update(this_tire,target_tire,available_tires,settings,thisversion,log):
    if 'osupdate' in settings and settings['osupdate'] == 'on':
        log("Starting Operating Systems Package Update ..") 
        rhel_os_package_update(log)
    eenos_pip_update(this_tire,target_tire,available_tires,log)
    rhel_eenos_web_update(this_tire,target_tire,available_tires,log)
    rhel_eenoscore_update(log)
    license_update(log)
    report(thisversion,this_tire,log)


def start(log):
    banner()
    log("Starting Eenos auto update .. ")
    if have_update(log):
        settings=readyaml(backup_conf,log)
        current=commands.runandreturn('/usr/local/eenos/scripts/eenosversion')        
        thisversion=current[0].split(':')[1]
        this_tire=current[1].split(':')[1]
        #Only can update to the following tires
        available_tires=str(current[2].split(':')[1]).split(',')    
        target_tire= settings['tire']
        log("Detecting eenos installed version : %s" %thisversion)
        log("Current installed TIRE : %s" %this_tire)
        log("Target update TIRE : %s" %target_tire)  
        if target_tire in available_tires:
            if os.path.isfile('/etc/os-release'):
                osdata=get_os()
            else:
                osdata=dict()
            if bool(osdata) and 'os' in osdata:
                if osdata['os'] == 'Ubuntu':
                    log("Updating for Ubuntu")
                    os.environ["DEBIAN_FRONTEND"]="noninteractive"
                    ubuntu_update(this_tire,target_tire,available_tires,settings,thisversion,log)
                elif osdata['os'] == 'RHEL':
                    log("Eenos Updating for : %s" %osdata['pretty_name'])
                    rhel_update(this_tire,target_tire,available_tires,settings,thisversion,log)
                else:                    
                    log("Unsupported operating system")
            else:
                log("Unsupported operating system")
        else:
            log("There is no update available to the selected tire yet. Update aborted !!")        
       
    else:
        log("Eenos auto update not enabled")


if __name__ == "__main__":
    if  os.geteuid()==0:
        logfile='eenos-update.log'   
        logger=get_logger(logfile)   
        log=logger.info 
        if os.path.isfile('/etc/eenos_devserver'):
            print("Eenos dev server. Ensure backup and remove the lock '/etc/eenos_devserver' to run update")
        else:            
            start(log)
        logger.handlers.clear()
        sys.exit(0)
    else:
        print("You don't have privilege to run this tool")
        sys.exit(2)
