mirror of
				https://github.com/usmannasir/cyberpanel.git
				synced 2025-10-31 10:26:01 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1391 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1391 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import argparse
 | |
| import json
 | |
| import os
 | |
| import sys
 | |
| import time
 | |
| 
 | |
| import paramiko
 | |
| import requests
 | |
| import json
 | |
| import configparser
 | |
| from django.http import HttpResponse
 | |
| 
 | |
| sys.path.append('/usr/local/CyberCP')
 | |
| import django
 | |
| import plogical.CyberCPLogFileWriter as logging
 | |
| 
 | |
| import plogical.mysqlUtilities as mysqlUtilities
 | |
| 
 | |
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
 | |
| try:
 | |
|     django.setup()
 | |
| except:
 | |
|     pass
 | |
| 
 | |
| from plogical.processUtilities import ProcessUtilities
 | |
| from plogical.processUtilities import ProcessUtilities as pu
 | |
| 
 | |
| import threading as multi
 | |
| 
 | |
| 
 | |
| class CPBackupsV2(multi.Thread):
 | |
|     PENDING_START = 0
 | |
|     RUNNING = 1
 | |
|     COMPLETED = 2
 | |
|     FAILED = 3
 | |
| 
 | |
|     ### RCLONE BACKEND TYPES
 | |
|     SFTP = 1
 | |
|     LOCAL = 2
 | |
|     GDrive = 3
 | |
| 
 | |
|     RUSTIC_PATH = '/usr/bin/rustic'
 | |
|     RCLONE_CONFIG = '/root/.config/rclone/rclone.conf'
 | |
|     command = 'rclone obscure hosting'
 | |
| 
 | |
|     def __init__(self, data):
 | |
|         multi.Thread.__init__(self)
 | |
|         self.data = data
 | |
|         try:
 | |
|             self.function = data['function']
 | |
|         except:
 | |
|             pass
 | |
| 
 | |
|         statusRes, message = self.InstallRustic()
 | |
| 
 | |
|         ### set self.website as it is needed in many functions
 | |
|         from websiteFunctions.models import Websites
 | |
|         self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|         # Resresh gdive access_token code
 | |
|         try:
 | |
|             self.LocalRclonePath = f'/home/{self.website.domain}/.config/rclone'
 | |
|             self.ConfigFilePath = f'{self.LocalRclonePath}/rclone.conf'
 | |
| 
 | |
|             reponame =  self.data['BackendName']
 | |
| 
 | |
|             try:
 | |
|                 ### refresh token if gdrie
 | |
|                 command = f"rclone config dump"
 | |
|                 token = json.loads(ProcessUtilities.outputExecutioner(command, self.website.externalApp, True).rstrip('\n'))
 | |
| 
 | |
|                 refreshToken = json.loads(token[reponame]['token'])['refresh_token']
 | |
| 
 | |
|                 logging.CyberCPLogFileWriter.writeToFile(f"Refresh Token: {refreshToken}")
 | |
| 
 | |
|                 new_Acess_token = self.refresh_V2Gdive_token(refreshToken)
 | |
| 
 | |
|                 command = f"""rclone config update '{reponame}' token '{{"access_token":"{new_Acess_token}","token_type":"Bearer","refresh_token":"{refreshToken}","expiry":"2024-04-08T21:53:00.123456789Z"}}' --non-interactive"""
 | |
|                 result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|                 if os.path.exists(ProcessUtilities.debugPath):
 | |
|                     logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
| 
 | |
|             except BaseException as msg:
 | |
|                 logging.CyberCPLogFileWriter.writeToFile(f"Token Not upadate inside. Error: {str(msg)}")
 | |
|                 pass
 | |
| 
 | |
|             # command = 'cat %s' % (path)
 | |
|             # CurrentContent = pu.outputExecutioner(command)
 | |
| 
 | |
| 
 | |
|             # if CurrentContent.find(reponame) > -1:
 | |
|             #     config = configparser.ConfigParser()
 | |
|             #     config.read_string(CurrentContent)
 | |
|             #
 | |
|             #     token_str = config.get(reponame, 'token')
 | |
|             #     token_dict = json.loads(token_str)
 | |
|             #     refresh_token = token_dict['refresh_token']
 | |
|             #
 | |
|             #     new_Acess_token = self.refresh_V2Gdive_token(refresh_token)
 | |
|             #
 | |
|             #     old_access_token = token_dict['access_token']
 | |
|             #
 | |
|             #     new_content = CurrentContent.replace(str(old_access_token), new_Acess_token)
 | |
|             #
 | |
|             #     command = f"cat /dev/null > {self.ConfigFilePath}"
 | |
|             #     pu.executioner(command, self.website.externalApp, True)
 | |
|             #
 | |
|             #     command = f"echo '{new_content}' >> {self.ConfigFilePath}"
 | |
|             #     ProcessUtilities.executioner(command, self.website.externalApp, True)
 | |
|             #
 | |
|             #     command = f"chmod 600 {self.ConfigFilePath}"
 | |
|             #     ProcessUtilities.executioner(command, self.website.externalApp)
 | |
|             # else:
 | |
|             #     logging.CyberCPLogFileWriter.writeToFile("Token Not upadate..........")
 | |
|         except BaseException as msg:
 | |
|             logging.CyberCPLogFileWriter.writeToFile("Error update token............%s"%msg)
 | |
| 
 | |
| 
 | |
| 
 | |
|         ## Set up the repo name to be used
 | |
| 
 | |
|         if self.data['BackendName'] != 'local':
 | |
|             self.repo = f"rclone:'{self.data['BackendName']}':{self.data['domain']}"
 | |
|         else:
 | |
|             self.repo = f"rclone:'{self.data['BackendName']}':/home/{self.data['domain']}/incrementalbackups"
 | |
| 
 | |
|         ### This will contain list of all snapshots id generated and it will be merged
 | |
| 
 | |
|         self.snapshots = []
 | |
| 
 | |
|         ##
 | |
| 
 | |
|         self.StatusFile = f'/home/cyberpanel/{self.website.domain}_rustic_backup_log'
 | |
|         self.StatusFile_Restore = f'/home/cyberpanel/{self.website.domain}_rustic_backup_log_Restore'
 | |
| 
 | |
|         ## restore or backup?
 | |
| 
 | |
|         self.restore = 0
 | |
| 
 | |
|         if os.path.exists(self.StatusFile):
 | |
|             os.remove(self.StatusFile)
 | |
| 
 | |
| 
 | |
|         #### i want to keep a merge flag, if not delete all snapshots in case of backup fail
 | |
| 
 | |
|         self.MergeSnapshotFlag = 1
 | |
| 
 | |
|         # ### delete repo function
 | |
|         # try:
 | |
|         #     self.repo = data['BackendName']
 | |
|         # except:
 | |
|         #     pass
 | |
| 
 | |
|     def run(self):
 | |
|         try:
 | |
|             if self.function == 'InitiateBackup':
 | |
|                 self.InitiateBackup()
 | |
|             elif self.function == 'InitiateRestore':
 | |
|                 self.InitiateRestore()
 | |
|         except BaseException as msg:
 | |
|             logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [CPBackupsV2.run]')
 | |
| 
 | |
|     def FetchSnapShots(self):
 | |
|         try:
 | |
|             command = f'rustic -r {self.repo} snapshots --password "" --json 2>/dev/null'
 | |
|             # SLSkjoSCczb6wxTMCBPmBMGq/UDSpp28-u cyber5986 rustic -r rclone:None:cyberpanel.net snapshots --password "" --json 2>/dev/null
 | |
|             result = json.loads(
 | |
|                 ProcessUtilities.outputExecutioner(command, self.website.externalApp, True).rstrip('\n'))
 | |
|             return 1, result
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
| 
 | |
|     def SetupRcloneBackend(self, type, config):
 | |
|         try:
 | |
|             self.LocalRclonePath = f'/home/{self.website.domain}/.config/rclone'
 | |
|             self.ConfigFilePath = f'{self.LocalRclonePath}/rclone.conf'
 | |
| 
 | |
|             command = f'mkdir -p {self.LocalRclonePath}'
 | |
|             ProcessUtilities.executioner(command, self.website.externalApp)
 | |
| 
 | |
