mikes-backup/mikes-backup

493 lines
10 KiB
Plaintext
Raw Normal View History

2018-09-01 05:06:13 -07:00
#!/usr/bin/env python3
2019-08-04 18:04:45 -07:00
2018-09-01 05:06:13 -07:00
#
import datetime
import os
import shlex
import subprocess
import sys
2019-08-04 18:04:45 -07:00
2018-09-01 05:06:13 -07:00
#
class MikesBackup:
#
2019-08-04 18:04:45 -07:00
__log_dir = None
__remote_host = None
__remote_user = None
__destination_dir_base = None
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
__ssh_key = None
__quiet_ssh = True
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
__source_dirs = []
__source_dir_excludes = []
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
__force_full = False
__force_differential = False
2018-09-01 05:06:13 -07:00
#
def __init__(self):
2019-08-04 18:04:45 -07:00
self.parse_args()
2018-09-01 05:06:13 -07:00
#
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
#
2019-08-04 18:04:45 -07:00
def parse_args(self):
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Simple mapped args
2018-09-01 05:06:13 -07:00
args_map = {
2019-08-04 18:04:45 -07:00
"--log-dir": "__log_dir",
"--remote-host": "__remote_host",
"--remote-user": "__remote_user",
"--destination-dir": "__destination_dir_base",
"--ssh-key": "__ssh_key"
2018-09-01 05:06:13 -07:00
}
#
print()
2019-08-04 18:04:45 -07:00
print("Parsing arguments")
2018-09-01 05:06:13 -07:00
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]
2019-08-04 18:04:45 -07:00
self_var_value = sys.argv[a + 1]
2018-09-01 05:06:13 -07:00
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
2019-08-04 18:04:45 -07:00
self.__force_full = True
2018-09-01 05:06:13 -07:00
elif arg == "--diff" or arg == "--differential":
valid_arg = True
2019-08-04 18:04:45 -07:00
self.__force_differential = True
elif arg == "--source-dir":
valid_arg = True
2019-08-04 18:04:45 -07:00
self.__source_dirs.append(sys.argv[a + 1])
print("Found source dir:", sys.argv[a + 1])
a = a + 1
2018-09-01 05:06:13 -07:00
elif arg == "--exclude":
valid_arg = True
2019-08-04 18:04:45 -07:00
self.__source_dir_excludes.append(sys.argv[a + 1])
print("Found exclude dir:", sys.argv[a + 1])
2018-09-01 05:06:13 -07:00
a = a + 1
a = a + 1
#
if not valid_arg:
2019-08-04 18:04:45 -07:00
raise Exception("Invalid argument:", arg)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
@staticmethod
def get_datetime_for_filename():
2018-09-01 05:06:13 -07:00
#
return datetime.datetime.now().strftime('%Y-%b-%d_%I%M%p')
#
2019-08-04 18:04:45 -07:00
def is_using_ssh(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if (
self.__remote_host is not None
or self.__remote_user is not None
or self.__ssh_key is not None
):
return True
return False
#
2019-08-04 18:04:45 -07:00
def demand_ssh_config(self):
#
2019-08-04 18:04:45 -07:00
if self.is_using_ssh():
if self.__remote_host is None:
raise Exception("Please provide remote host")
2019-08-04 18:04:45 -07:00
if self.__remote_user is None:
raise Exception("Please provide remote user")
#
2019-08-04 18:04:45 -07:00
def demand_destination_directory_config(self):
#
2019-08-04 18:04:45 -07:00
if self.__destination_dir_base is None:
raise Exception("Please provide backup destination directory")
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def does_destination_directory_exist(self, destination_path):
#
print("Trying to determine if destination path exists:", destination_path)
2019-08-04 18:04:45 -07:00
# Local?
if not self.is_using_ssh():
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
2018-09-01 05:06:13 -07:00
#
print("Checking for remote destination path")
2018-09-01 05:06:13 -07:00
command = [
"[ -d " + destination_path + " ]"
2018-09-01 05:06:13 -07:00
]
#
2019-08-04 18:04:45 -07:00
code, stdout, stderr = self.execute_remote_ssh_command(command)
2018-09-01 05:06:13 -07:00
if code == 0:
print("Remote destination dir was found: " + destination_path)
2018-09-01 05:06:13 -07:00
return True
#
print("Remote dir didn't seem to exist: " + destination_path)
2018-09-01 05:06:13 -07:00
return False
#
2019-08-04 18:04:45 -07:00
def demand_destination_base_backup_directory(self):
#
2019-08-04 18:04:45 -07:00
self.demand_destination_directory_config()
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
destination_path = self.__destination_dir_base
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if self.does_destination_directory_exist(destination_path) is False:
raise Exception("Backup destination directory doesn't exist: " + destination_path)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def does_full_backup_destination_directory_exist(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
dir_path = self.make_full_backup_destination_path()
2018-09-01 05:06:13 -07:00
#
print("Trying to determine if Full backup destination directory exists:", dir_path)
2019-08-04 18:04:45 -07:00
return self.does_destination_directory_exist(dir_path)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def get_source_directories(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if len(self.__source_dirs) == 0:
raise Exception("No source directories specified")
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
return self.__source_dirs
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def do_backup(self):
2018-09-01 05:06:13 -07:00
#
print()
2019-08-04 18:04:45 -07:00
print("Enter: do_backup")
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Remote base dir must exist
self.demand_destination_base_backup_directory()
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Forced full or differential by args?
if self.__force_full is True or self.__force_differential is True:
if self.__force_full is True:
2018-09-01 05:06:13 -07:00
print("Forcing full backup")
2019-08-04 18:04:45 -07:00
self.do_full_backup()
2018-09-01 05:06:13 -07:00
else:
print("Forcing differential backup")
2019-08-04 18:04:45 -07:00
self.do_differential_backup()
2018-09-01 05:06:13 -07:00
return
2019-08-04 18:04:45 -07:00
# Automatically choose full or differential
if self.does_full_backup_destination_directory_exist():
print("Automatically choosing differential backup, because full backup destination directory already exists")
2019-08-04 18:04:45 -07:00
self.do_differential_backup()
2018-09-01 05:06:13 -07:00
else:
print("Automatically choosing full backup, because full backup destination directory wasn't found")
2019-08-04 18:04:45 -07:00
self.do_full_backup()
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def do_full_backup(self):
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Start args
2018-09-01 05:06:13 -07:00
args = []
2019-08-04 18:04:45 -07:00
# Get destination directory
destination_dir = self.make_full_backup_destination_path()
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Append source directories
args.extend(self.get_source_directories())
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Append remote destination directory
# args.append( self.__remote_user + "@" + self.__remote_host + ":" + remote_dir)
args.append(self.make_rsync_remote_destination_part(destination_dir))
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# print("Args", str(args))
print("Destination dir:", destination_dir)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
self.execute_rsync(args)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def do_differential_backup(self):
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Start args
2018-09-01 05:06:13 -07:00
args = []
2019-08-04 18:04:45 -07:00
# Get directories
link_dest_dir = self.make_full_backup_destination_path()
destination_dir = self.make_remote_differential_backup_path()
self.ensure_destination_directory(destination_dir)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Add link dest arg
2018-09-01 05:06:13 -07:00
args.append("--link-dest")
args.append(link_dest_dir)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Append source directories
args.extend(self.get_source_directories())
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Append remote destination directory
# args.append( self.__remote_user + "@" + self.__remote_host + ":" + remote_dir)
args.append(self.make_rsync_remote_destination_part(destination_dir))
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# print("Args", str(args))
print("Link destination dir:", link_dest_dir)
print("Destination dir:", destination_dir)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
self.execute_rsync(args)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def make_log_directory_path(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
log_dir = self.__log_dir
if log_dir is None:
2018-09-01 05:06:13 -07:00
print("No log directory specified; Defaulting to current working directory")
log_dir = "."
return log_dir
#
2019-08-04 18:04:45 -07:00
def make_log_path(self):
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Log dir
log_dir = self.make_log_directory_path()
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Path
log_path = os.path.join(log_dir, self.get_datetime_for_filename() + ".log")
2018-09-01 05:06:13 -07:00
return log_path
#
2019-08-04 18:04:45 -07:00
def make_full_backup_destination_path(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if self.__destination_dir_base is None:
2018-09-01 05:06:13 -07:00
raise Exception("No remote directory was specified")
#
2019-08-04 18:04:45 -07:00
return os.path.join(self.__destination_dir_base, "Full")
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def make_remote_differential_backup_path(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if self.__destination_dir_base is None:
2018-09-01 05:06:13 -07:00
raise Exception("No remote directory was specified")
#
2019-08-04 18:04:45 -07:00
return os.path.join(self.__destination_dir_base, "Differential", self.get_datetime_for_filename())
#
2019-08-04 18:04:45 -07:00
def make_rsync_remote_destination_part(self, destination_dir):
#
part = ""
#
2019-08-04 18:04:45 -07:00
if self.__remote_host is not None:
if self.__remote_user is not None:
part += self.__remote_user + "@"
part += self.__remote_host + ":"
#
part += destination_dir
return part
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
@staticmethod
def ensure_local_directory(d):
2018-09-01 05:06:13 -07:00
#
if not os.path.exists(d):
os.makedirs(d)
#
2019-08-04 18:04:45 -07:00
def ensure_destination_directory(self, d):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if not self.does_destination_directory_exist(d):
#
print("Destination directory doesn't exist; Will create:", d)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
if self.is_using_ssh():
command = [
"mkdir",
"--parents",
d
]
2019-08-04 18:04:45 -07:00
self.execute_remote_ssh_command(command)
else:
os.makedirs(d)
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def start_rsync_args(self):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
self.ensure_local_directory(self.make_log_directory_path())
2018-09-01 05:06:13 -07:00
args = [
"rsync",
2019-08-04 18:04:45 -07:00
"--log-file", self.make_log_path(),
2018-09-01 05:06:13 -07:00
"--archive",
"--compress",
"--progress",
"--stats",
"--human-readable",
"--itemize-changes",
"--one-file-system",
"--delete",
"--delete-excluded"
]
#
2019-08-04 18:04:45 -07:00
for e in self.__source_dir_excludes:
2018-09-01 05:06:13 -07:00
args.append("--exclude")
args.append(e)
#
2019-08-04 18:04:45 -07:00
# args.append("--dry-run") # DEBUG !!!
2018-09-01 05:06:13 -07:00
return args
#
2019-08-04 18:04:45 -07:00
def start_rsync_environment_variables(self):
2018-09-01 05:06:13 -07:00
#
env = {}
#
2019-08-04 18:04:45 -07:00
if self.__ssh_key is not None or self.__quiet_ssh is True:
2018-09-01 05:06:13 -07:00
env["RSYNC_RSH"] = "ssh"
2019-08-04 18:04:45 -07:00
if self.__ssh_key is not None:
env["RSYNC_RSH"] += " -i " + shlex.quote(self.__ssh_key)
if self.__quiet_ssh is True:
2018-09-01 05:06:13 -07:00
env["RSYNC_RSH"] += " -q"
return env
#
2019-08-04 18:04:45 -07:00
def execute_remote_ssh_command(self, command):
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
self.demand_ssh_config()
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
args = list()
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# ssh command
2018-09-01 05:06:13 -07:00
args.append("ssh")
2019-08-04 18:04:45 -07:00
# Quiet?
if self.__quiet_ssh is True:
2018-09-01 05:06:13 -07:00
args.append("-q")
2019-08-04 18:04:45 -07:00
# ssh key
if self.__ssh_key is not None:
2018-09-01 05:06:13 -07:00
args.append("-i")
2019-08-04 18:04:45 -07:00
args.append(self.__ssh_key)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# ssh user@host
args.append(self.__remote_user + "@" + self.__remote_host)
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Append the command
2018-09-01 05:06:13 -07:00
args.append("--")
if isinstance(command, str):
args.append(command)
elif isinstance(command, list):
args.extend(command)
else:
raise Exception("Unsupported command datatype")
2019-08-04 18:04:45 -07:00
# Spawn SSH in shell
# process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2018-09-01 05:06:13 -07:00
process = subprocess.Popen(args)
2019-08-04 18:04:45 -07:00
# stdout, stderr = process.communicate()
2018-09-01 05:06:13 -07:00
process.communicate()
stdout = ""
stderr = ""
2019-08-04 18:04:45 -07:00
# print(stderr.decode())
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
return process.returncode, stdout, stderr
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
def execute_rsync(self, _args):
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
# Demand stuff
self.demand_destination_directory_config()
if self.is_using_ssh():
self.demand_ssh_config()
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
args = self.start_rsync_args()
2018-09-01 05:06:13 -07:00
args.extend(_args)
2019-08-04 18:04:45 -07:00
# print(str(args))
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
env = self.start_rsync_environment_variables()
2018-09-01 05:06:13 -07:00
#
2019-08-04 18:04:45 -07:00
# print("Debug -> Want to execute Rsync")
# print("Args:", str(args))
# print("Env:", str(env))
# return (0, "", "")
2019-08-04 18:04:45 -07:00
# Spawn Rsync in shell
# process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
2018-09-01 05:06:13 -07:00
process = subprocess.Popen(args, env=env)
2019-08-04 18:04:45 -07:00
# stdout, stderr = process.communicate()
2018-09-01 05:06:13 -07:00
process.communicate()
2019-08-04 18:04:45 -07:00
# self.eprint(stderr.decode())
2018-09-01 05:06:13 -07:00
stdout = ""
stderr = ""
2019-08-04 18:04:45 -07:00
return process.returncode, stdout, stderr
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
def main():
2018-09-01 05:06:13 -07:00
b = MikesBackup()
2019-08-04 18:04:45 -07:00
b.do_backup()
2018-09-01 05:06:13 -07:00
2019-08-04 18:04:45 -07:00
#
if __name__ == "__main__":
#
main()