-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackupV2
executable file
·128 lines (112 loc) · 5.27 KB
/
backupV2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
import subprocess
import argparse
import sys
import os
import socket
# TODO: move targets list to config file
# List of tuples of form
# ("laptop directory to back up", [HDD LOCATION]"/HDD backup file")
targets_list = [("~/Files/", "/Files/"),
("~/Pictures/", "/Pictures/")]
def check_continue(question_prompt, default=True):
"""
Display a yes/no prompt to stdout, of form (question) [y/n].
Return true if yes, false if no, or default if only [return] is entered.
Only consider the first letter of the response when deciding if it's y or n.
"""
letter_prompt = "[Y/n]" if default else "[y/N]"
while True:
selection = input(question_prompt + ' ' + letter_prompt).lower()
if selection == "":
return default
if selection[0] in ["y", "n"]:
return True if selection[0]=="y" else False
print("Invalid response. Please write y/n")
def parse_config(filename):
"""
Split lines from the config file defined by 'filename' into
a dictionary. Return that dictionary.
"""
# TODO add robustness against no config file
# TODO check config file contains correct fields / correct formatting
# TODO deal with above issues as appropriate (exceptions?)
# TODO reconfigure config file to have different sections, eg [REMOTE]
# for setting up remote backup, [TARGETS] for setting up target list,
# [MOUNT] for setting up local mount point, etc.
# TODO build more advanced parser to deal with the more advanced configs
# TODO more advanced return, to indicate whether remote and local are
# adequately defined in the config file
with open(filename) as f:
lines = f.readlines()
ret = {}
for line in lines:
i,j = line.split()
ret[i.lower()]=j
return ret
def get_mount_point():
"""
Find the mount point of the portable hard drive.
Return it.
"""
# TODO: remove hard-coding of backup filename, move to config file
response = subprocess.run('mount', stdout=subprocess.PIPE)
lines = response.stdout.decode('utf-8').split('\n')
mounts = [line for line in lines if 'BACKUP' in line]
return None if len(mounts)==0 else mounts[0].split(' ')[2]
def run_rsync(source,target, proc_args="-avh"):
"""
Run rsync, from source to target, using the given process args.
Return the return code.
"""
proc_command = "rsync {0} {1} {2}".format(proc_args, source, target)
print(proc_command)
proc = subprocess.Popen(proc_command, stdout=subprocess.PIPE, shell=True)
while proc.poll() is None:
line = proc.stdout.readline()
print(line.decode('utf-8'), end='')
print(proc.stdout.read())
return proc.poll()
def test_connection(remotehost_ip, port, timeout=10):
"""
Try connecting to the remote host on the given port.
Return true if a connection can be established, false otherwise.
"""
try:
socket.create_connection((remotehost_ip, port), timeout=timeout)
return True
except OSError:
pass
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run rsync backup job, TODO")
parser.add_argument("-r", "--remote", help="Backup to the system defined in ~/.backup.conf", default=False, action="store_true")
parser.add_argument("-f", "--force", help="Force rsync to run; don't query before doing so", default=False, action="store_true")
parser.add_argument("-n", "--dryrun", help="Dry run, don't actually send any files", default=False, action="store_true")
parser.add_argument("-d", "--delete", help="When backing up, delete any files from the backup that have been deleted on the source",
default=False, action="store_true")
args = parser.parse_args()
proc_args = "-avhn" if args.dryrun else "-avh"
proc_args += " --delete" if args.delete else ""
if args.remote:
# Set up for running a remote backup
# TODO: check if config file exists; if it doesn't, display some sort of warning or promt, or create it from some sort of blank template
# TODO: load config file regardless of whether or not we're remote'd, to load drive name, targets list, etc.
config_path = os.path.expanduser('~/.backup.conf')
config_file = parse_config(config_path)
if not test_connection(config_file["ip"], config_file["port"]):
sys.exit("Timeout exceeded when trying to establish a connection to remote host. Exiting.")
mount_directory = config_file["mountpoint"]
mount_directory = mount_directory[:-1] if mount_directory[-1]=='/' else mount_directory
proc_args += " -e \"ssh -p {0} -i ~/.ssh/id_ed25519\"".format(config_file["port"])
mount_point = config_file["user"] + '@' + config_file["ip"] + ':' + mount_directory
else:
# Set up for running a local backup
mount_point = get_mount_point()
if mount_point is None:
sys.exit("No portable hard drive found. Exiting.")
for source,dest in targets_list:
# Run it!
proc_command = "rsync {0} {1} {2}".format(proc_args, source, mount_point+dest)
if args.force or check_continue("About to run {0}; continue?".format(proc_command)):
run_rsync(source, mount_point+dest, proc_args)