Skip to content

Commit 402ecd7

Browse files
committed
bug fix: incremental backups
1 parent 8dc3e8b commit 402ecd7

File tree

4 files changed

+198
-208
lines changed

4 files changed

+198
-208
lines changed

IncBackups/IncBackupsControl.py

Lines changed: 113 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ def getAWSData(self):
135135
secret = open(path, 'r').read()
136136
return key, secret
137137

138-
def awsFunction(self, fType, backupPath=None, snapshotID=None, bType=None):
138+
## Last argument delete is set when the snapshot is to be deleted from this repo, when this argument is set, any preceding argument is not used
139+
140+
def awsFunction(self, fType, backupPath=None, snapshotID=None, bType=None, delete=None):
139141
try:
140142
if fType == 'backup':
141143
key, secret = self.getAWSData()
@@ -184,6 +186,25 @@ def awsFunction(self, fType, backupPath=None, snapshotID=None, bType=None):
184186
if result.find('restoring') == -1:
185187
logging.statusWriter(self.statusPath, 'Failed: %s. [5009]' % (result), 1)
186188
return 0
189+
elif delete:
190+
191+
self.backupDestinations = self.jobid.destination
192+
193+
key, secret = self.getAWSData()
194+
195+
command = 'export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s forget %s --password-file %s' % (
196+
key, secret, self.website, snapshotID, self.passwordFile)
197+
198+
result = ProcessUtilities.outputExecutioner(command)
199+
200+
if result.find('removed snapshot') == -1:
201+
logging.statusWriter(self.statusPath, 'Failed: %s. [5009]' % (result), 1)
202+
return 0
203+
204+
command = 'export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s prune --password-file %s' % (
205+
key, secret, self.website, self.passwordFile)
206+
207+
ProcessUtilities.outputExecutioner(command)
187208
else:
188209
self.backupDestinations = self.jobid.destination
189210

@@ -205,7 +226,10 @@ def awsFunction(self, fType, backupPath=None, snapshotID=None, bType=None):
205226
logging.statusWriter(self.statusPath, "%s [88][5009]" % (str(msg)), 1)
206227
return 0
207228

208-
def localFunction(self, backupPath, type, restore=None):
229+
## Last argument delete is set when the snapshot is to be deleted from this repo, when this argument is set, any preceding argument is not used
230+
231+
def localFunction(self, backupPath, type, restore=None, delete=None):
232+
209233
if restore == None:
210234
# Define our excludes file for use with restic
211235
backupExcludesFile = '/home/%s/backup-exclude.conf' % (self.website.domain)
@@ -233,6 +257,21 @@ def localFunction(self, backupPath, type, restore=None):
233257
newSnapshot = JobSnapshots(job=self.jobid, type='%s:%s' % (type, backupPath), snapshotid=snapShotid,
234258
destination=self.backupDestinations)
235259
newSnapshot.save()
260+
return 1
261+
elif delete:
262+
263+
repoLocation = '/home/%s/incbackup' % (self.website)
264+
265+
command = 'restic -r %s forget %s --password-file %s' % (repoLocation, self.jobid.snapshotid, self.passwordFile)
266+
result = ProcessUtilities.outputExecutioner(command)
267+
268+
if result.find('removed snapshot') == -1:
269+
logging.statusWriter(self.statusPath, 'Failed: %s. [5009]' % (result), 1)
270+
return 0
271+
272+
command = 'restic -r %s prune --password-file %s' % (repoLocation, self.passwordFile)
273+
ProcessUtilities.outputExecutioner(command)
274+
236275
return 1
237276
else:
238277
repoLocation = '/home/%s/incbackup' % (self.website)
@@ -247,7 +286,9 @@ def localFunction(self, backupPath, type, restore=None):
247286

248287
return 1
249288

