commit db4191ac18aa6b51d6117eff22d898d500a76315 Author: dbraeuer@bounty Date: Fri Jan 7 18:19:09 2022 +0100 [init] first commit diff --git a/rsync_python.py b/rsync_python.py new file mode 100644 index 0000000..8f7ba50 --- /dev/null +++ b/rsync_python.py @@ -0,0 +1,155 @@ +#!/usr/bin/python3 +####################################################### +# Python rsync Backup script +# Sebastian Kraft, 24.06.2013 +# +####################################################### + +#----------------------------------------------------- +# Config + +# Source directories +DIRECTORY_LIST = ( + "/home/kraft/Bilder", + "/home/kraft/Dokumente", + "/home/kraft/Projekte/", + "/home/kraft/Videos/", + "/home/kraft/Musik/" +) + +# Target directory to store the backups +TARGET_DIRECTORY = "/media/kraft/fantec_ext4/Kraft_Backup/" + +# Some settings +WRITE_LOGFILE = True # write a logfile to TARGET_DIRECTORY + +# TODO +#ALLOW_EMPTY_BACKUPS = TRUE # perform backup even if there are no changes detected by rsync +#CHECK_BACKUPS = True # check file consistency after every backup run +#------------------------------------------------------ + +####################################################### + +#------------------------------------------------------ +# Some functions + + +def ask_ok(prompt, retries=4, complaint='Please type yes or no...'): + while True: + ok = input(prompt) + if ok in ('y', 'Y', 'yes', 'Yes'): + return True + if ok in ('n', 'N', 'no', 'No'): + return False + print(complaint) + + +def print2log(s, filehandle=0): + global WRITE_LOGFILE + sys.stdout.buffer.write(bytes(s, "utf-8")) + sys.stdout.flush() + # '\r' has no effect for file write + if (WRITE_LOGFILE==True) and filehandle and (s.find('\r')==-1): + filehandle.write(bytes(s, "utf-8")) + +#------------------------------------------------------ +# Main program + +import time +import subprocess +import os +import sys +import re + +# check if target directory is mounted +if not os.path.exists(TARGET_DIRECTORY): + print("\nERROR: Target directory \n>> "+TARGET_DIRECTORY+" <<\nis not available! If it's located on an external or network drive check if it is correctly mounted in the expected place.\n\n") + sys.exit() + +# prepare logfile +if WRITE_LOGFILE: + logFile = open(os.path.join(TARGET_DIRECTORY, 'rsync_' + time.strftime( "%Y-%m-%dT%H:%M:%S") + '.log'), 'wb') +else: + logFile = 0 + +numBackupItems = len(DIRECTORY_LIST) +currBackupItem = 0 + +# iterate over directories +for backupDir in DIRECTORY_LIST: + + currBackupItem = currBackupItem + 1 + + countStr = "("+str(currBackupItem)+"/"+str(numBackupItems)+")" + + print2log("\n-----------------------------------------------------------\n", logFile) + print2log("Backing up " + backupDir + " " + countStr +"\n", logFile) + print2log("-----------------------------------------------------------\n", logFile) + + # check if source directory exists + if not os.path.exists(backupDir): + print2log("ERROR: Source directory does not exist! Skipping...\n", logFile) + input("Press any key to proceed with next backup item...") + continue + + timestamp = time.strftime( "%Y-%m-%dT%H:%M:%S") + current_backup_target = os.path.join(TARGET_DIRECTORY, os.path.basename(os.path.normpath(backupDir))) + previous_backup_link = "" + + # check for previous backups + prevBackupsFound = False + if os.path.exists(current_backup_target): + dirListing = os.listdir(current_backup_target) + dirListing = [name for name in os.listdir(current_backup_target) if os.path.isdir(os.path.join(current_backup_target,name))] + # match directory names of type 2013-06-24T18:44:31 + rex = re.compile('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]{8}$') + dirListing = [x for x in dirListing if rex.match(x)] + numOldBackups = len(dirListing) + if numOldBackups>0: + #dirListing.sort(key=lambda s: os.path.getmtime(os.path.join(current_backup_target, s))) + dirListing.sort() + dirListing.reverse() + previous_backup_link = os.path.join(current_backup_target, dirListing[0]) + print2log("Previous backup will be used as hard link reference: "+previous_backup_link+"\n", logFile) + previous_backup_link = '--link-dest="' + previous_backup_link +'" ' + prevBackupsFound = True + + # if no old backups are found, ask to create a new one + if not prevBackupsFound: + print2log("WARNING: No previous data for incremental backups were found!\n", logFile) + if ask_ok("Should a complete backup be performed? (y/n)"): + if not os.path.exists(current_backup_target): + os.mkdir(current_backup_target) + else: + # continue with next backup item + continue + + # assemble rsync commandline and run it + rsynccmd = 'rsync -aP ' + previous_backup_link + ' ' + backupDir + ' ' + os.path.join(current_backup_target,timestamp+"_tmp") + print2log("+" + countStr + "+ " + rsynccmd + "\n\n", logFile) + rsyncproc = subprocess.Popen(rsynccmd, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + + # read rsync output and print to console + while True: + next_line = rsyncproc.stdout.readline().decode("utf-8") + if not next_line: + break + print2log("+" + countStr + "+ " + next_line, logFile) + + # wait until process is really terminated + exitcode = rsyncproc.wait() + # check exit code + if exitcode==0: + os.rename(os.path.join(current_backup_target,timestamp+"_tmp"), os.path.join(current_backup_target,timestamp)) + print2log("done \n\n", logFile) + else: + print2log("\nWARNING: looks like some error occured :( \n\n", logFile) + break + +# close logfile +if (WRITE_LOGFILE==True) and logFile: + logFile.close()