| 
 | |
|             if type == CPBackupsV2.SFTP:
 | |
|                 ## config = {"name":, "host":, "user":, "port":, "path":, "password":,}
 | |
| 
 | |
| 
 | |
|                 ### first check sftp credentails details
 | |
| 
 | |
|                 # Connect to the remote server using the private key
 | |
|                 ssh = paramiko.SSHClient()
 | |
|                 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 | |
| 
 | |
|                 try:
 | |
|                     # Connect to the server using the private key
 | |
|                     ssh.connect(config["host"], username=config["user"], password=config["password"], port=config["sshPort"])
 | |
|                     ssh.close()
 | |
|                     if os.path.exists(ProcessUtilities.debugPath):
 | |
|                         logging.CyberCPLogFileWriter.writeToFile(f'Successfully connected to {config["host"]} through user {config["user"]}')
 | |
|                 except BaseException as msg:
 | |
|                     return 0, str(msg)
 | |
| 
 | |
| 
 | |
| 
 | |
|                 command = f'rclone obscure {config["password"]}'
 | |
|                 ObsecurePassword = ProcessUtilities.outputExecutioner(command).rstrip('\n')
 | |
| 
 | |
|                 content = f'''
 | |
|                 
 | |
| [{config["Repo_Name"]}]
 | |
| type = sftp
 | |
| host = {config["host"]}
 | |
| user = {config["user"]}
 | |
| pass = {ObsecurePassword}
 | |
| port = {config["sshPort"]}
 | |
| '''
 | |
| 
 | |
|                 command = f"echo '{content}' >> {self.ConfigFilePath}"
 | |
|                 ProcessUtilities.executioner(command, self.website.externalApp, True)
 | |
| 
 | |
|                 command = f"chmod 600 {self.ConfigFilePath}"
 | |
|                 ProcessUtilities.executioner(command, self.website.externalApp)
 | |
|                 return 1, None
 | |
|             elif type == CPBackupsV2.GDrive:
 | |
|                 token = """{"access_token":"%s","token_type":"Bearer","refresh_token":"%s", "expiry":"2024-04-08T21:53:00.123456789Z"}""" % (
 | |
|                 config["token"], config["refresh_token"])
 | |
| 
 | |
|                 if config["client_id"] == 'undefined':
 | |
|                     config["client_id"] = ''
 | |
|                     config["client_secret"] = ''
 | |
| 
 | |
|                 content = f'''
 | |
| [{config["accountname"]}]
 | |
| type = drive
 | |
| scope = drive
 | |
| client_id={config["client_id"]}
 | |
| client_secret={config["client_secret"]}
 | |
| token = {token}
 | |
| team_drive =
 | |
| '''
 | |
|                 command = f"echo '{content}' >> {self.ConfigFilePath}"
 | |
|                 ProcessUtilities.executioner(command, self.website.externalApp, True)
 | |
| 
 | |
|                 command = f"chmod 600 {self.ConfigFilePath}"
 | |
|                 ProcessUtilities.executioner(command, self.website.externalApp)
 | |
|                 return 1, None
 | |
|         except BaseException as msg:
 | |
|             logging.CyberCPLogFileWriter.writeToFile(str(msg) + ' [Configure.run]')
 | |
|             return 0, str(msg)
 | |
| 
 | |
|     @staticmethod
 | |
|     def FetchCurrentTimeStamp():
 | |
|         import time
 | |
|         return str(time.time())
 | |
| 
 | |
|     def UpdateStatus(self, message, status):
 | |
| 
 | |
|         if status == CPBackupsV2.FAILED:
 | |
|             self.website.BackupLock = 0
 | |
|             self.website.save()
 | |
|             ### delete leftover dbs if backup fails
 | |
| 
 | |
|             command = f'rm -f {self.FinalPathRuctic}/*.sql'
 | |
|             ProcessUtilities.executioner(command, None, True)
 | |
| 
 | |
|             file = open(self.StatusFile, 'a')
 | |
|             file.writelines("[" + time.strftime(
 | |
|                 "%m.%d.%Y_%H-%M-%S") + ":FAILED] " + message + "[404]" + "\n")
 | |
|             file.close()
 | |
| 
 | |
|             ### if backup failed, we need to prune snapshots as they are not needed.
 | |
| 
 | |
|             snapshots = ''
 | |
|             for snapshot in self.snapshots:
 | |
|                 snapshots = f'{snapshots} {snapshot}'
 | |
| 
 | |
|             command = f'rustic -r {self.repo} forget {snapshots}  --password ""'
 | |
|             result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|             if os.path.exists(ProcessUtilities.debugPath):
 | |
|                 logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
|         elif status == CPBackupsV2.COMPLETED:
 | |
|             self.website.BackupLock = 0
 | |
|             self.website.save()
 | |
|             file = open(self.StatusFile, 'a')
 | |
|             file.writelines("[" + time.strftime(
 | |
|                 "%m.%d.%Y_%H-%M-%S") + ":COMPLETED] " + message + "[200]" + "\n")
 | |
|             file.close()
 | |
|         else:
 | |
|             file = open(self.StatusFile, 'a')
 | |
|             file.writelines("[" + time.strftime(
 | |
|                 "%m.%d.%Y_%H-%M-%S") + ":INFO] " + message + "\n")
 | |
|             file.close()
 | |
| 
 | |
|     ## parent is used to link this snapshot with master snapshot
 | |
|     def BackupConfig(self):
 | |
|         ### Backup config file to rustic
 | |
| 
 | |
|         command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|         ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'rustic init -r {self.repo} --password ""'
 | |
|         ProcessUtilities.executioner(command, self.website.externalApp)
 | |
| 
 | |
|         # command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}/config.json'
 | |
|         ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'rustic -r {self.repo} backup {self.FinalPathRuctic}/config.json --json --password "" 2>/dev/null'
 | |
|         status, result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True, None, True)
 | |
| 
 | |
|         if os.path.exists(ProcessUtilities.debugPath):
 | |
|             logging.CyberCPLogFileWriter.writeToFile(f'Status {str(status)}')
 | |
| 
 | |
|         if status:
 | |
| 
 | |
|             result = json.loads(result.rstrip('\n'))
 | |
| 
 | |
|             try:
 | |
|                 SnapShotID = result['id']  ## snapshot id that we need to store in db
 | |
|                 files_new = result['summary']['files_new']  ## basically new files in backup
 | |
|                 total_duration = result['summary']['total_duration']  ## time taken
 | |
| 
 | |
|                 self.snapshots.append(SnapShotID)
 | |
| 
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Backup failed as no snapshot id found, error: {str(msg)}', CPBackupsV2.FAILED)
 | |
|                 return 0
 | |
| 
 | |
|             command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}/config.json'
 | |
|             ProcessUtilities.executioner(command)
 | |
| 
 | |
|             return 1
 | |
|         else:
 | |
|             self.UpdateStatus(f'Backup failed , error: {str(result)}', CPBackupsV2.FAILED)
 | |
|             return 0
 | |
| 
 | |
|     def MergeSnapshots(self):
 | |
|         snapshots = ''
 | |
|         for snapshot in self.snapshots:
 | |
|             snapshots = f'{snapshots} {snapshot}'
 | |
| 
 | |
|         if self.MergeSnapshotFlag:
 | |
|             command = f'rustic -r {self.repo} merge {snapshots}  --password "" --json'
 | |
|             result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|             if os.path.exists(ProcessUtilities.debugPath):
 | |
|                 logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
|         command = f'rustic -r {self.repo} forget {snapshots}  --password ""'
 | |
|         result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|         if os.path.exists(ProcessUtilities.debugPath):
 | |
|             logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
|     def InitiateBackup(self):
 | |
|         logging.CyberCPLogFileWriter.writeToFile("[Create Backup start]")
 | |
|         from websiteFunctions.models import Websites, Backupsv2
 | |
|         from django.forms.models import model_to_dict
 | |
|         from plogical.mysqlUtilities import mysqlUtilities
 | |
|         self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|         ## Base path is basically the path set by user where all the backups will be housed
 | |
| 
 | |
|         if not os.path.exists(self.data['BasePath']):
 | |
|             command = f"mkdir -p {self.data['BasePath']}"
 | |