250-
def sftpFunction(self, backupPath, type, restore=None):
289+
## Last argument delete is set when the snapshot is to be deleted from this repo, when this argument is set, any preceding argument is not used
290+
291+
def sftpFunction(self, backupPath, type, restore=None, delete=None):
251292
if restore == None:
252293
# Define our excludes file for use with restic
253294
backupExcludesFile = '/home/%s/backup-exclude.conf' % (self.website.domain)
@@ -276,6 +317,17 @@ def sftpFunction(self, backupPath, type, restore=None):
276317
destination=self.backupDestinations)
277318
newSnapshot.save()
278319
return 1
320+
elif delete:
321+
repoLocation = '/home/backup/%s' % (self.website)
322+
command = 'export PATH=${PATH}:/usr/bin && restic -r %s:%s forget %s --password-file %s' % (
323+
self.jobid.destination, repoLocation, self.jobid.snapshotid, self.passwordFile)
324+
result = ProcessUtilities.outputExecutioner(command)
325+
if result.find('removed snapshot') == -1:
326+
logging.statusWriter(self.statusPath, 'Failed: %s. [5009]' % (result), 1)
327+
return 0
328+
329+
command = 'export PATH=${PATH}:/usr/bin && restic -r %s:%s prune --password-file %s' % (self.jobid.destination, repoLocation, self.passwordFile)
330+
ProcessUtilities.outputExecutioner(command)
279331
else:
280332
if self.reconstruct == 'remote':
281333
repoLocation = '/home/backup/%s' % (self.website)
@@ -437,6 +489,7 @@ def reconstructWithMeta(self):
437489

438490
def restorePoint(self):
439491
try:
492+
440493
self.statusPath = self.extraArgs['tempPath']
441494
self.website = self.extraArgs['website']
442495
jobid = self.extraArgs['jobid']
@@ -526,177 +579,29 @@ def restorePoint(self):
526579
def prepareBackupMeta(self):
527580
try:
528581

529-
######### Generating meta
530-
531-
## XML Generation
532-
533-
metaFileXML = Element('metaFile')
534-
535-
child = SubElement(metaFileXML, 'masterDomain')
536-
child.text = self.website.domain
537-
538-
child = SubElement(metaFileXML, 'phpSelection')
539-
child.text = self.website.phpSelection
540-
541-
child = SubElement(metaFileXML, 'externalApp')
542-
child.text = self.website.externalApp
543-
544-
childDomains = self.website.childdomains_set.all()
545-
546-
databases = self.website.databases_set.all()
547-
548-
## Child domains XML
549-
550-
childDomainsXML = Element('ChildDomains')
551-
552-
for items in childDomains:
553-
childDomainXML = Element('domain')
554-
555-
child = SubElement(childDomainXML, 'domain')
556-
child.text = items.domain
557-
child = SubElement(childDomainXML, 'phpSelection')
558-
child.text = items.phpSelection
559-
child = SubElement(childDomainXML, 'path')
560-
child.text = items.path
561-
562-
childDomainsXML.append(childDomainXML)
563-
564-
metaFileXML.append(childDomainsXML)
565-
566-
## Databases XML
567-
568-
databasesXML = Element('Databases')
569-
570-
for items in databases:
571-
try:
572-
dbuser = DBUsers.objects.get(user=items.dbUser)
573-
userToTry = items.dbUser
574-
except:
575-
dbusers = DBUsers.objects.all().filter(user=items.dbUser)
576-
for it in dbusers:
577-
dbuser = it
578-
break
579-
580-
userToTry = mysqlUtilities.mysqlUtilities.fetchuser(items.dbName)
581-
582-
if userToTry == 0 or userToTry == 1:
583-
continue
584-
585-
try:
586-
dbuser = DBUsers.objects.get(user=userToTry)
587-
except:
588-
dbusers = DBUsers.objects.all().filter(user=userToTry)
589-
for it in dbusers:
590-
dbuser = it
591-
break
592-
593-
594-
databaseXML = Element('database')
595-
596-
child = SubElement(databaseXML, 'dbName')
597-
child.text = items.dbName
598-
child = SubElement(databaseXML, 'dbUser')
599-
child.text = userToTry
600-
child = SubElement(databaseXML, 'password')
601-
child.text = dbuser.password
602-
603-
databasesXML.append(databaseXML)
604-
605-
metaFileXML.append(databasesXML)
606-
607-
## Get Aliases
608-
609-
aliasesXML = Element('Aliases')
610-
611-
aliases = backupUtilities.getAliases(self.website.domain)
582+
## Use the meta function from backup utils for future improvements.
612583

