Initial mysync implementation

This commit is contained in:
Dirk Alders 2020-01-26 17:13:41 +01:00
parent 727495386e
commit 81f7cf4b2e
3 changed files with 176 additions and 0 deletions

14
example/backup_config.py Normal file
View File

@ -0,0 +1,14 @@
import os
import sys
backup = True
backup_basepath = '/data/backup/ahorn'
installations = ['mint', 'kubuntu', 'openSuSE']
for installation in installations:
setattr(sys.modules[__name__], 'home_%s_remotepath' % installation, 'dirk@ahorn:/home')
setattr(sys.modules[__name__], 'home_%s_localpath' % installation, os.path.join(installation, 'home'))
user_data_remotepath = 'dirk@ahorn:/user_data'
user_data_localpath = 'user_data'
user_data_skip = ['static_data']

25
example/sync_config.py Normal file
View File

@ -0,0 +1,25 @@
import os
import sys
backup = False
hosts = ['ahorn', 'erle', 'linde']
__basepath__ = '/user_data'
entries = (
('dirk@%s:/user_data/bin', os.path.join(__basepath__, 'bin'), None),
('dirk@%s:/user_data/data', os.path.join(__basepath__, 'data'), None),
('dirk@%s:/user_data/static_data', os.path.join(__basepath__, 'static_data'), ['Audio', 'timeshift', 'lost+found']),
('dirk@%s:/home', '/home', None),
)
for host in hosts:
for remote, local, skip in entries:
setattr(sys.modules[__name__], host + '_' + os.path.basename(local) + '_remotepath', remote % host)
setattr(sys.modules[__name__], host + '_' + os.path.basename(local) + '_localpath', local)
if skip is not None:
setattr(sys.modules[__name__], host + '_' + os.path.basename(local) + '_skip', skip)
mount_mockery_audio_remotepath = 'root@mount-mockery.de:/data/audio/items'
mount_mockery_audio_localpath = os.path.join(__basepath__, 'static_data', 'dirk', 'Audio')

137
mysync Executable file
View File

@ -0,0 +1,137 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
import config
import os
import sys
import time
import subprocess
DEBUG = False
RSYNC_REMOTE_COMMAND = 'sudo rsync'
TIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
try:
basepath = config.backup_basepath
except AttributeError:
basepath = os.path.dirname(os.path.abspath(__file__))
timestamp = time.strftime(TIME_FORMAT)
output_lines = 5
PROP_REMOTE = 'remotepath'
PROP_LOCAL = 'localpath'
PROP_SKIP = 'skip'
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
CLEAR_REST_OF_LINE = '\033[K'
def status_line(line):
sys.stdout.write('\r .....' + line.replace('\n', '')[-60:] + CLEAR_REST_OF_LINE)
sys.stdout.flush()
def parse_config_to_dict():
d = {}
config_dict = dir(config)
for key in config_dict:
if key.endswith('_' + PROP_REMOTE):
entry_name = key[:-len(PROP_REMOTE) - 1]
if entry_name + '_' + PROP_LOCAL in config_dict:
d[entry_name] = {}
for key in config_dict:
if key.startswith(entry_name):
d[entry_name][key[key.rfind('_') + 1:]] = getattr(config, key)
return d
def rsync_command(**kwargs):
def path_filter(path):
return path if path.endswith('/') else path + '/'
#
if config.backup:
localpath = path_filter(os.path.join(basepath, timestamp, kwargs[PROP_LOCAL]))
if timestamp not in localpath:
sys.stderr.write('Unable to backup to %s. Please give a relative path for a backup!\n' % localpath)
localpath = None
else:
localpath = path_filter(os.path.join(basepath, kwargs.get('localpath', '')))
if localpath is not None:
remotepath = path_filter(kwargs.get(PROP_REMOTE))
skip = kwargs.get(PROP_SKIP)
cmd_l = ['rsync', '-avn' if DEBUG else '-av', '--delete', '--rsync-path="%s"' % RSYNC_REMOTE_COMMAND, remotepath, localpath]
if skip is not None:
cmd_l.append('--exclude=%s' % ','.join(skip))
if not os.path.exists(localpath):
return 'mkdir -p %s && ' % localpath + ' '.join(cmd_l)
return ' '.join(cmd_l)
def link_copy_command():
newest_backup = None
if not os.path.exists(basepath):
os.system('mkdir -p ' + basepath)
for path in os.listdir(basepath):
try:
t = time.strptime(path, TIME_FORMAT)
if newest_backup is None or t > newest_backup:
newest_backup = t
except ValueError:
pass # seems to be not one of my backups
if newest_backup is not None:
newest_backup = os.path.join(basepath, time.strftime(TIME_FORMAT, newest_backup))
return 'cp -alv ' + newest_backup + ' ' + os.path.join(basepath, timestamp)
def help_msg():
backup_dict = parse_config_to_dict()
print("Possible arguments are:")
for entry in backup_dict:
print(" * %s (%s -> %s)" % (entry + (20 - len(entry)) * ' ', backup_dict[entry][PROP_REMOTE] + (40 - len(backup_dict[entry][PROP_REMOTE])) * ' ', backup_dict[entry][PROP_LOCAL] + (40 - len(backup_dict[entry][PROP_LOCAL])) * ' '))
def make_link_copy():
cmd = link_copy_command()
if cmd is not None:
sys.stdout.write(BOLD + HEADER + UNDERLINE + 'Creating Link copy of last backup.' + ENDC + '\n\n')
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
err = b""
for line in p.stdout:
status_line(line.decode('utf-8'))
err += p.stderr.read()
sys.stdout.write('\n' + FAIL + BOLD + err.decode('utf-8') + ENDC)
sys.stdout.write('\n' + 80 * '-' + '\n')
#
if __name__ == '__main__':
backup_dict = parse_config_to_dict()
#
backup_dict = parse_config_to_dict()
did_nothing = True
for entry in backup_dict:
if entry in sys.argv:
cmd = rsync_command(**backup_dict[entry])
if cmd is not None:
if config.backup and did_nothing:
make_link_copy()
did_nothing = False
sys.stdout.write(BOLD + HEADER + UNDERLINE + 'Doing %s for %s:' % ('backup' if config.backup else 'sync', repr(entry)) + ENDC + '\n\n')
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
err = b""
for line in p.stdout:
status_line(line.decode('utf-8'))
err += p.stderr.read()
sys.stdout.write('\n' + FAIL + BOLD + err.decode('utf-8') + ENDC)
sys.stdout.write('\n' + 80 * '-' + '\n')
if did_nothing:
help_msg()
else:
help_msg()