|             ProcessUtilities.executioner(command)
 | |
| 
 | |
|             #command = f"chmod 711 {self.data['BasePath']}"
 | |
|             #ProcessUtilities.executioner(command)
 | |
| 
 | |
|         self.StartingTimeStamp = CPBackupsV2.FetchCurrentTimeStamp()
 | |
| 
 | |
|         ### Init rustic repo in main func so dont need to do again and again
 | |
| 
 | |
|         while (1):
 | |
| 
 | |
|             self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|             if self.website.BackupLock == 0:
 | |
| 
 | |
|                 Disk1 = f"du -sm /home/{self.website.domain}/"
 | |
|                 Disk2 = "2>/dev/null | awk '{print $1}'"
 | |
| 
 | |
|                 self.WebsiteDiskUsage = int(
 | |
|                     ProcessUtilities.outputExecutioner(f"{Disk1} {Disk2}", 'root', True).rstrip('\n'))
 | |
| 
 | |
|                 self.CurrentFreeSpaceOnDisk = int(
 | |
|                     ProcessUtilities.outputExecutioner("df -m / | awk 'NR==2 {print $4}'", 'root', True).rstrip('\n'))
 | |
| 
 | |
|                 if self.WebsiteDiskUsage > self.CurrentFreeSpaceOnDisk:
 | |
