From b386a1963c70498e04bb975e7e17236eab3766e3 Mon Sep 17 00:00:00 2001 From: "usman@cyberpersons.com" Date: Mon, 3 Apr 2023 16:16:01 +0500 Subject: [PATCH] initial complete of rustic backups restore --- IncBackups/static/IncBackups/IncBackups.js | 3 +- .../templates/IncBackups/RestoreV2Backup.html | 1 + IncBackups/views.py | 15 +- plogical/Backupsv2.py | 193 +++--------------- plogical/mysqlUtilities.py | 69 ++++--- 5 files changed, 73 insertions(+), 208 deletions(-) diff --git a/IncBackups/static/IncBackups/IncBackups.js b/IncBackups/static/IncBackups/IncBackups.js index ea8ec296f..a3776ce12 100644 --- a/IncBackups/static/IncBackups/IncBackups.js +++ b/IncBackups/static/IncBackups/IncBackups.js @@ -1294,7 +1294,8 @@ app.controller('restorev2backupoage', function ($scope, $http, $timeout, $compil var data = { snapshotid: SnapshotId, path: Path, - selwebsite: $scope.selwebsite + selwebsite: $scope.selwebsite, + selectedrepo:$('#reposelectbox').val() } var config = { diff --git a/IncBackups/templates/IncBackups/RestoreV2Backup.html b/IncBackups/templates/IncBackups/RestoreV2Backup.html index 9e8ed155c..bc75bc74d 100644 --- a/IncBackups/templates/IncBackups/RestoreV2Backup.html +++ b/IncBackups/templates/IncBackups/RestoreV2Backup.html @@ -40,6 +40,7 @@
+ diff --git a/IncBackups/views.py b/IncBackups/views.py index c412e7962..054ea1069 100644 --- a/IncBackups/views.py +++ b/IncBackups/views.py @@ -843,6 +843,7 @@ def RestorePathV2(request): SnapShotId = data['snapshotid'] Path = data['path'] Selectedwebsite = data['selwebsite'] + Selectedrepo = data['selectedrepo'] currentACL = ACLManager.loadedACL(userID) admin = Administrator.objects.get(pk=userID) @@ -852,25 +853,17 @@ def RestorePathV2(request): else: return ACLManager.loadError() - # f'rustic -r testremote snapshots --password "" --json 2>/dev/null' - # final_json = json.dumps({'status': 0, 'fetchStatus': 1, 'error_message': Selectedrepo }) - # return HttpResponse(final_json) - - vm = CPBackupsV2({'domain': Selectedwebsite, 'BackendName': Selectedrepo, "function": ""}) - status, data = vm.FetchSnapShots() + vm = CPBackupsV2({'domain': Selectedwebsite, 'BackendName': Selectedrepo, "function": "", 'BasePath': '/home/backup'}) + status = vm.InitiateRestore(SnapShotId, Path) if status == 1: - final_json = json.dumps({'status': 1, 'fetchStatus': 1, 'error_message': "None", "data": data}) + final_json = json.dumps({'status': 1, 'fetchStatus': 1, 'error_message': "None"}) return HttpResponse(final_json) else: # final_json = json.dumps({'status': 0, 'fetchStatus': 1, 'error_message': ac,}) final_json = json.dumps({'status': 0, 'fetchStatus': 1, 'error_message': 'Cannot Find!', }) return HttpResponse(final_json) - final_dic = {'status': 1, 'SnapShotId': SnapShotId, 'Path': Path} - final_json = json.dumps(final_dic) - return HttpResponse(final_json) - except BaseException as msg: final_dic = {'status': 0, 'fetchStatus': 0, 'error_message': str(msg)} final_json = json.dumps(final_dic) diff --git a/plogical/Backupsv2.py b/plogical/Backupsv2.py index ae02f6635..a9c1bdc68 100644 --- a/plogical/Backupsv2.py +++ b/plogical/Backupsv2.py @@ -60,6 +60,12 @@ class CPBackupsV2(multi.Thread): ## 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) @@ -629,7 +635,12 @@ token = {token} #### Resote Functions - def InitiateRestore(self): + def InitiateRestore(self, snapshotid, path): + + ### if restore then status file should be restore status file + + self.restore = 1 + self.StatusFile = self.StatusFile_Restore from websiteFunctions.models import Websites, Backupsv2 from django.forms.models import model_to_dict @@ -663,42 +674,14 @@ token = {token} 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) + self.UpdateStatus(f'Not enough disk space on the server to restore this website.', CPBackupsV2.FAILED) return 0 - ### 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) @@ -708,151 +691,29 @@ token = {token} command = f"chmod 711 {self.FinalPathRuctic}" ProcessUtilities.executioner(command) - try: + ### Find Restore path first, if path is db, only then restore it to cp - self.UpdateStatus('Creating backup config,0', CPBackupsV2.RUNNING) + if path.find('.sql') > -1: + mysqlUtilities.restoreDatabaseBackup(path.rstrip('.sql'), None, None, None, None, 1, self.repo, self.website.externalApp, snapshotid) + else: - 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) + if path.find('/home/vmail') > -1: + externalApp = None + else: + externalApp = self.website.externalApp - ### Child domains to config + command = f'rustic -r {self.repo} restore {snapshotid}:{path} {path} --password "" --json 2>/dev/null' + result = ProcessUtilities.outputExecutioner(command, externalApp, True) - ChildsList = [] + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(result) - for childDomains in self.website.childdomains_set.all(): - print(childDomains.domain) - ChildsList.append(model_to_dict(childDomains)) + self.UpdateStatus('Completed', CPBackupsV2.COMPLETED) - Config['ChildDomains'] = ChildsList + return 1 - #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 - - 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 - - ### 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) - return 0 - - self.UpdateStatus('Database backups completed successfully..,25', CPBackupsV2.RUNNING) - - if self.data['BackupData']: - self.UpdateStatus('Backing up website data..,30', CPBackupsV2.RUNNING) - if self.BackupRustic() == 0: - return 0 - self.UpdateStatus('Website data backup completed successfully..,70', CPBackupsV2.RUNNING) - - if self.data['BackupEmails']: - self.UpdateStatus('Backing up emails..,75', CPBackupsV2.RUNNING) - if self.BackupEmailsRustic() == 0: - return 0 - 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) - - self.MergeSnapshots() - - self.UpdateStatus('Completed', CPBackupsV2.COMPLETED) - - break - except BaseException as msg: - self.UpdateStatus(f'Failed, Error: {str(msg)}', CPBackupsV2.FAILED) - return 0 else: time.sleep(5) diff --git a/plogical/mysqlUtilities.py b/plogical/mysqlUtilities.py index 2be4a3cec..1b22713fc 100755 --- a/plogical/mysqlUtilities.py +++ b/plogical/mysqlUtilities.py @@ -339,7 +339,7 @@ password=%s return 0 @staticmethod - def restoreDatabaseBackup(databaseName, tempStoragePath, dbPassword, passwordCheck = None, additionalName = None): + def restoreDatabaseBackup(databaseName, tempStoragePath, dbPassword, passwordCheck = None, additionalName = None, rustic=0, RusticRepoName = None, externalApp = None, snapshotid = None): try: passFile = "/etc/cyberpanel/mysqlPassword" @@ -379,38 +379,47 @@ password=%s command = 'chown cyberpanel:cyberpanel %s' % (cnfPath) subprocess.call(shlex.split(command)) - command = 'mysql --defaults-extra-file=/home/cyberpanel/.my.cnf -u %s --host=%s --port %s %s' % (mysqluser, mysqlhost, mysqlport, databaseName) - if os.path.exists(ProcessUtilities.debugPath): - logging.CyberCPLogFileWriter.writeToFile(f'{command} {tempStoragePath}/{databaseName} ' ) - cmd = shlex.split(command) + if rustic == 0: - if additionalName == None: - with open(tempStoragePath + "/" + databaseName + '.sql', 'r') as f: - res = subprocess.call(cmd, stdin=f) - if res != 0: - logging.CyberCPLogFileWriter.writeToFile("Could not restore MYSQL database: " + databaseName +"! [restoreDatabaseBackup]") - return 0 + command = 'mysql --defaults-extra-file=/home/cyberpanel/.my.cnf -u %s --host=%s --port %s %s' % (mysqluser, mysqlhost, mysqlport, databaseName) + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'{command} {tempStoragePath}/{databaseName} ' ) + cmd = shlex.split(command) + + if additionalName == None: + with open(tempStoragePath + "/" + databaseName + '.sql', 'r') as f: + res = subprocess.call(cmd, stdin=f) + if res != 0: + logging.CyberCPLogFileWriter.writeToFile("Could not restore MYSQL database: " + databaseName +"! [restoreDatabaseBackup]") + return 0 + else: + with open(tempStoragePath + "/" + additionalName + '.sql', 'r') as f: + res = subprocess.call(cmd, stdin=f) + + if res != 0: + logging.CyberCPLogFileWriter.writeToFile("Could not restore MYSQL database: " + additionalName + "! [restoreDatabaseBackup]") + return 0 + + if passwordCheck == None: + + connection, cursor = mysqlUtilities.setupConnection() + + if connection == 0: + return 0 + + passwordCMD = "use mysql;SET PASSWORD FOR '" + databaseName + "'@'%s' = '" % (mysqlUtilities.LOCALHOST) + dbPassword + "';FLUSH PRIVILEGES;" + + cursor.execute(passwordCMD) + connection.close() + + return 1 else: - with open(tempStoragePath + "/" + additionalName + '.sql', 'r') as f: - res = subprocess.call(cmd, stdin=f) + command = f'sudo -u {externalApp} rustic -r {RusticRepoName} dump {snapshotid}:{databaseName}.sql --password "" 2>/dev/null | mysql --defaults-extra-file=/home/cyberpanel/.my.cnf -u %s --host=%s --port %s %s' % ( + mysqluser, mysqlhost, mysqlport, databaseName) + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'{command} {tempStoragePath}/{databaseName} ') + ProcessUtilities.outputExecutioner(command, None, True) - if res != 0: - logging.CyberCPLogFileWriter.writeToFile("Could not restore MYSQL database: " + additionalName + "! [restoreDatabaseBackup]") - return 0 - - if passwordCheck == None: - - connection, cursor = mysqlUtilities.setupConnection() - - if connection == 0: - return 0 - - passwordCMD = "use mysql;SET PASSWORD FOR '" + databaseName + "'@'%s' = '" % (mysqlUtilities.LOCALHOST) + dbPassword + "';FLUSH PRIVILEGES;" - - cursor.execute(passwordCMD) - connection.close() - - return 1 except BaseException as msg: logging.CyberCPLogFileWriter.writeToFile(str(msg) + "[restoreDatabaseBackup]") return 0