PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/thread-self/root/proc/self/root/proc/self/root/opt/sharedrads/oldrads/ |
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 : //proc/thread-self/root/proc/self/root/proc/self/root/opt/sharedrads/oldrads/remote_dump_old |
#!/opt/imh-python/bin/python3 import arrow import shutil import csv import os import argparse import base64 import requests from rads.common import colors col = colors() import ftplib import socket import picker from rads.common import strcolor import string import random from rads.cpanel_api import cpanel_api from rads.common import is_cpanel_user import sys import time from glob import glob from prettytable import PrettyTable from rads.common import yes_or_no def welcome(parsed, maninput): print( "We will attempt to transfer all cPanel users from the reseller {} at the host {}".format( parsed.User, parsed.RemoteHost ) ) if maninput: print('If you need to run this again you can use the shortened version') print( "./remote_dump --user {} --pass '{}' --host {} --ftphost {} --ftpuser {} --ftppass '{}'".format( parsed.User, parsed.ResPass, parsed.RemoteHost, parsed.FTPhost, parsed.FTPuser, parsed.FTPpass, ) ) if not parsed.use_ssl: print( strcolor( 'red', 'If you have troubles, you might want to try the --ssl flag', ) ) def update_csv(user, status, csvfile): fo_append = ''.join( random.choice( string.ascii_uppercase + string.digits + string.ascii_lowercase ) for _ in range(6) ) f = open(csvfile, 'rb') fo_file = f'/tmp/status-{fo_append}.csv' fo = open(fo_file, 'wb') # go through each line of the file for line in f: bits = line.split(',') # change second column if bits[0] == user: bits[1] = status # join it back together and write it out fo.write(','.join(bits)) f.close() fo.close() shutil.copyfile(fo_file, csvfile) def success_from_path(path): pending = 0 transfer = 0 backup_file = {} backups = glob(f'{path}/backup-*.tar.gz') if os.path.isfile(f'{path}/status.csv'): with open(f'{path}/status.csv', 'rb') as csvfile: csvinfo = csv.DictReader(csvfile, delimiter=',') for row in csvinfo: for file in backups: if file.endswith('_{}.tar.gz'.format(row['user'])): row['status'] = 'Transferring' backupfile = file backup_file[row['user']] = file if row['status'] == 'Transferring': with open('/var/log/messages') as logfile: for line in logfile: if ( '{} uploaded'.format(backupfile.split('/')[-1]) in line ): row['status'] = 'Complete' if row['status'] == 'Complete' and not os.path.isfile( '/var/cpanel/users/{}'.format(row['user']) ): print(backup_file[row['user']]) def status_from_path(path, timeout): pending = 0 transfer = 0 backup_file = {} backups = glob(f'{path}/backup-*.tar.gz') if os.path.isfile(f'{path}/status.csv'): print('Found Status File') with open(f'{path}/status.csv', 'rb') as csvfile: csvinfo = csv.DictReader(csvfile, delimiter=',') for row in csvinfo: for file in backups: if file.endswith('_{}.tar.gz'.format(row['user'])): if row['status'] != 'Complete': row['status'] = 'Transferring' backupfile = file backup_file[row['user']] = file if row['status'] == 'Transferring': with open('/var/log/messages') as logfile: for line in logfile: if ( '{} uploaded'.format(backupfile.split('/')[-1]) in line ): row['status'] = 'Complete' update_csv( row['user'], row['status'], f'{path}/status.csv', ) if os.path.isfile( '{}/{}.started'.format(path, row['user']) ): os.remove( '{}/{}.started'.format( path, row['user'] ) ) if row['status'] != 'Waiting' and row['status'] != 'Pending': update_csv(row['user'], row['status'], f'{path}/status.csv') if row['status'] == 'Pending': if os.path.isfile( '{}/{}.started'.format(path, row['user']) ): print( '{} Start time was {} seconds ago'.format( row['user'], time.time() - os.path.getctime( '{}/{}.started'.format(path, row['user']) ), ) ) if ( time.time() - os.path.getctime( '{}/{}.started'.format(path, row['user']) ) > timeout ): print('{} Timed Out'.format(row['user'])) update_csv( row['user'], 'Timed Out', f'{path}/status.csv' ) row['status'] == 'Timed Out' if row['status'] == 'Pending': pending += 1 if row['status'] == 'Transferring': transfer += 1 x = PrettyTable( ["User", "Start Time", "Status", "Backup File", "Conflict Info"] ) with open(f'{path}/status.csv') as csvfile: csvinfo = csv.DictReader(csvfile) for row in csvinfo: backupfile = None conflict = None if row['user'] in backup_file: backupfile = backup_file[row['user']] if os.path.isfile('/var/cpanel/users/{}'.format(row['user'])): with open( '/var/cpanel/users/{}'.format(row['user']) ) as userfile: for line in userfile: if line.startswith('OWNER='): owner = line.split('=')[1].strip() conflict = f'Account exists. Owner: {owner}' row['status'] = '{}_with_Conflict'.format(row['status']) x.add_row( [ row['user'], arrow.get(row['starttime']).humanize(), row['status'], backupfile, conflict, ] ) print(x) print(f'There are currently {pending} pending transfers') print(f'There are currently {transfer} active transfers') return pending, transfer def input_parse(): parser = argparse.ArgumentParser() parser.add_argument( '-e', '--email', action='store', dest='Email', help='Set email to send notification to', ) parser.add_argument( '-u', '--user', action='store', dest='User', help='Define your User' ) parser.add_argument( '-p', '--pass', action='store', dest='ResPass', default=None, help='Set the password for the remote reseller. Not recommended to pass as an argument, but an option nonetheless.', ) parser.add_argument( '-r', '--host', action='store', dest='RemoteHost', default=None, help='Remote Hostname.', ) # not -h because it conflicts with help parser.add_argument( '--ftphost', action='store', dest='FTPhost', default=False, help='Set hostname to use for FTP transfer', ) parser.add_argument( '--ftpuser', action='store', dest='FTPuser', default=False, help='Set username to use for FTP transfer', ) parser.add_argument( '--ftppass', action='store', dest='FTPpass', default=False, help='Set password for FTP transfer', ) parser.add_argument( '-s', '--ssl', action='store_true', default=False, dest='use_ssl', help='Will make it run over port 2087 instead of 2086. Can cause SSL errors', ) parser.add_argument( '-n', '--nosize', action='store_true', default=False, dest='nosize', help='Will make it run over port 2087 instead of 2086. Can cause SSL errors', ) parser.add_argument( '--skip', action='store', default=False, nargs='+', dest='skip', help='Skip specific accounts. Useful with --all', ) parser.add_argument( '--accounts', action='store', default=False, nargs='+', dest='accounts', help='Set an inclusive list of accounts to move. Skips the menu', ) parser.add_argument( '--concurrent', action='store', default=2, dest='concurrent', type=int, help='Set how many backups will run at once when you use the --all option', ) parser.add_argument( '--timeout', '-t', action='store', default=1800, dest='timeout', type=int, help='Set a timeout in seconds to set item from Pending to Timed Out and set an available concurrency slot. Default: 1800 sec (30 min)', ) parser.add_argument( '--all', action='store_true', default=False, dest='all', help='Dump all backups for accounts within size specification', ) parser.add_argument( '-c', '--createftp', action='store', default=False, dest='createftp', help='Create an FTP account automatically to do the transfer. Required for --status', ) parser.add_argument( '--status', action='store', default=False, dest='status', help='Provide a user or a path to check the status for', ) parser.add_argument( '--success', action='store', default=False, dest='success', help='Provide a path and it will show you all successful complete backups in that Directory', ) parser.add_argument('--version', action='version', version='%(prog)s 1.0') raw = parser.parse_known_args() parsed = raw[0] if parsed.status: status_from_path(parsed.status, parsed.timeout) sys.exit() if parsed.success: success_from_path(parsed.success) sys.exit() if not parsed.createftp: print( 'It is strongly suggested you allow remote_dump to create the ftp user for you so it can track progress.' ) create_ftp = yes_or_no('Would you like to create an ftp user?') if create_ftp: parsed.createftp = input( "What cPanel account should the FTP user be created on: " ) if parsed.createftp: ( parsed.FTPhost, parsed.FTPuser, parsed.FTPpass, parsed.ftproot, ) = create_ftp_user(parsed.createftp) try: maninput = False if not parsed.User: parsed.User = input( "Enter the reseller user from the remote host: " ) maninput = True if not parsed.ResPass: parsed.ResPass = input( "Enter the reseller password for the remote host: " ) maninput = True if not parsed.RemoteHost: parsed.RemoteHost = input("Enter remote hostname or IP address: ") maninput = True if not parsed.FTPhost: parsed.FTPhost = input( "Enter hostname for FTP transfer (new server): " ) maninput = True if not parsed.FTPuser: parsed.FTPuser = input("Enter FTP user for new server: ") maninput = True if not parsed.FTPpass: parsed.FTPpass = input( 'Enter Password for ' + parsed.FTPuser + ': ' ) maninput = True welcome(parsed, maninput) except KeyboardInterrupt as exc: print('\nKeyboard Quit Detected') remote_ftp_user(parsed, destroy=1) sys.exit() return parsed def remote_ftp_user(parsed, destroy=0): print('Removing FTP Account') result = cpanel_api( function='delftp', module='Ftp', version=2, destroy=destroy, user=parsed.FTPuser, cpanel_jsonapi_user=parsed.createftp, quota=0, ) try: if result['cpanelresult']['event']['result'] == 1: print(f'Successfully removed FTP account {parsed.FTPuser}') else: print(f'Unable to remove FTP account {parsed.FTPuser}') except KeyError as exc: sys.exit(f'Unable To remove FTP account {parsed.FTPuser}') def create_ftp_user(user): if is_cpanel_user(user): with open(f'/var/cpanel/users/{user}') as f: for line in f: if line.startswith('DNS='): primarydomain = line.split('=')[1].strip() break else: sys.exit('Not a valid user') ftp_modifier = ''.join( random.choice( string.ascii_uppercase + string.digits + string.ascii_lowercase ) for _ in range(6) ) ftp_acct = f'imhxfer-{ftp_modifier}' ftp_pass = ''.join( random.choice( string.ascii_uppercase + string.digits + string.ascii_lowercase ) for _ in range(10) ) hostname = socket.gethostname() IP = socket.gethostbyname(hostname) result = cpanel_api( function='addftp', module='Ftp', version=2, user=ftp_acct, cpanel_jsonapi_user=user, quota=0, _data={'pass': ftp_pass}, ) if result['cpanelresult']['event']['result'] == 1: print( f'{IP} Created account {ftp_acct}@{primarydomain} with the password {ftp_pass}' ) else: sys.exit( f'Unable to create FTP account. Output from cpanel API - {result}' ) if os.path.isdir(f'/home/{user}/{ftp_acct}@{primarydomain}'): ftp_doc_root = f'/home/{user}/{ftp_acct}@{primarydomain}' elif os.path.isdir(f'/home/{user}/{ftp_acct}'): ftp_doc_root = f'/home/{user}/{ftp_acct}' else: sys.exit('Unable to find Document Root of FTP account') print(f'FTP Document root is {ftp_doc_root}') return IP, f'{ftp_acct}@{primarydomain}', ftp_pass, ftp_doc_root def remote_whm_post(parsed, jsoncall, **kwargs): user = kwargs.get('user', parsed.User) port = kwargs.get('port', '2086') remoteHost = parsed.RemoteHost if parsed.use_ssl: port = 2087 url = 'https://' + remoteHost + ':' + port + '/' + jsoncall else: url = 'http://' + remoteHost + ':' + port + '/' + jsoncall passholder = parsed.ResPass encoded_pass = base64.b64encode(user + ':' + passholder) auth = "Basic " + encoded_pass header = {'Authorization': auth} response = requests.post(url, headers=header, verify=False).json() return response def remote_cpanel_api( host, user, cppass, function, version, module, ssl=False, pos=None, timeout=180, _data=None, **kwargs, ): """Make a POST request to the cPanel JSON API. Args: function (str): function name posted as cpanel_jsonapi_func version (int): version of the API to use (1 or 2) module (str): module name posted as cpanel_jsonapi_module pos (list): list of positional args sent into the function as arg-0, arg-1, et al. timeout (int, default 180): seconds to give the API to respond All other data which must be supplied to the API can be supplied as additional kwargs. Special arg: _data (dict): This shouldn't be necessary to use. If an argument is impossible to send to the API as a kwarg for whatever reason, it can be forced in using this. For example, in python a kwarg cannot have a period in its name or begin with a number. If you needed to supply stupid.arg as an argument, you can do _data={'stupid.arg': value_here}""" data = {} if _data is None else _data # required kwargs. All others are supplied inside data{} data['cpanel_jsonapi_module'] = module data['cpanel_jsonapi_func'] = function data['cpanel_jsonapi_apiversion'] = version for key, val in kwargs.items(): data[key] = val # positional args are sent in as post variables, in format # &arg-0=SOMETHING&arg-1=SOMETHING # the pos (pun intended) variable holds them in the order # they are sent into the function if isinstance(pos, str): # even if there is only one arg, it should be a list raise ValueError('pos should be a list, received string') if isinstance(pos, list): for pos_index, pos_arg in enumerate(pos): data['arg-%d' % pos_index] = pos_arg try: print('%(yellow)sConnecting to remote cPanel API%(none)s' % col) except OSError: return None try: passholder = cppass encoded_pass = base64.b64encode(user + ':' + passholder) auth = "Basic " + encoded_pass if ssl == True: url = 'https://' + host + ':2087/json-api/cpanel' else: url = 'http://' + host + ':2086/json-api/cpanel' return requests.post( url, data=data, headers={'Authorization': auth}, timeout=timeout, verify=False, ).json() except ( requests.exceptions.RequestException, ValueError, # JSON issues TypeError, # JSON issues ): return None def biguser(response, parsed): userlist = [] toobig = [] if 'cpanelresult' in response: if 'error' in response['cpanelresult']: print('%(red)sPossible Bad Password%(none)s' % col) print('cPanel API Response:') print(response['cpanelresult']['error']) for i in range(len(response['data']['acct'])): size = response['data']['acct'][i]['diskused'] if size == 'none': userlist.append( { 'user': response['data']['acct'][i]['user'], 'disk': response['data']['acct'][i]['diskused'].split('M')[ 0 ], 'quota': response['data']['acct'][i]['disklimit'], } ) if size != None and size != 'none': size = int(size.split('M')[0]) if not parsed.nosize: if size <= 6000: userlist.append( { 'user': response['data']['acct'][i]['user'], 'disk': response['data']['acct'][i][ 'diskused' ].split('M')[0], 'quota': response['data']['acct'][i]['disklimit'], } ) else: print('%(red)sAccount is over 6GB:%(none)s' % col) print(response['data']['acct'][i]['user']) toobig.append( { 'user': response['data']['acct'][i]['user'], 'disk': response['data']['acct'][i][ 'diskused' ].split('M')[0], 'quota': response['data']['acct'][i]['disklimit'], } ) else: userlist.append( { 'user': response['data']['acct'][i]['user'], 'disk': response['data']['acct'][i]['diskused'].split( 'M' )[0], 'quota': response['data']['acct'][i]['disklimit'], } ) if size == None: userlist.append( { 'user': response['data']['acct'][i]['user'], 'disk': response['data']['acct'][i]['diskused'].split('M')[ 0 ], 'quota': response['data']['acct'][i]['disklimit'], } ) return (userlist, toobig) def gen_backup_select(userdict, parsed): select_user_list = [] for i in range(len(userdict)): select_user_list.append(userdict[i]['user']) if parsed.all: return select_user_list if parsed.accounts: return parsed.accounts else: opts = picker.Picker( title='Select cPanel user to transfer', options=sorted(select_user_list), ).get_selected() if opts == False: print("Aborted!") else: print(opts) return opts def backup_user(parsed, mvusers): users_to_mv = gen_backup_select(mvusers, parsed) if parsed.createftp: if os.path.isdir(parsed.ftproot): statusfile = f'{parsed.ftproot}/status.csv' with open(statusfile, 'w') as status: status.write('user,status,starttime\n') for user in sorted(users_to_mv): status.write(f'{user},Waiting,{time.time()}\n') else: sys.exit(f'Unable to find FTP directory {parsed.ftproot}') dest = 'passiveftp' server = parsed.FTPhost user = parsed.FTPuser ftppass = parsed.FTPpass if not parsed.Email: email = ('docs@inmotionhosting.com',) else: email = parsed.Email port = ('21',) rdir = ('bkup',) for cpuser in sorted(users_to_mv): if parsed.createftp: while True: pending, transferring = status_from_path( f'{parsed.ftproot}', parsed.timeout ) if pending < parsed.concurrent: print('Starting Backup for ' + cpuser) open(f'{parsed.ftproot}/{cpuser}.started', 'a').close() result = remote_cpanel_api( host=parsed.RemoteHost, user=parsed.User, cppass=parsed.ResPass, version=1, ssl=True, module='Fileman', function='fullbackup', cpanel_jsonapi_user=cpuser, pos=[dest, server, user, ftppass, email, port, rdir], ) try: # print result if result['event']['result'] == 1: # print '%(green)sSuccess%(none)s' % col update_csv( cpuser, 'Pending', f'{parsed.ftproot}/status.csv', ) break except KeyError: print(result) break else: print( f'Already {parsed.concurrent} transfers running. Pausing for 15 seconds then checking again' ) time.sleep(15) else: print('Starting Backup for ' + cpuser) result = remote_cpanel_api( host=parsed.RemoteHost, user=parsed.User, cppass=parsed.ResPass, version=1, ssl=True, module='Fileman', function='fullbackup', cpanel_jsonapi_user=cpuser, pos=[dest, server, user, ftppass, email, port, rdir], ) try: # print result if result['event']['result'] == 1: print( strcolor( 'green', f'Successfully started Backup for {cpuser}' ) ) except KeyError: print(result) def test_ftp_creds(parsed): print('Testing FTP Login') server = parsed.FTPhost user = parsed.FTPuser password = parsed.FTPpass try: ftp = ftplib.FTP(server) ftp.login(user, password) except Exception as e: print(e) print( "Unable to connect via FTP. The transfer will fail. Check your FTP credentials and submit again" ) quit() print('%(green)sFTP Connection Established%(none)s' % col) def validate_cpanel_connection(parsed): s = socket.socket() address = parsed.RemoteHost port = 2086 # port number is a number, not string try: s.connect((address, port)) except Exception as e: print( "Unable to connect to {} on port {}. Check to see if valid connection".format( address, port ) ) quit() def print_notice(parsed): if not parsed.Email: print( '%(yellow)sEmail response from remote API will go to docs@inmotionhosting.com instead of mht@inmotionhosting.com%(none)s' % col ) else: print( strcolor( 'yellow', f'Email response from remote API will go to {parsed.Email}', ) ) def main(): parsed = input_parse() print_notice(parsed) validate_cpanel_connection(parsed) test_ftp_creds(parsed) print('Determining the accounts to move by size') userlists = biguser( remote_whm_post( parsed, 'json-api/listaccts?api.version=1&want=user,disklimit,diskused', ), parsed, ) mvusers = userlists[0] if parsed.skip: mvuser_copy = list(mvusers) for entry in mvusers: if str(entry['user']) in parsed.skip: mvuser_copy.remove(entry) mvusers = list(mvuser_copy) bigusers = userlists[1] print( '%(green)sThe following users are the correct size to be backed up from the remote host%(none)s' % col ) x = PrettyTable(['User', 'Disk Space in MB']) for entry in mvusers: if parsed.accounts: if entry['user'] in parsed.accounts: x.add_row([entry['user'], entry['disk']]) else: x.add_row([entry['user'], entry['disk']]) print(x) input("Press Enter to continue...") backup_user(parsed, mvusers) if parsed.createftp: pending, transferring = status_from_path( f'{parsed.ftproot}/', parsed.timeout ) while True: if pending > 0 or transferring > 0: print(pending, transferring) pending, transferring = status_from_path( f'{parsed.ftproot}', parsed.timeout ) time.sleep(10) else: print(f'Pending: {pending} \nTransferring {transferring}') break if __name__ == "__main__": main()