155 lines
2.9 KiB
Python
Executable file
155 lines
2.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import subprocess, sys, time, traceback
|
|
from configparser import ConfigParser
|
|
from getpass import getpass, getuser
|
|
from pathlib import Path
|
|
|
|
|
|
exit=False
|
|
sudo = {}
|
|
procs = []
|
|
|
|
|
|
def Rsync(src, dest, archive=False, bwlimit=None, exclude=[], user=None):
|
|
cmd = []
|
|
current_user = getuser()
|
|
|
|
if user and user != current_user:
|
|
cmd.extend(['sudo', '-Su', user])
|
|
|
|
if not sudo.get(user):
|
|
sudo[user] = getpass(f'[pysudo] password for {current_user}: ')
|
|
|
|
cmd.append('rsync')
|
|
cmd.append('--partial')
|
|
cmd.append('--progress')
|
|
cmd.append('--numeric-ids')
|
|
|
|
if archive:
|
|
cmd.append('-aAXhx')
|
|
cmd.append('--delete')
|
|
|
|
if bwlimit:
|
|
cmd.append(f'--bwlimit={int(bwlimit)*1000}')
|
|
|
|
if type(exclude) == str:
|
|
exclude = [exclude]
|
|
|
|
for line in exclude:
|
|
cmd.append('--exclude')
|
|
cmd.append(line)
|
|
|
|
for line in [src, dest]:
|
|
if not line.endswith('/'):
|
|
line += '/'
|
|
|
|
cmd.append(line)
|
|
|
|
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
|
|
|
|
if user and user != current_user:
|
|
password = sudo[user] + '\r'
|
|
proc.communicate(input=password.encode())
|
|
|
|
return proc
|
|
|
|
|
|
def load_config(path):
|
|
path = Path(path)
|
|
new_config = {}
|
|
config = ConfigParser()
|
|
config.read(path)
|
|
|
|
if not path.is_file():
|
|
print('Config file not found at', path)
|
|
return
|
|
|
|
config = dict(config)
|
|
|
|
if not config.get('DEFAULT'):
|
|
print('Missing "DEFAULT" section in config')
|
|
return
|
|
|
|
if not config['DEFAULT'].get('backup_dir'):
|
|
print('Missing "backup_dir" setting')
|
|
return
|
|
|
|
for category, var in config.items():
|
|
if category == 'DEFAULT':
|
|
new_config['backup_dir'] = var['backup_dir']
|
|
continue
|
|
|
|
new_config[category] = {}
|
|
|
|
for k,v in var.items():
|
|
new_config[category][k] = v
|
|
|
|
return new_config
|
|
|
|
|
|
def main(config):
|
|
global exit
|
|
global procs
|
|
|
|
backup_dir = Path(config.get('backup_dir'))
|
|
|
|
try:
|
|
for name, data in config.items():
|
|
if name == 'backup_dir':
|
|
continue
|
|
|
|
exclude = data.get('exclude', [])
|
|
user = data.get('user')
|
|
bwlimit = data.get('bwlimit')
|
|
|
|
if not data.get('src'):
|
|
print(f'Missing source directory for {name}')
|
|
continue
|
|
|
|
data['dest'] = str(backup_dir.joinpath(name))
|
|
|
|
for path in ['src', 'dest']:
|
|
if not data[path].endswith('/'):
|
|
data[path] += '/'
|
|
|
|
src = data.get('src')
|
|
dest = data.get('dest')
|
|
|
|
print(f'Running backup for {name}')
|
|
proc = Rsync(src, dest, archive=True, exclude=exclude, bwlimit=bwlimit, user=user)
|
|
|
|
if not proc:
|
|
print(f'Failed to run backup for {name}')
|
|
|
|
else:
|
|
procs.append(proc)
|
|
proc.wait()
|
|
|
|
if exit:
|
|
break
|
|
|
|
except KeyboardInterrupt:
|
|
print('Bye!')
|
|
|
|
for proc in procs:
|
|
if proc.poll() == None:
|
|
print('Terminating proc:', ' '.join(proc.args))
|
|
proc.terminate()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = sys.argv[1:]
|
|
configfile = Path.home().joinpath('.config', 'barkshark', 'pysync.ini')
|
|
config = {}
|
|
|
|
if len(args) > 0:
|
|
configfile = Path(args[0])
|
|
|
|
config = load_config(configfile)
|
|
|
|
if not config:
|
|
print('failed to load config')
|
|
sys.exit()
|
|
|
|
main(config)
|