#!/usr/bin/env python3 # import datetime import os import shlex import subprocess import sys # class MikesBackup: # _log_dir = None _remote_host = None _remote_user = None _destination_dir_base = None # _ssh_key = None _quiet_ssh = True # _source_dirs = [] _source_dir_excludes = [] # _force_full = False _force_differential = False # def __init__(self): self.ParseArgs() # def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) # def log(): pass # def ParseArgs(self): # Simple mapped args args_map = { "--log-dir" : "_log_dir", "--remote-host" : "_remote_host", "--remote-user" : "_remote_user", "--destination-dir" : "_destination_dir_base", "--ssh-key" : "_ssh_key" } # print() print ("Parsing arguments") a = 1 while a < len(sys.argv): # arg = sys.argv[a] # valid_arg = False for arg_name in args_map: if arg == arg_name: valid_arg = True self_var_name = args_map[arg_name] self_var_value = sys.argv[ a + 1 ] self.__dict__[self_var_name] = self_var_value print("Found argument \"", arg_name, "\" ==>", self_var_value) a = a + 1 break if arg == "": valid_arg = True elif arg == "--full": valid_arg = True self._force_full = True elif arg == "--diff" or arg == "--differential": valid_arg = True self._force_differential = True elif arg == "--source-dir": valid_arg = True self._source_dirs.append( sys.argv[ a + 1 ] ) print("Found source dir:", sys.argv[ a + 1 ]) a = a + 1 elif arg == "--exclude": valid_arg = True self._source_dir_excludes.append( sys.argv[ a + 1 ] ) print("Found exclude dir:", sys.argv[ a + 1 ]) a = a + 1 a = a + 1 # if not valid_arg: raise Exception("Invalid argument:", arg); # def GetDatetimeForFilename(self): # return datetime.datetime.now().strftime('%Y-%b-%d_%I%M%p') # def IsUsingSSH(self): # if self._remote_host != None or self._remote_user != None or self._ssh_key != None: return True return False # def DemandSSHConfig(self): # if self.IsUsingSSH(): if self._remote_host == None: raise Exception("Please provide remote host") if self._remote_user == None: raise Exception("Please provide remote user") # def DemandDestinationDirectoryConfig(self): # if self._destination_dir_base == None: raise Exception("Please provide backup destination directory") # def DoesDestinationDirectoryExist(self, destination_path): # print("Trying to determine if destination path exists:", destination_path) # Local? if not self.IsUsingSSH(): print("Checking for local destination path") if os.path.isdir(destination_path): print("Local destination path exists") return True else: print("Local destination path does not exist") return False # print("Checking for remote destination path") command = [ "[ -d " + destination_path + " ]" ] # code, stdout, stderr = self.ExecuteRemoteSSHCommand(command) if code == 0: print("Remote destination dir was found: " + destination_path) return True # print("Remote dir didn't seem to exist: " + destination_path) return False # def DemandDestinationBaseBackupDirectory(self): # self.DemandDestinationDirectoryConfig() # destination_path = self._destination_dir_base # if self.DoesDestinationDirectoryExist(destination_path) == False: raise Exception("Backup destination directory doesn't exist: " + destination_path) # def DoesFullBackupDestinationDirectoryExist(self): # dir_path = self.MakeFullBackupDestinationPath() # print("Trying to determine if Full backup destination directory exists:", dir_path) return self.DoesDestinationDirectoryExist(dir_path) # def GetSourceDirectories(self): # if len(self._source_dirs) == 0: raise Exception("No source directories specified") return self._source_dirs # def DoBackup(self): # print() print("Enter: DoBackup") # Remote base dir must exist self.DemandDestinationBaseBackupDirectory() # Forced full or differential by args? if self._force_full == True or self._force_differential == True: if self._force_full == True: print("Forcing full backup") self.DoFullBackup() else: print("Forcing differential backup") self.DoDifferentialBackup() return # Automatically choose full or differential if self.DoesFullBackupDestinationDirectoryExist(): print("Automatically choosing differential backup, because full backup destination directory already exists") self.DoDifferentialBackup() else: print("Automatically choosing full backup, because full backup destination directory wasn't found") self.DoFullBackup() # def DoFullBackup(self): # Start args args = [] # Get destination directory destination_dir = self.MakeFullBackupDestinationPath() # Append source directories args.extend(self.GetSourceDirectories()) # Append remote destination directory #args.append( self._remote_user + "@" + self._remote_host + ":" + remote_dir) args.append( self.MakeRsyncRemoteDestinationPart(destination_dir) ) #print("Args", str(args)) print("Destination dir:", destination_dir) self.ExecuteRsync(args) # def DoDifferentialBackup(self): # Start args args = [] # Get directories link_dest_dir = self.MakeFullBackupDestinationPath() destination_dir = self.MakeRemoteDifferentialBackupPath() self.EnsureDestinationDirectory(destination_dir) # Add link dest arg args.append("--link-dest") args.append(link_dest_dir) # Append source directories args.extend(self.GetSourceDirectories()) # Append remote destination directory #args.append( self._remote_user + "@" + self._remote_host + ":" + remote_dir) args.append( self.MakeRsyncRemoteDestinationPart(destination_dir) ) #print("Args", str(args)) print("Link destination dir:", link_dest_dir) print("Destination dir:", destination_dir) self.ExecuteRsync(args) # def MakeLogDirectoryPath(self): # log_dir = self._log_dir if log_dir == None: print("No log directory specified; Defaulting to current working directory") log_dir = "." return log_dir # def MakeLogPath(self): # Log dir log_dir = self.MakeLogDirectoryPath() # Path log_path = os.path.join(log_dir, self.GetDatetimeForFilename() + ".log") return log_path # def MakeFullBackupDestinationPath(self): # if self._destination_dir_base == None: raise Exception("No remote directory was specified") # return os.path.join(self._destination_dir_base, "Full") # def MakeRemoteDifferentialBackupPath(self): # if self._destination_dir_base == None: raise Exception("No remote directory was specified") # return os.path.join(self._destination_dir_base, "Differential", self.GetDatetimeForFilename()) # def MakeRsyncRemoteDestinationPart(self, destination_dir): # part = "" # if self._remote_host != None: if self._remote_user != None: part += self._remote_user + "@" part += self._remote_host + ":" # part += destination_dir return part # def EnsureLocalDirectory(self, d): # if not os.path.exists(d): os.makedirs(d) # def EnsureDestinationDirectory(self, d): # if not self.DoesDestinationDirectoryExist(d): # print("Destination directory doesn't exist; Will create:", d) # if self.IsUsingSSH(): command = [ "mkdir", "--parents", d ] self.ExecuteRemoteSSHCommand(command) else: os.makedirs(d) # def StartRsyncArgs(self): # self.EnsureLocalDirectory(self.MakeLogDirectoryPath()) args = [ "rsync", "--log-file", self.MakeLogPath(), "--archive", "--compress", "--progress", "--stats", "--human-readable", "--itemize-changes", "--one-file-system", "--delete", "--delete-excluded" ] # for e in self._source_dir_excludes: args.append("--exclude") args.append(e) # #args.append("--dry-run") # DEBUG !!! return args # def StartRsyncEnvironmentalVariables(self): # env = {} # if self._ssh_key != None or self._quiet_ssh == True: env["RSYNC_RSH"] = "ssh" if self._ssh_key != None: env["RSYNC_RSH"] += " -i " + shlex.quote(self._ssh_key) if self._quiet_ssh == True: env["RSYNC_RSH"] += " -q" return env # def ExecuteRemoteSSHCommand(self, command): # self.DemandSSHConfig() # args = [] # ssh command args.append("ssh") # Quiet? if self._quiet_ssh == True: args.append("-q") # ssh key if self._ssh_key != None: args.append("-i") args.append(self._ssh_key) # ssh user@host args.append(self._remote_user + "@" + self._remote_host) # Append the command args.append("--") if isinstance(command, str): args.append(command) elif isinstance(command, list): args.extend(command) else: raise Exception("Unsupported command datatype") # Spawn SSH in shell #process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process = subprocess.Popen(args) #stdout, stderr = process.communicate() process.communicate() stdout = "" stderr = "" #print (stderr.decode()) return (process.returncode, stdout, stderr) # def ExecuteRsync(self, _args): # Demand stuff self.DemandDestinationDirectoryConfig() if self.IsUsingSSH(): self.DemandSSHConfig() # args = self.StartRsyncArgs() args.extend(_args) #print(str(args)) # env = self.StartRsyncEnvironmentalVariables() # #print("Debug -> Want to execute Rsync") #print("Args:", str(args)) #print("Env:", str(env)) #return (0, "", "") # Spawn Rsync in shell #process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) process = subprocess.Popen(args, env=env) #stdout, stderr = process.communicate() process.communicate() #self.eprint(stderr.decode()) stdout = "" stderr = "" return (process.returncode, stdout, stderr) # if __name__ == "__main__": # b = MikesBackup() b.DoBackup()