Verpackt

Wie Tyler Cipriani habe ich höchsten Respekt vor rsync, im Gegensatz zu ihm hält sich mein Ehrgeiz, rsync in Python zu implementieren, in engen Grenzen. Stattdessen habe ich für meine leichte Backup-Obsession einen kleinen rsync-Wrapper geschrieben:

#!/usr/local/bin/python3 import os import sys import subprocess import argparse import configparser import datetime import scriptmail config_file = '/etc/default/rbackup.ini' config = configparser.ConfigParser() config.read(config_file) log_file = '/var/log/rbackup.log' class BackupProfile: def __init__(self, profile): for name, value in config.items(profile): setattr(self, name, value) @property def target(self): target_dir = '' # usually not required, defined via ~/.ssh/config if hasattr(self, 'target_user'): target_dir += self.target_user + '@' if hasattr(self, 'target_host'): target_dir += self.target_host + ':' target_dir += self.target_path return target_dir @property def source(self): source_dir = '' # usually not required, defined via ~/.ssh/config if hasattr(self, 'source_user'): source_dir += self.source_user + '@' if hasattr(self, 'source_host'): source_dir += self.source_host + ':' source_dir += self.source_path return source_dir @property def excludepath(self): if hasattr(self, 'exclude'): return ['--exclude', self.exclude] else: return False @property def options(self): if hasattr(self, 'rsync_options'): return self.rsync_options.split() else: return False def logging(self, success=True): now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') strings = { 'log' : f'{now}: {self.label}', 'mail' : f'"{self.label}" was completed.' } if not success: print('rsync failed!') for key in strings: strings[key] += ' – unsuccessful!' else: print('rsync completed successfully.') print('Sending message...') scriptmail.send_message(self.label, strings['mail']) print('Appending log file...') with open(log_file, 'a') as logfile: logfile.write(f'{strings["log"]}\n') def backup(self): print('Executing backup profile "{0}" ...'.format(self.label)) command_list = ["/opt/homebrew/bin/rsync", "-e", "ssh"] if self.options: command_list.extend(self.options) if self.excludepath: command_list.extend(self.excludepath) command_list.extend([self.source, self.target]) # print(command_list) return_code = subprocess.call(command_list) if return_code == 0: self.logging() else: self.logging(success=False) def main(): parser = argparse.ArgumentParser(description='Backup with rsync') parser.add_argument('profile', type=str, choices=config.sections(), help='the profile to backup') args = parser.parse_args() my_backup = BackupProfile(args.profile) my_backup.backup() if __name__=='__main__': main()

Anders als sysrsync verlässt sich rbackup.py nicht nur auf rsync, sondern auch auf ~/.ssh/config und kann die Konfiguration für SSH-gestützte Synchronisationsläufe dadurch deutlich vereinfachen:

[folder1] label: Important data source_path: /home/joaquin/personal_data/ source_host: 1blu target_path: /Users/jan/personal_data rsync_options: -av --delete-after --progress --port=1143 [folder2] label: Database files source_path: /Users/jan/dbdump/ target_path: /home/joaquin/dbdump target_host: 1blu rsync_options: -av --delete-after --progress --port=1143

Neben bidirektionalem SSH-Transfer beherrscht rsync (und damit rbackup.py) natürlich auch lokale Übertragungen:

[folder3] label: USB Backup source_path: /Users/jan/Backup/ target_path: /Volumes/USBKey/Backup rsync_options: -ivcrLtz --ignore-missing-args --delete-after --progress exclude: .*

Nutzt man rbackup.py seinerseits, um mehrere Synchronisationsprofile in kurzer Folge aufzurufen –

#!/usr/local/bin/python3 import time from rbackup import BackupProfile profiles = ['profile1', 'profile2', 'profile3', 'profile4', 'profile5', 'profile6', 'profile7', 'profile8'] for profile in profiles: backup_dir = BackupProfile(profile) backup_dir.backup() time.sleep(30)

– sollte man nach jedem Aufruf eine kurze Pause einlegen, um bei anvil nicht den Eindruck einer winzigen DoS-Attacke zu erzeugen.