|                     self.UpdateStatus(f'Not enough disk space on the server to backup this website.',
 | |
|                                       CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|                 self.UpdateStatus('Checking if backup modules installed,0', CPBackupsV2.RUNNING)
 | |
| 
 | |
|                 ### Before doing anything install rustic
 | |
| 
 | |
|                 statusRes, message = self.InstallRustic()
 | |
| 
 | |
|                 if statusRes == 0:
 | |
|                     self.UpdateStatus(f'Failed to install Rustic, error: {message}', CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|                 # = Backupsv2(website=self.website, fileName='backup-' + self.data['domain'] + "-" + time.strftime("%m.%d.%Y_%H-%M-%S"), status=CPBackupsV2.RUNNING, BasePath=self.data['BasePath'])
 | |
|                 # self.buv2.save()
 | |
| 
 | |
|                 # self.FinalPath = f"{self.data['BasePath']}/{self.buv2.fileName}"
 | |
| 
 | |
|                 ### Rustic backup final path
 | |
| 
 | |
|                 self.FinalPathRuctic = f"{self.data['BasePath']}/{self.website.domain}"
 | |
| 
 | |
|                 # command = f"mkdir -p {self.FinalPath}"
 | |
|                 # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 # command = f"chown {website.externalApp}:{website.externalApp} {self.FinalPath}"
 | |
|                 # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 # command = f'chown cyberpanel:cyberpanel {self.FinalPath}'
 | |
|                 # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 # command = f"chmod 711 {self.FinalPath}"
 | |
|                 # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = f"mkdir -p {self.FinalPathRuctic}"
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = f"chmod 711 {self.FinalPathRuctic}"
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 try:
 | |
| 
 | |
|                     self.UpdateStatus('Creating backup config,0', CPBackupsV2.RUNNING)
 | |
| 
 | |
|                     Config = {'MainWebsite': model_to_dict(self.website,
 | |
|                                                            fields=['domain', 'adminEmail', 'phpSelection', 'state',
 | |
|                                                                    'config'])}
 | |
|                     Config['admin'] = model_to_dict(self.website.admin,
 | |
|                                                     fields=['userName', 'password', 'firstName', 'lastName',
 | |
|                                                             'email', 'type', 'owner', 'token', 'api', 'securityLevel',
 | |
|                                                             'state', 'initself.websitesLimit', 'twoFA', 'secretKey',
 | |
|                                                             'config'])
 | |
|                     Config['acl'] = model_to_dict(self.website.admin.acl)
 | |
| 
 | |
|                     ### Child domains to config
 | |
| 
 | |
|                     ChildsList = []
 | |
| 
 | |
|                     for childDomains in self.website.childdomains_set.all():
 | |
|                         print(childDomains.domain)
 | |
|                         ChildsList.append(model_to_dict(childDomains))
 | |
| 
 | |
|                     Config['ChildDomains'] = ChildsList
 | |
| 
 | |
|                     # print(str(Config))
 | |
| 
 | |
|                     ### Databases
 | |
| 
 | |
|                     connection, cursor = mysqlUtilities.setupConnection()
 | |
| 
 | |
|                     if connection == 0:
 | |
|                         return 0
 | |
| 
 | |
|                     dataBases = self.website.databases_set.all()
 | |
|                     DBSList = []
 | |
| 
 | |
|                     for db in dataBases:
 | |
| 
 | |
|                         query = f"SELECT host,user FROM mysql.db WHERE db='{db.dbName}';"
 | |
|                         cursor.execute(query)
 | |
|                         DBUsers = cursor.fetchall()
 | |
| 
 | |
|                         UserList = []
 | |
| 
 | |
|                         for databaseUser in DBUsers:
 | |
|                             query = f"SELECT password FROM `mysql`.`user` WHERE `Host`='{databaseUser[0]}' AND `User`='{databaseUser[1]}';"
 | |
|                             cursor.execute(query)
 | |
|                             resp = cursor.fetchall()
 | |
|                             print(resp)
 | |
|                             UserList.append({'user': databaseUser[1], 'host': databaseUser[0], 'password': resp[0][0]})
 | |
| 
 | |
|                         DBSList.append({db.dbName: UserList})
 | |
| 
 | |
|                     Config['databases'] = DBSList
 | |
| 
 | |
|                     WPSitesList = []
 | |
| 
 | |
|                     for wpsite in self.website.wpsites_set.all():
 | |
|                         WPSitesList.append(model_to_dict(wpsite, fields=['title', 'path', 'FinalURL', 'AutoUpdates',
 | |
|                                                                          'PluginUpdates', 'ThemeUpdates',
 | |
|                                                                          'WPLockState']))
 | |
| 
 | |
|                     Config['WPSites'] = WPSitesList
 | |
|                     self.config = Config
 | |
| 
 | |
|                     ### DNS Records
 | |
| 
 | |
|                     try:
 | |
|                         from dns.models import Domains
 | |
| 
 | |
|                         self.dnsDomain = Domains.objects.get(name=self.website.domain)
 | |
| 
 | |
|                         DNSRecords = []
 | |
| 
 | |
|                         for record in self.dnsDomain.records_set.all():
 | |
|                             DNSRecords.append(model_to_dict(record))
 | |
| 
 | |
|                         Config['MainDNSDomain'] = model_to_dict(self.dnsDomain)
 | |
|                         Config['DNSRecords'] = DNSRecords
 | |
|                     except:
 | |
|                         pass
 | |
| 
 | |
|                     ### Email accounts
 | |
| 
 | |
|                     try:
 | |
|                         from mailServer.models import Domains
 | |
| 
 | |
|                         self.emailDomain = Domains.objects.get(domain=self.website.domain)
 | |
| 
 | |
|                         EmailAddrList = []
 | |
| 
 | |
|                         for record in self.emailDomain.eusers_set.all():
 | |
|                             EmailAddrList.append(model_to_dict(record))
 | |
| 
 | |
|                         Config['MainEmailDomain'] = model_to_dict(self.emailDomain)
 | |
|                         Config['EmailAddresses'] = EmailAddrList
 | |
|                     except:
 | |
|                         pass
 | |
| 
 | |
|                     # command = f"echo '{json.dumps(Config)}' > {self.FinalPath}/config.json"
 | |
|                     # ProcessUtilities.executioner(command, self.website.externalApp, True)
 | |
| 
 | |
|                     command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}/config.json'
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     WriteToFile = open(f'{self.FinalPathRuctic}/config.json', 'w')
 | |
|                     WriteToFile.write(json.dumps(Config))
 | |
|                     WriteToFile.close()
 | |
| 
 | |
|                     command = f"chmod 600 {self.FinalPathRuctic}/config.json"
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     if self.BackupConfig() == 0:
 | |
|                         return 0
 | |
| 
 | |
|                     self.UpdateStatus('Backup config created,5', CPBackupsV2.RUNNING)
 | |
|                 except BaseException as msg:
 | |
|                     self.UpdateStatus(f'Failed during config generation, Error: {str(msg)}', CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|                 try:
 | |
|                     if self.data['BackupDatabase']:
 | |
|                         self.UpdateStatus('Backing up databases..,10', CPBackupsV2.RUNNING)
 | |
|                         if self.BackupDataBasesRustic() == 0:
 | |
|                             self.UpdateStatus(f'Failed to create backup for databases.', CPBackupsV2.FAILED)
 | |
|                             self.MergeSnapshotFlag = 0
 | |
|                             #return 0
 | |
|                         else:
 | |
|                             self.UpdateStatus('Database backups completed successfully..,25', CPBackupsV2.RUNNING)
 | |
| 
 | |
|                     if self.data['BackupData'] and self.MergeSnapshotFlag:
 | |
|                         self.UpdateStatus('Backing up website data..,30', CPBackupsV2.RUNNING)
 | |
|                         if self.BackupRustic() == 0:
 | |
|                             self.UpdateStatus(f'Failed to create backup for data..', CPBackupsV2.FAILED)
 | |
|                             self.MergeSnapshotFlag = 0
 | |
|                             # return 0
 | |
|                         else:
 | |
|                             self.UpdateStatus('Website data backup completed successfully..,70', CPBackupsV2.RUNNING)
 | |
| 
 | |
|                     if self.data['BackupEmails'] and self.MergeSnapshotFlag:
 | |
|                         self.UpdateStatus('Backing up emails..,75', CPBackupsV2.RUNNING)
 | |
|                         if self.BackupEmailsRustic() == 0:
 | |
|                             self.UpdateStatus(f'Failed to create backup for emails..', CPBackupsV2.FAILED)
 | |
|                             self.MergeSnapshotFlag = 0
 | |
|                         else:
 | |
|                             self.UpdateStatus('Emails backup completed successfully..,85', CPBackupsV2.RUNNING)
 | |
| 
 | |
|                     ### Finally change the backup rustic folder to the website user owner
 | |
| 
 | |
|                     command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     if os.path.exists(ProcessUtilities.debugPath):
 | |
|                         logging.CyberCPLogFileWriter.writeToFile(f'Snapshots to be merged {str(self.snapshots)}')
 | |
| 
 | |
|                     self.MergeSnapshots()
 | |
| 
 | |
|                     self.UpdateStatus('Completed', CPBackupsV2.COMPLETED)
 | |
|                     return 1
 | |
| 
 | |
|                     break
 | |
|                 except BaseException as msg:
 | |
|                     self.UpdateStatus(f'Failed, Error: {str(msg)}', CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
|             else:
 | |
|                 time.sleep(5)
 | |
| 
 | |
|                 ### If website lock is there for more then 20 minutes it means old backup is stucked or backup job failed, thus continue backup
 | |
| 
 | |
|                 if float(CPBackupsV2.FetchCurrentTimeStamp()) > (float(self.StartingTimeStamp) + 1200):
 | |
|                     self.website = Websites.objects.get(domain=self.data['domain'])
 | |
|                     self.website.BackupLock = 0
 | |
|                     self.website.save()
 | |
| 
 | |
|     def BackupDataBasesRustic(self):
 | |
| 
 | |
|         ### This function will backup databases of the website, also need to take care of database that we need to exclude
 | |
|         ### excluded databases are in a list self.data['ExcludedDatabases'] only backup databases if backupdatabase check is on
 | |
|         ## For example if self.data['BackupDatabase'] is one then only run this function otherwise not
 | |
| 
 | |
|         # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'rustic init -r {self.repo} --password ""'
 | |
|         ProcessUtilities.executioner(command, self.website.externalApp)
 | |
| 
 | |
|         command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|         ProcessUtilities.executioner(command)
 | |
| 
 | |
|         from plogical.mysqlUtilities import mysqlUtilities
 | |
| 
 | |
|         for dbs in self.config['databases']:
 | |
| 
 | |
|             ### Pending: Need to only backup database present in the list of databases that need backing up
 | |
| 
 | |
|             for key, value in dbs.items():
 | |
|                 print(f'DB {key}')
 | |
| 
 | |
|                 CurrentDBPath = f"{self.FinalPathRuctic}/{key}.sql"
 | |
| 
 | |
|                 DBResult, SnapID = mysqlUtilities.createDatabaseBackup(key, self.FinalPathRuctic, 1, self.repo,
 | |
|                                                                        self.website.externalApp)
 | |
| 
 | |
|                 if DBResult == 1:
 | |
|                     self.snapshots.append(SnapID)
 | |
| 
 | |
|                     # command = f'chown {self.website.externalApp}:{self.website.externalApp} {CurrentDBPath}'
 | |
|                     # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     ## Now pack config into same thing
 | |
| 
 | |
|                     # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}/config.json'
 | |
|                     # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     # command = f'rustic -r {self.repo} backup {CurrentDBPath} --password "" --json 2>/dev/null'
 | |
|                     # print(f'db command rustic: {command}')
 | |
|                     # result = json.loads(
 | |
|                     #     ProcessUtilities.outputExecutioner(command, self.website.externalApp, True).rstrip('\n'))
 | |
|                     #
 | |
|                     # try:
 | |
|                     #     SnapShotID = result['id']  ## snapshot id that we need to store in db
 | |
|                     #     files_new = result['summary']['files_new']  ## basically new files in backup
 | |
|                     #     total_duration = result['summary']['total_duration']  ## time taken
 | |
|                     #
 | |
|                     #     self.snapshots.append(SnapShotID)
 | |
|                     #
 | |
|                     #     ### Config is saved with each database, snapshot of config is attached to db snapshot with parent
 | |
|                     #
 | |
|                     #     #self.BackupConfig(SnapShotID)
 | |
|                     #
 | |
|                     #     command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|                     #     ProcessUtilities.executioner(command)
 | |
|                     #
 | |
|                     # except BaseException as msg:
 | |
|                     #     self.UpdateStatus(f'Backup failed as no snapshot id found, error: {str(msg)}',
 | |
|                     #                       CPBackupsV2.FAILED)
 | |
|                     #     return 0
 | |
|                     #
 | |
|                     #
 | |
|                     # for dbUsers in value:
 | |
|                     #     print(f'User: {dbUsers["user"]}, Host: {dbUsers["host"]}, Pass: {dbUsers["password"]}')
 | |
|                     #
 | |
|                     # command = f'rm -f {CurrentDBPath}'
 | |
|                     # ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 else:
 | |
|                     command = f'rm -f {CurrentDBPath}'
 | |
|                     ProcessUtilities.executioner(command)
 | |
|                     self.UpdateStatus(f'Failed to create backup for database {key}.', CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|         return 1
 | |
| 
 | |
|     def BackupRustic(self):
 | |
| 
 | |
|         ### This function will backup data of the website, also need to take care of directories that we need to exclude
 | |
|         ### excluded directories are in a list self.data['ExcludedDirectories'] only backup data if backupdata check is on
 | |
|         ## For example if self.data['BackupData'] is one then only run this function otherwise not
 | |
| 
 | |
|         # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'rustic init -r {self.repo} --password ""'
 | |
|         ProcessUtilities.executioner(command, self.website.externalApp)
 | |
| 
 | |
|         source = f'/home/{self.website.domain}'
 | |
| 
 | |
|         # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}/config.json'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         ## Pending add user provided folders in the exclude list
 | |
| 
 | |
|         exclude = f' --glob !{source}/logs '
 | |
| 
 | |
|         command = f'rustic -r {self.repo} backup {source} --password "" {exclude} --json 2>/dev/null'
 | |
|         status, result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True, None, True)
 | |
| 
 | |
|         if os.path.exists(ProcessUtilities.debugPath):
 | |
|             logging.CyberCPLogFileWriter.writeToFile(f'Status code {status}')
 | |
| 
 | |
|         if status:
 | |
|             result = json.loads(result.rstrip('\n'))
 | |
| 
 | |
|             try:
 | |
|                 SnapShotID = result['id']  ## snapshot id that we need to store in db
 | |
|                 files_new = result['summary']['files_new']  ## basically new files in backup
 | |
|                 total_duration = result['summary']['total_duration']  ## time taken
 | |
| 
 | |
|                 self.snapshots.append(SnapShotID)
 | |
| 
 | |
|                 ### Config is saved with each backup, snapshot of config is attached to data snapshot with parent
 | |
| 
 | |
|                 # self.BackupConfig(SnapShotID)
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Backup failed as no snapshot id found, error: {str(msg)}', CPBackupsV2.FAILED)
 | |
|                 return 0
 | |
| 
 | |
|             # self.UpdateStatus(f'Rustic command result id: {SnapShotID}, files new {files_new}, total_duration {total_duration}', CPBackupsV2.RUNNING)
 | |
| 
 | |
|             return 1
 | |
|         else:
 | |
|             self.UpdateStatus(f'Backup failed, error: {str(result)}', CPBackupsV2.FAILED)
 | |
|             return 0
 | |
| 
 | |
|     def BackupEmailsRustic(self):
 | |
| 
 | |
|         ### This function will backup emails of the website, also need to take care of emails that we need to exclude
 | |
|         ### excluded emails are in a list self.data['ExcludedEmails'] only backup data if backupemail check is on
 | |
|         ## For example if self.data['BackupEmails'] is one then only run this function otherwise not
 | |
| 
 | |
|         # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         command = f'rustic init -r {self.repo} --password ""'
 | |
|         ProcessUtilities.executioner(command, self.website.externalApp)
 | |
| 
 | |
|         # command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}/config.json'
 | |
|         # ProcessUtilities.executioner(command)
 | |
| 
 | |
|         source = f'/home/vmail/{self.website.domain}'
 | |
| 
 | |
|         if os.path.exists(source):
 | |
|             ## Pending add user provided folders in the exclude list
 | |
| 
 | |
|             exclude = f' --exclude-if-present rusticbackup  --exclude-if-present logs '
 | |
| 
 | |
|             command = f'export RCLONE_CONFIG=/home/{self.website.domain}/.config/rclone/rclone.conf && rustic -r {self.repo} backup {source} --password "" {exclude} --json 2>/dev/null'
 | |
| 
 | |
|             result = json.loads(ProcessUtilities.outputExecutioner(command, None, True).rstrip('\n'))
 | |
| 
 | |
|             try:
 | |
|                 SnapShotID = result['id']  ## snapshot id that we need to store in db
 | |
|                 files_new = result['summary']['files_new']  ## basically new files in backup
 | |
|                 total_duration = result['summary']['total_duration']  ## time taken
 | |
| 
 | |
|                 self.snapshots.append(SnapShotID)
 | |
| 
 | |
|                 ### Config is saved with each email backup, snapshot of config is attached to email snapshot with parent
 | |
| 
 | |
|                 # self.BackupConfig(SnapShotID)
 | |
| 
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Backup failed as no snapshot id found, error: {str(msg)}', CPBackupsV2.FAILED)
 | |
|                 return 0
 | |
| 
 | |
|         return 1
 | |
| 
 | |
|     #### Resote Functions
 | |
| 
 | |
|     def RestoreConfig(self):
 | |
|         try:
 | |
| 
 | |
|             self.UpdateStatus(f'Restoring config..,10',
 | |
|                               CPBackupsV2.RUNNING)
 | |
|             ConfigPath = f'/home/backup/{self.website.domain}/config.json'
 | |
|             RestoreConfigPath = f'/home/{self.website.domain}/'
 | |
| 
 | |
|             command = f'rustic -r {self.repo} restore {self.data["snapshotid"]}:{ConfigPath} {RestoreConfigPath} --password ""  2>/dev/null'
 | |
|             result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|             ConfigContent = json.loads(ProcessUtilities.outputExecutioner(f'cat {RestoreConfigPath}/config.json').rstrip('\n'))
 | |
| 
 | |
|             ### ACL Creation code
 | |
| 
 | |
|             # ### First check if the acl exists
 | |
|             # from loginSystem.models import ACL, Administrator
 | |
|             # from django.http import HttpRequest
 | |
|             # requestNew = HttpRequest()
 | |
|             # try:
 | |
|             #     if os.path.exists(ProcessUtilities.debugPath):
 | |
|             #         logging.CyberCPLogFileWriter.writeToFile(f'ACL in config: {ConfigContent["acl"]}')
 | |
|             #     acl = ACL.objects.get(name=ConfigContent['acl']['name'])
 | |
|             # except:
 | |
|             #     if os.path.exists(ProcessUtilities.debugPath):
 | |
|             #         logging.CyberCPLogFileWriter.writeToFile('ACL Already existed.')
 | |
|             #     requestNew.session['userID'] = Administrator.objects.get(userName='admin').id
 | |
|             #     from userManagment.views import createACLFunc
 | |
|             #     dataToPass = ConfigContent['acl']
 | |
|             #     dataToPass['makeAdmin'] = ConfigContent['acl']['adminStatus']
 | |
|             #     requestNew._body = json.dumps(dataToPass)
 | |
|             #
 | |
|             #     if os.path.exists(ProcessUtilities.debugPath):
 | |
|             #         logging.CyberCPLogFileWriter.writeToFile(f'Passed content to Create ACL Func: {json.dumps(dataToPass)}')
 | |
|             #
 | |
|             #     resp = createACLFunc(requestNew)
 | |
|             #     if os.path.exists(ProcessUtilities.debugPath):
 | |
|             #         logging.CyberCPLogFileWriter.writeToFile(f'CreateACLFunc stats: {str(resp.content)}')
 | |
| 
 | |
|             ### Create DNS Records
 | |
| 
 | |
|             try:
 | |
|                 from dns.models import Domains
 | |
|                 zone = Domains.objects.get(name=self.website.domain)
 | |
|                 from plogical.dnsUtilities import DNS
 | |
| 
 | |
|                 for record in ConfigContent['DNSRecords']:
 | |
|                     DNS.createDNSRecord(zone, record['name'], record['type'], record['content'], 0, record['ttl'])
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Error in RestoreConfig while restoring dns config. Error: {str(msg)}',
 | |
|                                       CPBackupsV2.RUNNING)
 | |
| 
 | |
| 
 | |
|             ### Create Emails Accounts
 | |
| 
 | |
| 
 | |
|             #logging.statusWriter(statusPath, "Restoring email accounts!", 1)
 | |
| 
 | |
|             try:
 | |
| 
 | |
|                 from plogical.mailUtilities import mailUtilities
 | |
|                 for emailAccount in ConfigContent['EmailAddresses']:
 | |
| 
 | |
|                     email = emailAccount['email']
 | |
|                     username = email.split("@")[0]
 | |
|                     password = emailAccount['password']
 | |
| 
 | |
|                     result = mailUtilities.createEmailAccount(self.website.domain, username, password)
 | |
|                     if result[0] == 0:
 | |
|                         # #logging.statusWriter(statusPath,
 | |
|                         #                      'Email existed, updating password according to last snapshot. %s' % (
 | |
|                         #                          email))
 | |
|                         if mailUtilities.changeEmailPassword(email, password, 1)[0] == 0:
 | |
|                             # logging.statusWriter(statusPath,
 | |
|                             #                      'Failed changing password for: %s' % (
 | |
|                             #                          email))
 | |
|                             pass
 | |
|                         else:
 | |
|                             pass
 | |
|                             # logging.statusWriter(statusPath,
 | |
|                             #                      'Password changed for: %s' % (
 | |
|                             #                          email))
 | |
| 
 | |
|                     else:
 | |
|                         pass
 | |
|                         # logging.statusWriter(statusPath,
 | |
|                         #                      'Email created: %s' % (
 | |
|                         #                          email))
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Error in RestoreConfig while restoring email config. Error: {str(msg)}',
 | |
|                                           CPBackupsV2.RUNNING)
 | |
| 
 | |
| 
 | |
|             ### Restoring DBs
 | |
| 
 | |
|             try:
 | |
| 
 | |
|                 from databases.models import Databases, DatabasesUsers
 | |
| 
 | |
|                 for database in ConfigContent['databases']:
 | |
| 
 | |
|                     dbName = list(database.keys())[0]
 | |
| 
 | |
|                     if os.path.exists(ProcessUtilities.debugPath):
 | |
|                         logging.CyberCPLogFileWriter.writeToFile(f'Databasename: {dbName}')
 | |
| 
 | |
|                     first = 1
 | |
| 
 | |
|                     databaseUsers = database[dbName]
 | |
| 
 | |
|                     for databaseUser in databaseUsers:
 | |
| 
 | |
|                         dbUser = databaseUser['user']
 | |
|                         dbHost = databaseUser['host']
 | |
|                         password = databaseUser['password']
 | |
| 
 | |
|                         if os.path.exists(ProcessUtilities.debugPath):
 | |
|                             logging.CyberCPLogFileWriter.writeToFile('Database user: %s' % (dbUser))
 | |
|                             logging.CyberCPLogFileWriter.writeToFile('Database host: %s' % (dbHost))
 | |
|                             logging.CyberCPLogFileWriter.writeToFile('Database password: %s' % (password))
 | |
| 
 | |
|                         if first:
 | |
| 
 | |
|                             first = 0
 | |
| 
 | |
|                             try:
 | |
|                                 dbExist = Databases.objects.get(dbName=dbName)
 | |
|                                 logging.CyberCPLogFileWriter.writeToFile('Database exists, changing Database password.. %s' % (dbName))
 | |
| 
 | |
|                                 if mysqlUtilities.mysqlUtilities.changePassword(dbUser, password, 1, dbHost) == 0:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile('Failed changing password for database: %s' % (dbName))
 | |
|                                 else:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile('Password successfully changed for database: %s.' % (dbName))
 | |
| 
 | |
|                             except:
 | |
| 
 | |
|                                 logging.CyberCPLogFileWriter.writeToFile('Database did not exist, creating new.. %s' % (dbName))
 | |
| 
 | |
|                                 if mysqlUtilities.mysqlUtilities.createDatabase(dbName, dbUser, "cyberpanel") == 0:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile('Failed the creation of database: %s' % (dbName))
 | |
|                                 else:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile('Database: %s successfully created.' % (dbName))
 | |
| 
 | |
|                                 mysqlUtilities.mysqlUtilities.changePassword(dbUser, password, 1)
 | |
| 
 | |
|                                 if mysqlUtilities.mysqlUtilities.changePassword(dbUser, password, 1) == 0:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile('Failed changing password for database: %s' % (dbName))
 | |
|                                 else:
 | |
|                                     logging.CyberCPLogFileWriter.writeToFile(
 | |
|                                                          'Password successfully changed for database: %s.' % (dbName))
 | |
| 
 | |
|                                 try:
 | |
|                                     newDB = Databases(website=self.website, dbName=dbName, dbUser=dbUser)
 | |
|                                     newDB.save()
 | |
|                                 except:
 | |
|                                     pass
 | |
| 
 | |
|                         ## This function will not create database, only database user is created as third value is 0 for createDB
 | |
| 
 | |
|                         mysqlUtilities.mysqlUtilities.createDatabase(dbName, dbUser, password, 0, dbHost)
 | |
|                         mysqlUtilities.mysqlUtilities.changePassword(dbUser, password, 1, dbHost)
 | |
|             except BaseException as msg:
 | |
|                 self.UpdateStatus(f'Error in RestoreConfig while restoring database config. Error: {str(msg)}', CPBackupsV2.RUNNING)
 | |
| 
 | |
|             return 1, None
 | |
| 
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
| 
 | |
|     def InitiateRestore(self):
 | |
| 
 | |
|         ### if restore then status file should be restore status file
 | |
| 
 | |
|         self.restore = 1
 | |
|         # self.StatusFile = self.StatusFile_Restore
 | |
| 
 | |
|         from websiteFunctions.models import Websites
 | |
|         from plogical.mysqlUtilities import mysqlUtilities
 | |
|         self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|         self.UpdateStatus('Started restoring,20', CPBackupsV2.RUNNING)
 | |
| 
 | |
|         ## Base path is basically the path set by user where all the backups will be housed
 | |
| 
 | |
|         if not os.path.exists(self.data['BasePath']):
 | |
|             command = f"mkdir -p {self.data['BasePath']}"
 | |
|             ProcessUtilities.executioner(command)
 | |
| 
 | |
|             #command = f"chmod 711 {self.data['BasePath']}"
 | |
|             #ProcessUtilities.executioner(command)
 | |
| 
 | |
|         self.StartingTimeStamp = CPBackupsV2.FetchCurrentTimeStamp()
 | |
| 
 | |
|         ### Init rustic repo in main func so dont need to do again and again
 | |
| 
 | |
|         while (1):
 | |
| 
 | |
|             self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|             if self.website.BackupLock == 0:
 | |
| 
 | |
|                 Disk1 = f"du -sm /home/{self.website.domain}/"
 | |
|                 Disk2 = "2>/dev/null | awk '{print $1}'"
 | |
| 
 | |
|                 self.WebsiteDiskUsage = int(
 | |
|                     ProcessUtilities.outputExecutioner(f"{Disk1} {Disk2}", 'root', True).rstrip('\n'))
 | |
| 
 | |
|                 self.CurrentFreeSpaceOnDisk = int(
 | |
|                     ProcessUtilities.outputExecutioner("df -m / | awk 'NR==2 {print $4}'", 'root', True).rstrip('\n'))
 | |
| 
 | |
|                 if self.WebsiteDiskUsage > self.CurrentFreeSpaceOnDisk:
 | |
|                     self.UpdateStatus(f'Not enough disk space on the server to restore this website.',
 | |
|                                       CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|                 ### Rustic backup final path
 | |
| 
 | |
|                 self.FinalPathRuctic = f"{self.data['BasePath']}/{self.website.domain}"
 | |
| 
 | |
|                 command = f"mkdir -p {self.FinalPathRuctic}"
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = f"chmod 711 {self.FinalPathRuctic}"
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 ### Find Restore path first, if path is db, only then restore it to cp
 | |
| 
 | |
|                 status, message = self.RestoreConfig()
 | |
|                 if status == 0:
 | |
|                     self.UpdateStatus(f'Failed to restore config, Error {message}',
 | |
|                                       CPBackupsV2.FAILED)
 | |
|                     return 0
 | |
| 
 | |
|                 if self.data["path"].find('.sql') > -1:
 | |
|                     mysqlUtilities.restoreDatabaseBackup(self.data["path"].rstrip('.sql'), None, None, None, None, 1,
 | |
|                                                          self.repo, self.website.externalApp, self.data["snapshotid"])
 | |
| 
 | |
|                 else:
 | |
| 
 | |
|                     if self.data["path"].find('/home/vmail') > -1:
 | |
|                         externalApp = None
 | |
|                         InitialCommand = f'export RCLONE_CONFIG=/home/{self.website.domain}/.config/rclone/rclone.conf && '
 | |
|                     else:
 | |
|                         externalApp = self.website.externalApp
 | |
|                         InitialCommand = ''
 | |
| 
 | |
|                     command = f'{InitialCommand}rustic -r {self.repo} restore {self.data["snapshotid"]}:{self.data["path"]} {self.data["path"]} --password ""  2>/dev/null'
 | |
|                     result = ProcessUtilities.outputExecutioner(command, externalApp, True)
 | |
| 
 | |
|                     if os.path.exists(ProcessUtilities.debugPath):
 | |
|                         logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
|                 self.UpdateStatus('Completed', CPBackupsV2.COMPLETED)
 | |
| 
 | |
|                 return 1
 | |
| 
 | |
| 
 | |
|             else:
 | |
|                 time.sleep(5)
 | |
| 
 | |
|                 ### If website lock is there for more then 20 minutes it means old backup is stucked or backup job failed, thus continue backup
 | |
| 
 | |
|                 if float(CPBackupsV2.FetchCurrentTimeStamp()) > (float(self.StartingTimeStamp) + 1200):
 | |
|                     self.website = Websites.objects.get(domain=self.data['domain'])
 | |
|                     self.website.BackupLock = 0
 | |
|                     self.website.save()
 | |
| 
 | |
|     ### Delete Snapshots
 | |
| 
 | |
|     def DeleteSnapshots(self, deleteString):
 | |
| 
 | |
|         ### if restore then status file should be restore status file
 | |
| 
 | |
|         from websiteFunctions.models import Websites
 | |
|         self.website = Websites.objects.get(domain=self.data['domain'])
 | |
| 
 | |
|         command = f'rustic -r {self.repo} forget {deleteString} --prune --password ""  2>/dev/null'
 | |
|         result = ProcessUtilities.outputExecutioner(command, self.website.externalApp, True)
 | |
| 
 | |
|         if os.path.exists(ProcessUtilities.debugPath):
 | |
|             logging.CyberCPLogFileWriter.writeToFile(result)
 | |
| 
 | |
|     @staticmethod
 | |
|     def FetchCurrentSchedules(website):
 | |
|         try:
 | |
|             finalConfigPath = f'/home/cyberpanel/v2backups/{website}'
 | |
| 
 | |
|             if os.path.exists(finalConfigPath):
 | |
|                 command = f'cat {finalConfigPath}'
 | |
|                 RetResult = ProcessUtilities.outputExecutioner(command)
 | |
|                 print(repr(RetResult))
 | |
|                 BackupConfig = json.loads(ProcessUtilities.outputExecutioner(command).rstrip('\n'))
 | |
| 
 | |
|                 schedules = []
 | |
|                 for value in BackupConfig['schedules']:
 | |
| 
 | |
|                     schedules.append({
 | |
|                                       'repo': value['repo'],
 | |
|                                       'frequency': value['frequency'],
 | |
|                                       'websiteData': value['websiteData'],
 | |
|                                       'websiteEmails': value['websiteEmails'],
 | |
|                                       'websiteDatabases': value['websiteDatabases'],
 | |
|                                       'lastRun': value['lastRun'],
 | |
|                                       'retention': value['retention'],
 | |
|                                       'domain': website
 | |
|                                       })
 | |
| 
 | |
|                 return 1, schedules
 | |
|             else:
 | |
|                 return 1, []
 | |
| 
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
| 
 | |
|     @staticmethod
 | |
|     def refresh_V2Gdive_token(refresh_token):
 | |
|         try:
 | |
|             # refresh_token = "1//09pPJHjUgyp09CgYIARAAGAkSNgF-L9IrZ0FLMhuKVfPEwmv_6neFto3JJ-B9uXBYu1kPPdsPhSk1OJXDBA3ZvC3v_AH9S1rTIQ"
 | |
| 
 | |
|             if os.path.exists(ProcessUtilities.debugPath):
 | |
|                 logging.CyberCPLogFileWriter.writeToFile('Current Token: ' + refresh_token )
 | |
| 
 | |
|             finalData = json.dumps({'refresh_token': refresh_token})
 | |
|             r = requests.post("https://platform.cyberpersons.com/refreshToken", data=finalData
 | |
|                               )
 | |
|             newtoken = json.loads(r.text)['access_token']
 | |
| 
 | |
|             if os.path.exists(ProcessUtilities.debugPath):
 | |
|                 logging.CyberCPLogFileWriter.writeToFile('newtoken: ' + newtoken )
 | |
|                 logging.CyberCPLogFileWriter.writeToFile(r.text)
 | |
| 
 | |
|             return newtoken
 | |
|         except BaseException as msg:
 | |
|             logging.CyberCPLogFileWriter.writeToFile(f'Error in tkupdate: {str(msg)}')
 | |
|             print("Error Update token:%s" % msg)
 | |
|             return None
 | |
| 
 | |
|     @staticmethod
 | |
|     def DeleteSchedule(website, repo, frequency, websiteData, websiteDatabases, websiteEmails):
 | |
|         try:
 | |
|             finalConfigPath = f'/home/cyberpanel/v2backups/{website}'
 | |
| 
 | |
|             if os.path.exists(finalConfigPath):
 | |
|                 command = f'cat {finalConfigPath}'
 | |
|                 RetResult = ProcessUtilities.outputExecutioner(command)
 | |
|                 print(repr(RetResult))
 | |
|                 BackupConfig = json.loads(ProcessUtilities.outputExecutioner(command).rstrip('\n'))
 | |
|                 counter = 0
 | |
| 
 | |
|                 for value in BackupConfig['schedules']:
 | |
| 
 | |
|                     if value['repo'] == repo and value['frequency'] == frequency and value['websiteData'] == websiteData and \
 | |
|                             value['websiteEmails'] == websiteEmails and value['websiteDatabases'] == websiteDatabases:
 | |
|                         del BackupConfig['schedules'][counter]
 | |
|                         break
 | |
|                     else:
 | |
|                         counter = counter  + 1
 | |
| 
 | |
|                 FinalContent = json.dumps(BackupConfig)
 | |
|                 WriteToFile = open(finalConfigPath, 'w')
 | |
|                 WriteToFile.write(FinalContent)
 | |
|                 WriteToFile.close()
 | |
| 
 | |
|                 return 1, BackupConfig
 | |
|             else:
 | |
|                 return 1, []
 | |
| 
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
| 
 | |
|     @staticmethod
 | |
|     def CreateScheduleV2(website, repo, frequency, websiteData, websiteDatabases, websiteEmails, retention):
 | |
|         try:
 | |
| 
 | |
|             finalConfigPath = f'/home/cyberpanel/v2backups/{website}'
 | |
| 
 | |
|             if not os.path.exists('/home/cyberpanel/v2backups/'):
 | |
| 
 | |
|                 command = 'mkdir -p /home/cyberpanel/v2backups/'
 | |
|                 ProcessUtilities.executioner(command, 'cyberpanel')
 | |
| 
 | |
| 
 | |
|             if os.path.exists(finalConfigPath):
 | |
| 
 | |
|                 command = f'cat {finalConfigPath}'
 | |
|                 RetResult = ProcessUtilities.outputExecutioner(command)
 | |
|                 print(repr(RetResult))
 | |
|                 BackupConfig = json.loads(ProcessUtilities.outputExecutioner(command).rstrip('\n'))
 | |
| 
 | |
|                 try:
 | |
|                     BackupConfig['schedules'].append({"repo": repo, "retention": retention, "frequency": frequency, "websiteData": websiteData,
 | |
|                                       "websiteEmails": websiteEmails, "websiteDatabases": websiteDatabases,
 | |
|                                       "lastRun": ""})
 | |
|                 except:
 | |
|                     BackupConfig['schedules'] = [{"repo": repo, "retention": retention, "frequency": frequency, "websiteData": websiteData,
 | |
|                                       "websiteEmails": websiteEmails, "websiteDatabases": websiteDatabases,
 | |
|                                       "lastRun": ""}]
 | |
| 
 | |
|                 # BackupConfig['schedules'] = {"retention": "7", "frequency": frequency, "websiteData": websiteData,
 | |
|                 #                       "websiteEmails": websiteEmails, "websiteDatabases": websiteDatabases,
 | |
|                 #                       "lastRun": ""}
 | |
| 
 | |
|                 FinalContent = json.dumps(BackupConfig)
 | |
|                 WriteToFile = open(finalConfigPath, 'w')
 | |
|                 WriteToFile.write(FinalContent)
 | |
|                 WriteToFile.close()
 | |
|                 return 1, BackupConfig
 | |
|             else:
 | |
|                 BackupConfig = {'site': website,
 | |
|                                 'schedules':
 | |
|                                     [{"repo": repo, "retention": retention, "frequency": frequency,
 | |
|                                       "websiteData": websiteData,
 | |
|                                       "websiteEmails": websiteEmails, "websiteDatabases": websiteDatabases,
 | |
|                                       "lastRun": ""}]}
 | |
| 
 | |
|                 FinalContent = json.dumps(BackupConfig)
 | |
|                 WriteToFile = open(finalConfigPath, 'w')
 | |
|                 WriteToFile.write(FinalContent)
 | |
|                 WriteToFile.close()
 | |
| 
 | |
|                 return 1, BackupConfig
 | |
| 
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
| 
 | |
| 
 | |
| 
 | |
|     @staticmethod
 | |
|     def DeleteRepoScheduleV2(website, repo, eu):
 | |
|         try:
 | |
|             finalConfigPath = f'/home/{website}/.config/rclone/rclone.conf'
 | |
| 
 | |
|             if os.path.exists(finalConfigPath):
 | |
|                 command = f"sed -i '/\[{repo}\]/,/^$/d' {finalConfigPath}"
 | |
|                 ProcessUtilities.outputExecutioner(command, eu, True)
 | |
| 
 | |
| 
 | |
|                 return 1, 'Done'
 | |
|             else:
 | |
|                 return 0, "Repo not found!"
 | |
|         except BaseException as msg:
 | |
|             return 0, str(msg)
 | |
|     # def BackupEmails(self):
 | |
|     #
 | |
|     #     ### This function will backup emails of the website, also need to take care of emails that we need to exclude
 | |
|     #     ### excluded emails are in a list self.data['ExcludedEmails'] only backup data if backupemail check is on
 | |
|     #     ## For example if self.data['BackupEmails'] is one then only run this function otherwise not
 | |
|     #
 | |
|     #     destination = f'{self.FinalPath}/emails'
 | |
|     #     source = f'/home/vmail/{self.website.domain}'
 | |
|     #
 | |
|     #     ## Pending add user provided folders in the exclude list
 | |
|     #
 | |
|     #     exclude = f'--exclude=.cache --exclude=.cache --exclude=.cache --exclude=.wp-cli ' \
 | |
|     #               f'--exclude=backup --exclude=incbackup --exclude=incbackup --exclude=logs --exclude=lscache'
 | |
|     #
 | |
|     #     command = f'mkdir -p {destination}'
 | |
|     #     ProcessUtilities.executioner(command, 'cyberpanel')
 | |
|     #
 | |
|     #     command = f'chown vmail:vmail {destination}'
 | |
|     #     ProcessUtilities.executioner(command)
 | |
|     #
 | |
|     #     command = f'rsync -av  {source}/ {destination}/'
 | |
|     #     ProcessUtilities.executioner(command, 'vmail')
 | |
|     #
 | |
|     #     return 1
 | |
| 
 | |
|     # def BackupDataBases(self):
 | |
|     #
 | |
|     #     ### This function will backup databases of the website, also need to take care of database that we need to exclude
 | |
|     #     ### excluded databases are in a list self.data['ExcludedDatabases'] only backup databases if backupdatabase check is on
 | |
|     #     ## For example if self.data['BackupDatabase'] is one then only run this function otherwise not
 | |
|     #
 | |
|     #     command = f'chown {self.website.externalApp}:{self.website.externalApp} {self.FinalPathRuctic}'
 | |
|     #     ProcessUtilities.executioner(command)
 | |
|     #
 | |
|     #     command = f'rustic init -r {self.FinalPathRuctic} --password ""'
 | |
|     #     ProcessUtilities.executioner(command, self.website.externalApp)
 | |
|     #
 | |
|     #     command = f'chown cyberpanel:cyberpanel {self.FinalPathRuctic}'
 | |
|     #     ProcessUtilities.executioner(command)
 | |
|     #
 | |
|     #     from plogical.mysqlUtilities import mysqlUtilities
 | |
|     #
 | |
|     #     for dbs in self.config['databases']:
 | |
|     #
 | |
|     #         ### Pending: Need to only backup database present in the list of databases that need backing up
 | |
|     #
 | |
|     #         for key, value in dbs.items():
 | |
|     #             print(f'DB {key}')
 | |
|     #
 | |
|     #             if mysqlUtilities.createDatabaseBackup(key, self.FinalPath) == 0:
 | |
|     #                 self.UpdateStatus(f'Failed to create backup for database {key}.', CPBackupsV2.RUNNING)
 | |
|     #                 return 0
 | |
|     #
 | |
|     #             for dbUsers in value:
 | |
|     #                 print(f'User: {dbUsers["user"]}, Host: {dbUsers["host"]}, Pass: {dbUsers["password"]}')
 | |
|     #
 | |
|     #
 | |
|     #
 | |
|     #     return 1
 | |
| 
 | |
|     # def BackupData(self):
 | |
|     #
 | |
|     #     ### This function will backup data of the website, also need to take care of directories that we need to exclude
 | |
|     #     ### excluded directories are in a list self.data['ExcludedDirectories'] only backup data if backupdata check is on
 | |
|     #     ## For example if self.data['BackupData'] is one then only run this function otherwise not
 | |
|     #
 | |
|     #     destination = f'{self.FinalPath}/data'
 | |
|     #     source = f'/home/{self.website.domain}'
 | |
|     #
 | |
|     #     ## Pending add user provided folders in the exclude list
 | |
|     #
 | |
|     #     exclude = f'--exclude=.cache --exclude=.cache --exclude=.cache --exclude=.wp-cli ' \
 | |
|     #               f'--exclude=backup --exclude=incbackup --exclude=incbackup --exclude=logs --exclude=lscache'
 | |
|     #
 | |
|     #     command = f'mkdir -p {destination}'
 | |
|     #     ProcessUtilities.executioner(command, 'cyberpanel')
 | |
|     #
 | |
|     #     command = f'chown {self.website.externalApp}:{self.website.externalApp} {destination}'
 | |
|     #     ProcessUtilities.executioner(command)
 | |
|     #
 | |
|     #     command = f'rsync -av {exclude}  {source}/ {destination}/'
 | |
|     #     ProcessUtilities.executioner(command, self.website.externalApp)
 | |
|     #
 | |
|     #     return 1
 | |
| 
 | |
|     def InstallRustic(self):
 | |
|         try:
 | |
| 
 | |
|             if not os.path.exists(CPBackupsV2.RUSTIC_PATH):
 | |
| 
 | |
|                 ### also install rclone
 | |
| 
 | |
|                 command = 'curl https://rclone.org/install.sh | sudo bash'
 | |
|                 ProcessUtilities.executioner(command, None, True)
 | |
| 
 | |
| 
 | |
| 
 | |
|                 url = "https://api.github.com/repos/rustic-rs/rustic/releases/latest"
 | |
|                 response = requests.get(url)
 | |
| 
 | |
|                 if response.status_code == 200:
 | |
|                     data = response.json()
 | |
|                     version = data['tag_name']
 | |
|                     name = data['name']
 | |
|                 else:
 | |
|                     return 0, str(response.content)
 | |
| 
 | |
|                 # sudo mv filename /usr/bin/
 | |
|                 from plogical.acl import ACLManager
 | |
| 
 | |
|                 if ACLManager.ISARM():
 | |
|                     command = 'wget -P /home/rustic https://github.com/rustic-rs/rustic/releases/download/%s/rustic-%s-aarch64-unknown-linux-gnu.tar.gz' % (
 | |
|                         version, version)
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     command = 'tar xzf /home/rustic/rustic-%s-aarch64-unknown-linux-gnu.tar.gz -C /home/rustic//' % (
 | |
|                         version)
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 else:
 | |
|                     command = 'wget -P /home/rustic https://github.com/rustic-rs/rustic/releases/download/%s/rustic-%s-x86_64-unknown-linux-musl.tar.gz' % (
 | |
|                 version, version)
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                     command = 'tar xzf /home/rustic/rustic-%s-x86_64-unknown-linux-musl.tar.gz -C /home/rustic//' % (
 | |
|                         version)
 | |
|                     ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = 'sudo mv /home/rustic/rustic /usr/bin/'
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|                 command = 'rm -rf /home/rustic'
 | |
|                 ProcessUtilities.executioner(command)
 | |
| 
 | |
|             return 1, None
 | |
| 
 | |
|         except BaseException as msg:
 | |
|             print('Error: %s' % msg)
 | |
|             return 0, str(msg)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     try:
 | |
|         parser = argparse.ArgumentParser(description='CyberPanel Backup Generator')
 | |
|         parser.add_argument('function', help='Specify a function to call!')
 | |
|         parser.add_argument('--path', help='')
 | |
| 
 | |
|         args = parser.parse_args()
 | |
| 
 | |
|         if args.function == "BackupDataBases":
 | |
|             cpbuv2 = CPBackupsV2({'finalPath': args.path})
 | |
|             # cpbuv2.BackupDataBases()
 | |
| 
 | |
|     except:
 | |
|         cpbuv2 = CPBackupsV2(
 | |
|             {'function': 'InitiateRestore', 'domain': 'cyberpanel.net', 'BasePath': '/home/backup', 'SnapShotID': 1,
 | |
|              'BackendName': 'usman'})
 | |
|         cpbuv2.InitiateRestore()
 |