613-
for items in aliases:
614-
child = SubElement(aliasesXML, 'alias')
615-
child.text = items
616584

617-
metaFileXML.append(aliasesXML)
585+
if os.path.exists(ProcessUtilities.debugPath):
586+
logging.writeToFile('Creating meta for %s. [IncBackupsControl.py]' % (self.website.domain))
618587

619-
## Finish Alias
620-
621-
## DNS Records XML
622-
623-
try:
624-
625-
dnsRecordsXML = Element("dnsrecords")
626-
dnsRecords = DNS.getDNSRecords(self.website.domain)
627-
628-
for items in dnsRecords:
629-
dnsRecordXML = Element('dnsrecord')
630-
631-
child = SubElement(dnsRecordXML, 'type')
632-
child.text = items.type
633-
child = SubElement(dnsRecordXML, 'name')
634-
child.text = items.name
635-
child = SubElement(dnsRecordXML, 'content')
636-
child.text = items.content
637-
child = SubElement(dnsRecordXML, 'priority')
638-
child.text = str(items.prio)
639-
640-
dnsRecordsXML.append(dnsRecordXML)
641-
642-
metaFileXML.append(dnsRecordsXML)
643-
644-
except BaseException as msg:
645-
logging.statusWriter(self.statusPath, '%s. [158:prepMeta]' % (str(msg)), 1)
646-
647-
## Email accounts XML
648-
649-
try:
650-
emailRecordsXML = Element('emails')
651-
eDomain = eDomains.objects.get(domain=self.website.domain)
652-
emailAccounts = eDomain.eusers_set.all()
653-
654-
for items in emailAccounts:
655-
emailRecordXML = Element('emailAccount')
656-
657-
child = SubElement(emailRecordXML, 'email')
658-
child.text = items.email
659-
child = SubElement(emailRecordXML, 'password')
660-
child.text = items.password
661-
662-
emailRecordsXML.append(emailRecordXML)
663-
664-
metaFileXML.append(emailRecordsXML)
665-
except BaseException as msg:
666-
pass
667-
#logging.statusWriter(self.statusPath, '%s. [warning:179:prepMeta]' % (str(msg)), 1)
668-
669-
## Email meta generated!
670-
671-
def prettify(elem):
672-
"""Return a pretty-printed XML string for the Element.
673-
"""
674-
rough_string = ElementTree.tostring(elem, 'utf-8')
675-
reparsed = minidom.parseString(rough_string)
676-
return reparsed.toprettyxml(indent=" ")
677-
678-
## /home/example.com/backup/backup-example-06-50-03-Thu-Feb-2018/meta.xml -- metaPath
679-
680-
metaPath = '/home/cyberpanel/%s' % (str(randint(1000, 9999)))
681-
682-
xmlpretty = prettify(metaFileXML).encode('ascii', 'ignore')
683-
metaFile = open(metaPath, 'w')
684-
metaFile.write(xmlpretty.decode('utf-8'))
685-
metaFile.close()
686-
os.chmod(metaPath, 0o640)
588+
from plogical.backupUtilities import backupUtilities
589+
status, message, metaPath = backupUtilities.prepareBackupMeta(self.website.domain, None, None, None, 0)
687590

688591
## meta generated
689592

