Upgrades and tweaks
- Added "include" feature from rsync - Made some constants - When making a diff, the most recently created directory will be used as the link, to save time and space (both full and diff directories are considered now) -
This commit is contained in:
		
							
								
								
									
										203
									
								
								mikes-backup
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								mikes-backup
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ | |||||||
| # | # | ||||||
| import datetime | import datetime | ||||||
| import os | import os | ||||||
|  | import re | ||||||
| import shlex | import shlex | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
| @@ -19,7 +20,8 @@ class MikesBackup: | |||||||
| 	__remote_user = None | 	__remote_user = None | ||||||
| 	__destination_dir_base = None | 	__destination_dir_base = None | ||||||
| 	 | 	 | ||||||
| 	__source_dirs = [] | 	__source_dir = None | ||||||
|  | 	__source_dir_includes = [] | ||||||
| 	__source_dir_excludes = [] | 	__source_dir_excludes = [] | ||||||
| 	__source_mountpoint_demands = [] | 	__source_mountpoint_demands = [] | ||||||
| 	 | 	 | ||||||
| @@ -29,6 +31,9 @@ class MikesBackup: | |||||||
| 	__force_full = False | 	__force_full = False | ||||||
| 	__force_differential = False | 	__force_differential = False | ||||||
| 	 | 	 | ||||||
|  | 	CONST_FULL_DIRECTORY_NAME = "Full" | ||||||
|  | 	CONST_DIFFERENTIAL_DIRECTORY_NAME = "Differential" | ||||||
|  | 	 | ||||||
| 	# | 	# | ||||||
| 	def __init__(self): | 	def __init__(self): | ||||||
| 		 | 		 | ||||||
| @@ -44,7 +49,8 @@ class MikesBackup: | |||||||
| 		s += "\nRemote Host: " + str(self.__remote_host) | 		s += "\nRemote Host: " + str(self.__remote_host) | ||||||
| 		s += "\nRemote User: " + str(self.__remote_user) | 		s += "\nRemote User: " + str(self.__remote_user) | ||||||
| 		s += "\nDestination Dir Base: " + str(self.__destination_dir_base) | 		s += "\nDestination Dir Base: " + str(self.__destination_dir_base) | ||||||
| 		s += "\nSource Dirs: " + str(self.__source_dirs) | 		s += "\nSource Dir (Main): " + str(self.__source_dir) | ||||||
|  | 		s += "\nSource Dirs (Includes): " + str(self.__source_dir_includes) | ||||||
| 		s += "\nSource Dir Excludes: " + str(self.__source_dir_excludes) | 		s += "\nSource Dir Excludes: " + str(self.__source_dir_excludes) | ||||||
| 		s += "\nSource Mountpoint Demands: " + str(self.__source_mountpoint_demands) | 		s += "\nSource Mountpoint Demands: " + str(self.__source_mountpoint_demands) | ||||||
| 		s += "\nSSH Key: " + str(self.__ssh_key) | 		s += "\nSSH Key: " + str(self.__ssh_key) | ||||||
| @@ -105,13 +111,13 @@ class MikesBackup: | |||||||
| 				a = a + 1 | 				a = a + 1 | ||||||
| 			elif arg == "--source-dir": | 			elif arg == "--source-dir": | ||||||
| 				valid_arg = True | 				valid_arg = True | ||||||
| 				self.__source_dirs.append(sys.argv[a + 1]) | 				self.__source_dir = sys.argv[a + 1] | ||||||
| 				self.log("Found source dir: " + sys.argv[a + 1]) | 				self.log("Found source dir: " + sys.argv[a + 1]) | ||||||
| 				a = a + 1 | 				a = a + 1 | ||||||
| 			elif arg == "--exclude": | 			elif arg == "--include": | ||||||
| 				valid_arg = True | 				valid_arg = True | ||||||
| 				self.__source_dir_excludes.append(sys.argv[a + 1]) | 				self.__source_dir_includes.append(sys.argv[a + 1]) | ||||||
| 				self.log("Found exclude dir: " + sys.argv[a + 1]) | 				self.log("Found additional source dir include: " + sys.argv[a + 1]) | ||||||
| 				a = a + 1 | 				a = a + 1 | ||||||
| 			elif arg == "--source-mountpoint": | 			elif arg == "--source-mountpoint": | ||||||
| 				valid_arg = True | 				valid_arg = True | ||||||
| @@ -123,6 +129,11 @@ class MikesBackup: | |||||||
| 				self.__destination_dir_base = sys.argv[a + 1] | 				self.__destination_dir_base = sys.argv[a + 1] | ||||||
| 				self.log("Found destination dir: " + self.__destination_dir_base) | 				self.log("Found destination dir: " + self.__destination_dir_base) | ||||||
| 				a = a + 1 | 				a = a + 1 | ||||||
|  | 			elif arg == "--exclude": | ||||||
|  | 				valid_arg = True | ||||||
|  | 				self.__source_dir_excludes.append(sys.argv[a + 1]) | ||||||
|  | 				self.log("Found exclude dir: " + sys.argv[a + 1]) | ||||||
|  | 				a = a + 1 | ||||||
| 			elif arg == "--remote-host": | 			elif arg == "--remote-host": | ||||||
| 				valid_arg = True | 				valid_arg = True | ||||||
| 				self.__remote_host = sys.argv[a + 1] | 				self.__remote_host = sys.argv[a + 1] | ||||||
| @@ -186,6 +197,13 @@ class MikesBackup: | |||||||
| 			if self.__remote_user is None: | 			if self.__remote_user is None: | ||||||
| 				raise Exception("Please provide remote user") | 				raise Exception("Please provide remote user") | ||||||
| 	 | 	 | ||||||
|  | 	# | ||||||
|  | 	def demand_source_directory_config(self): | ||||||
|  | 		 | ||||||
|  | 		# | ||||||
|  | 		if self.__source_dir is None: | ||||||
|  | 			raise Exception("Please provide a source directory") | ||||||
|  | 	 | ||||||
| 	# | 	# | ||||||
| 	def demand_destination_directory_config(self): | 	def demand_destination_directory_config(self): | ||||||
| 		 | 		 | ||||||
| @@ -248,15 +266,6 @@ class MikesBackup: | |||||||
| 		self.log("Trying to determine if Full backup destination directory exists:", dir_path) | 		self.log("Trying to determine if Full backup destination directory exists:", dir_path) | ||||||
| 		return self.does_destination_directory_exist(dir_path) | 		return self.does_destination_directory_exist(dir_path) | ||||||
| 	 | 	 | ||||||
| 	# |  | ||||||
| 	def get_source_directories(self): |  | ||||||
| 		 |  | ||||||
| 		# |  | ||||||
| 		if len(self.__source_dirs) == 0: |  | ||||||
| 			raise Exception("No source directories specified") |  | ||||||
| 		 |  | ||||||
| 		return self.__source_dirs |  | ||||||
| 	 |  | ||||||
| 	# | 	# | ||||||
| 	def do_backup(self): | 	def do_backup(self): | ||||||
| 		 | 		 | ||||||
| @@ -270,8 +279,6 @@ class MikesBackup: | |||||||
| 		# Remote base dir must exist | 		# Remote base dir must exist | ||||||
| 		self.demand_destination_base_backup_directory() | 		self.demand_destination_base_backup_directory() | ||||||
| 		 | 		 | ||||||
| 		raise Exception("Just testing") |  | ||||||
| 		 |  | ||||||
| 		# Forced full or differential by args? | 		# Forced full or differential by args? | ||||||
| 		if self.__force_full is True or self.__force_differential is True: | 		if self.__force_full is True or self.__force_differential is True: | ||||||
| 			if self.__force_full is True: | 			if self.__force_full is True: | ||||||
| @@ -290,7 +297,7 @@ class MikesBackup: | |||||||
| 			self.log("Automatically choosing full backup, because full backup destination directory wasn't found") | 			self.log("Automatically choosing full backup, because full backup destination directory wasn't found") | ||||||
| 			self.do_full_backup() | 			self.do_full_backup() | ||||||
| 	 | 	 | ||||||
| 	# | 	# TODO: Full backups should clean out the differentials directory | ||||||
| 	def do_full_backup(self): | 	def do_full_backup(self): | ||||||
| 		 | 		 | ||||||
| 		# Start args | 		# Start args | ||||||
| @@ -299,8 +306,8 @@ class MikesBackup: | |||||||
| 		# Get destination directory | 		# Get destination directory | ||||||
| 		destination_dir = self.make_full_backup_destination_path() | 		destination_dir = self.make_full_backup_destination_path() | ||||||
| 		 | 		 | ||||||
| 		# Append source directories | 		# Append source directory | ||||||
| 		args.extend(self.get_source_directories()) | 		args.append(self.make_rsync_source_directory_part()) | ||||||
| 		 | 		 | ||||||
| 		# Append remote destination directory | 		# Append remote destination directory | ||||||
| 		# args.append( self.__remote_user + "@" + self.__remote_host + ":" + remote_dir) | 		# args.append( self.__remote_user + "@" + self.__remote_host + ":" + remote_dir) | ||||||
| @@ -318,24 +325,24 @@ class MikesBackup: | |||||||
| 		args = [] | 		args = [] | ||||||
| 		 | 		 | ||||||
| 		# Get directories | 		# Get directories | ||||||
| 		link_dest_dir = self.make_full_backup_destination_path() | 		link_dest_dir = self.determine_rsync_backup_link_destination_path() | ||||||
| 		destination_dir = self.make_remote_differential_backup_path() | 		destination_dir = self.make_remote_differential_backup_path() | ||||||
| 		self.ensure_destination_directory(destination_dir) | 		self.ensure_destination_directory(destination_dir) | ||||||
| 		 | 		 | ||||||
| 		# Add link dest arg | 		# Add link dest arg? | ||||||
| 		args.append("--link-dest") | 		if link_dest_dir: | ||||||
| 		args.append(link_dest_dir) | 			args.append("--link-dest") | ||||||
|  | 			args.append(link_dest_dir) | ||||||
| 		 | 		 | ||||||
| 		# Append source directories | 		# Append source directory | ||||||
| 		args.extend(self.get_source_directories()) | 		args.append(self.make_rsync_source_directory_part()) | ||||||
| 		 | 		 | ||||||
| 		# Append remote destination directory | 		# Append remote destination directory | ||||||
| 		# args.append( self.__remote_user + "@" + self.__remote_host + ":" + remote_dir) |  | ||||||
| 		args.append(self.make_rsync_remote_destination_part(destination_dir)) | 		args.append(self.make_rsync_remote_destination_part(destination_dir)) | ||||||
| 		 | 		 | ||||||
| 		# print("Args", str(args)) | 		# print("Args", str(args)) | ||||||
| 		self.log("Link destination dir:" + link_dest_dir) | 		self.log("Link destination dir: " + link_dest_dir) | ||||||
| 		self.log("Destination dir:" + destination_dir) | 		self.log("Destination dir: " + destination_dir) | ||||||
| 		 | 		 | ||||||
| 		self.execute_rsync(args) | 		self.execute_rsync(args) | ||||||
| 	 | 	 | ||||||
| @@ -369,17 +376,43 @@ class MikesBackup: | |||||||
| 			raise Exception("No remote directory was specified") | 			raise Exception("No remote directory was specified") | ||||||
| 		 | 		 | ||||||
| 		# | 		# | ||||||
| 		return os.path.join(self.__destination_dir_base, "Full") | 		return os.path.join(self.__destination_dir_base, self.CONST_FULL_DIRECTORY_NAME) | ||||||
| 	 | 	 | ||||||
| 	# | 	# | ||||||
| 	def make_remote_differential_backup_path(self): | 	def make_remote_differential_backup_path_base(self): | ||||||
| 		 | 		 | ||||||
| 		# | 		# | ||||||
| 		if self.__destination_dir_base is None: | 		if self.__destination_dir_base is None: | ||||||
| 			raise Exception("No remote directory was specified") | 			raise Exception("No remote directory was specified") | ||||||
| 		 | 		 | ||||||
|  | 		return os.path.join(self.__destination_dir_base, self.CONST_DIFFERENTIAL_DIRECTORY_NAME) | ||||||
|  | 	 | ||||||
|  | 	# | ||||||
|  | 	def make_remote_differential_backup_path(self): | ||||||
|  | 		 | ||||||
|  | 		diff_path_base = self.make_remote_differential_backup_path_base() | ||||||
|  | 		 | ||||||
| 		# | 		# | ||||||
| 		return os.path.join(self.__destination_dir_base, "Differential", self.get_datetime_for_filename()) | 		return os.path.join(diff_path_base, self.get_datetime_for_filename()) | ||||||
|  | 	 | ||||||
|  | 	# | ||||||
|  | 	def make_rsync_source_includes_part(self): | ||||||
|  | 		 | ||||||
|  | 		args = [] | ||||||
|  | 		 | ||||||
|  | 		for d in self.__source_dir_includes: | ||||||
|  | 			args.append("--include") | ||||||
|  | 			args.append(d) | ||||||
|  | 		 | ||||||
|  | 		return args | ||||||
|  | 	 | ||||||
|  | 	# | ||||||
|  | 	def make_rsync_source_directory_part(self): | ||||||
|  | 		 | ||||||
|  | 		self.demand_source_directory_config() | ||||||
|  | 		 | ||||||
|  | 		# | ||||||
|  | 		return self.__source_dir | ||||||
| 	 | 	 | ||||||
| 	# | 	# | ||||||
| 	def make_rsync_remote_destination_part(self, destination_dir): | 	def make_rsync_remote_destination_part(self, destination_dir): | ||||||
| @@ -398,6 +431,86 @@ class MikesBackup: | |||||||
| 		 | 		 | ||||||
| 		return part | 		return part | ||||||
| 	 | 	 | ||||||
|  | 	# | ||||||
|  | 	def determine_rsync_backup_link_destination_path(self): | ||||||
|  | 		 | ||||||
|  | 		self.demand_destination_directory_config() | ||||||
|  | 		 | ||||||
|  | 		self.log("Begin trying to determine which previous backup path to use as link") | ||||||
|  | 		 | ||||||
|  | 		# | ||||||
|  | 		newest_path = None | ||||||
|  | 		newest_path_date = None | ||||||
|  | 		 | ||||||
|  | 		# Pattern to parse the 'ls' command | ||||||
|  | 		pattern = re.compile( | ||||||
|  | 			""".*(?P<date>[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{9} .{5}) (?P<name>.+)$""", | ||||||
|  | 			re.MULTILINE | ||||||
|  | 		) | ||||||
|  | 		 | ||||||
|  | 		# Get listing info for the full path | ||||||
|  | 		destination_path_full = self.make_full_backup_destination_path() | ||||||
|  | 		return_code, stdout, stderr = self.execute_remote_ssh_command([ | ||||||
|  | 			"ls", | ||||||
|  | 			"-l", | ||||||
|  | 			"-c", | ||||||
|  | 			"--all", | ||||||
|  | 			"--full-time", | ||||||
|  | 			destination_path_full | ||||||
|  | 		]) | ||||||
|  | 		if return_code != 0: | ||||||
|  | 			raise Exception("Failed to get listing info for base destination directory") | ||||||
|  | 		for match in pattern.finditer(stdout): | ||||||
|  | 			 | ||||||
|  | 			name = match.group("name") | ||||||
|  | 			date = match.group("date") | ||||||
|  | 			if name == ".": | ||||||
|  | 				self.log("Start by assuming \"Full\" is the most recent backup: " + destination_path_full) | ||||||
|  | 				self.log("; With a date of " + date) | ||||||
|  | 				newest_path = destination_path_full | ||||||
|  | 				newest_path_date = date | ||||||
|  | 				break | ||||||
|  | 		if not newest_path: | ||||||
|  | 			self.log("Didn't find a \"Full\" backup on remote") | ||||||
|  | 		 | ||||||
|  | 		# TODO: Need to use a temp name for diff directories, so interrupted diffs don't get used | ||||||
|  | 		# TODO: Allow user to specify whether rsync success is only return 0, or also 23/24 (partial xfers) | ||||||
|  | 		# Get listing info for all differential directories | ||||||
|  | 		differential_path_base = self.make_remote_differential_backup_path_base() | ||||||
|  | 		return_code, stdout, stderr = self.execute_remote_ssh_command([ | ||||||
|  | 			"ls", | ||||||
|  | 			"-l", | ||||||
|  | 			"-c", | ||||||
|  | 			"--all", | ||||||
|  | 			"--full-time", | ||||||
|  | 			differential_path_base | ||||||
|  | 		]) | ||||||
|  | 		if return_code != 0: | ||||||
|  | 			raise Exception("Failed to get listing info for destination differential base directory") | ||||||
|  | 		 | ||||||
|  | 		# Look for the most recent differential directory | ||||||
|  | 		# (must be newer than the Full directory too) | ||||||
|  | 		for match in pattern.finditer(stdout): | ||||||
|  | 			 | ||||||
|  | 			name = match.group("name") | ||||||
|  | 			date = match.group("date") | ||||||
|  | 			 | ||||||
|  | 			if name == "." or name == "..": | ||||||
|  | 				continue | ||||||
|  | 			 | ||||||
|  | 			if newest_path is None or date > newest_path_date: | ||||||
|  | 				self.log("Found a newer differential backup: " + name + "; " + date) | ||||||
|  | 				newest_path = os.path.join(differential_path_base, name) | ||||||
|  | 				newest_path_date = date | ||||||
|  | 			else: | ||||||
|  | 				self.log("Not newer: " + name + "; " + date) | ||||||
|  | 		 | ||||||
|  | 		# | ||||||
|  | 		self.log("Newest backup path is: " + newest_path) | ||||||
|  | 		self.log("; With a date of: " + newest_path_date) | ||||||
|  | 		 | ||||||
|  | 		return newest_path | ||||||
|  | 	 | ||||||
| 	@staticmethod | 	@staticmethod | ||||||
| 	def ensure_local_directory(d): | 	def ensure_local_directory(d): | ||||||
| 		 | 		 | ||||||
| @@ -444,6 +557,11 @@ class MikesBackup: | |||||||
| 			"--delete-excluded" | 			"--delete-excluded" | ||||||
| 		] | 		] | ||||||
| 		 | 		 | ||||||
|  | 		# | ||||||
|  | 		for i in self.__source_dir_includes: | ||||||
|  | 			args.append("--include") | ||||||
|  | 			args.append(i) | ||||||
|  | 		 | ||||||
| 		# | 		# | ||||||
| 		for e in self.__source_dir_excludes: | 		for e in self.__source_dir_excludes: | ||||||
| 			args.append("--exclude") | 			args.append("--exclude") | ||||||
| @@ -504,12 +622,16 @@ class MikesBackup: | |||||||
| 			raise Exception("Unsupported command datatype") | 			raise Exception("Unsupported command datatype") | ||||||
| 		 | 		 | ||||||
| 		# Spawn SSH in shell | 		# Spawn SSH in shell | ||||||
| 		# process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 		process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||||||
| 		process = subprocess.Popen(args) | 		# process = subprocess.Popen(args) | ||||||
| 		# stdout, stderr = process.communicate() | 		stdout, stderr = process.communicate() | ||||||
| 		process.communicate() | 		 | ||||||
| 		stdout = "" | 		stdout = stdout.decode() | ||||||
| 		stderr = "" | 		stderr = stderr.decode() | ||||||
|  | 		 | ||||||
|  | 		# process.communicate() | ||||||
|  | 		# stdout = "" | ||||||
|  | 		# stderr = "" | ||||||
| 		# print(stderr.decode()) | 		# print(stderr.decode()) | ||||||
| 		 | 		 | ||||||
| 		return process.returncode, stdout, stderr | 		return process.returncode, stdout, stderr | ||||||
| @@ -518,6 +640,7 @@ class MikesBackup: | |||||||
| 	def execute_rsync(self, _args): | 	def execute_rsync(self, _args): | ||||||
| 		 | 		 | ||||||
| 		# Demand stuff | 		# Demand stuff | ||||||
|  | 		self.demand_source_directory_config() | ||||||
| 		self.demand_destination_directory_config() | 		self.demand_destination_directory_config() | ||||||
| 		if self.is_using_ssh(): | 		if self.is_using_ssh(): | ||||||
| 			self.demand_ssh_config() | 			self.demand_ssh_config() | ||||||
| @@ -530,6 +653,10 @@ class MikesBackup: | |||||||
| 		# | 		# | ||||||
| 		env = self.start_rsync_environment_variables() | 		env = self.start_rsync_environment_variables() | ||||||
| 		 | 		 | ||||||
|  | 		# | ||||||
|  | 		self.log("Executing rsync with the following arguments:", args) | ||||||
|  | 		self.log("; And the following environment:", env) | ||||||
|  | 		 | ||||||
| 		# | 		# | ||||||
| 		# print("Debug -> Want to execute Rsync") | 		# print("Debug -> Want to execute Rsync") | ||||||
| 		# print("Args:", str(args)) | 		# print("Args:", str(args)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user