690-
logging.statusWriter(self.statusPath, 'Meta data is ready..', 1)
691-
692-
metaPathNew = '/home/%s/meta.xml' % (self.website.domain)
693-
command = 'mv %s %s' % (metaPath, metaPathNew)
694-
ProcessUtilities.executioner(command)
695-
696-
return 1
593+
if status == 1:
594+
logging.statusWriter(self.statusPath, 'Meta data is ready..', 1)
595+
metaPathNew = '/home/%s/meta.xml' % (self.website.domain)
596+
command = 'mv %s %s' % (metaPath, metaPathNew)
597+
ProcessUtilities.executioner(command)
598+
return 1
599+
else:
600+
logging.statusWriter(self.statusPath, "%s [544][5009]" % (message), 1)
601+
return 0
697602

698603
except BaseException as msg:
699-
logging.statusWriter(self.statusPath, "%s [207][5009]" % (str(msg)), 1)
604+
logging.statusWriter(self.statusPath, "%s [548][5009]" % (str(msg)), 1)
700605
return 0
701606

702607
def backupData(self):
@@ -728,6 +633,7 @@ def backupDatabases(self):
728633
databases = self.website.databases_set.all()
729634

730635
for items in databases:
636+
731637
if mysqlUtilities.mysqlUtilities.createDatabaseBackup(items.dbName, '/home/cyberpanel') == 0:
732638
return 0
733639

@@ -760,6 +666,7 @@ def emailBackup(self):
760666
backupPath = '/home/vmail/%s' % (self.website.domain)
761667

762668
if os.path.exists(backupPath):
669+
763670
if self.backupDestinations == 'local':
764671
if self.localFunction(backupPath, 'email') == 0:
765672
return 0
@@ -929,6 +836,8 @@ def createBackup(self):
929836
if self.initiateRepo() == 0:
930837
return 0
931838

839+
840+
932841
if self.prepareBackupMeta() == 0:
933842
return 0
934843

@@ -958,3 +867,41 @@ def createBackup(self):
958867
'Failed to delete meta file: %s. [IncJobs.createBackup.591]' % str(msg), 1)
959868

960869
logging.statusWriter(self.statusPath, 'Completed', 1)
870+
871+
### Delete Snapshot
872+
873+
def DeleteSnapShot(self, inc_job):
874+
try:
875+
876+
self.statusPath = logging.fileName
877+
878+
job_snapshots = inc_job.jobsnapshots_set.all()
879+
880+
### Fetch the website name from JobSnapshot object and set these variable as they are needed in called functions below
881+
882+
self.website = job_snapshots[0].job.website.domain
883+
self.passwordFile = '/home/%s/%s' % (self.website, self.website)
884+
885+
for job_snapshot in job_snapshots:
886+
887+
## Functions above use the self.jobid varilable to extract information about this snapshot, so this below variable needs to be set
888+
889+
self.jobid = job_snapshot
890+
891+
if self.jobid.destination == 'local':
892+
if self.localFunction('none', 'none', 0, 1) == 0:
893+
return 0
894+
elif self.jobid.destination[:4] == 'sftp':
895+
if self.sftpFunction('none', 'none', 0, 1) == 0:
896+
return 0
897+
else:
898+
if self.awsFunction('restore', '', self.jobid.snapshotid, None, 1) == 0:
899+
return 0
900+
901+
return 1
902+
903+
except BaseException as msg:
904+
logging.statusWriter(self.statusPath, "%s [903:DeleteSnapShot][5009]" % (str(msg)), 1)
905+
return 0
906+
907+

IncBackups/views.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,12 @@ def delete_backup(request):
405405

406406
backup_id = data['backupID']
407407

408-
IncJob.objects.get(id=backup_id).delete()
408+
inc_job = IncJob.objects.get(id=backup_id)
409+
410+
job = IncJobs(None, None)
411+
job.DeleteSnapShot(inc_job)
412+
413+
inc_job.delete()
409414

410415
final_dic = {'status': 1, 'error_message': 'None'}
411416
final_json = json.dumps(final_dic)
@@ -450,7 +455,6 @@ def fetch_restore_points(request):
450455
final_json = json.dumps(final_dic)
451456
return HttpResponse(final_json)
452457

453-
454458
def restore_point(request):
455459
try:
456460
user_id, current_acl = _get_user_acl(request)

0 commit comments

Comments
 (0)