diff --git a/cbackupmirror.py b/cbackupmirror.py new file mode 100644 index 0000000..d7561c8 --- /dev/null +++ b/cbackupmirror.py @@ -0,0 +1,75 @@ +import coloredlogs +import logging +import sys + +from executor.contexts import create_context + +import rsync_system_backup as rsb + +# Config +SYSLOG_HOST = "192.168.112.11" +SYSLOG_PORT = 514 + +BACKUP_SOURCE = "backup-mirror@192.168.112.16:/data" +BACKUP_DESTINATION = "/data/backup-data/data" + + +def setup_logging(): + coloredlogs.install(syslog=True) + log = logging.getLogger() + + syslog = logging.handlers.SysLogHandler(address=(SYSLOG_HOST, SYSLOG_PORT)) + log.addHandler(syslog) + + return log + + +def do_backup(): + log = logging.getLogger() + + program_opts = { + 'backup_enabled': True, + 'snapshot_enabled': True, + 'rotate_enabled': True, + 'sudo_enabled': False, + 'dry_run': False, + 'multi_fs' : True, + 'notifications_enabled': False, + 'rsync_verbose_count': 1, + 'rsync_show_progress': True, + 'source_context': create_context(), + 'source': BACKUP_SOURCE, + 'destination': rsb.Destination(expression=BACKUP_DESTINATION) + } + + try: + # Initialize the program with the command line + # options and execute the requested action(s). + b = rsb.RsyncSystemBackup(**program_opts).execute() + except Exception as e: + if isinstance(e, rsb.exceptions.RsyncSystemBackupError): + # Special handling when the backup disk isn't available. + if isinstance(e, rsb.exceptions.MissingBackupDiskError): + log.info("Skipping backup: %s", e) + return 1 + # Known problems shouldn't produce + # an intimidating traceback to users. + log.error("Aborting due to error: %s", e) + else: + # Unhandled exceptions do get a traceback, + # because it may help fix programming errors. + log.exception("Aborting due to unhandled exception!") + return 1 + else: + return 0 + + +def main(): + + log = setup_logging() + log.info("Starting backup script") + return do_backup() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/rsync_system_backup/__init__.py b/rsync_system_backup/__init__.py index dfdfe90..6512397 100644 --- a/rsync_system_backup/__init__.py +++ b/rsync_system_backup/__init__.py @@ -178,6 +178,16 @@ def dry_run(self): """:data:`True` to simulate the backup without writing any files, :data:`False` otherwise.""" return False + @mutable_property + def checkum(self): + """:data:`True` to enable the checksum verification by rsync instead of usual time checks.""" + return False + + @mutable_property + def multi_fs(self): + """:data:`True` to allow rsync to cross filesystem boundaries, :data:`False` otherwise.""" + return False + @lazy_property(writable=True) def exclude_list(self): """ @@ -288,6 +298,21 @@ def sudo_enabled(self): """:data:`True` to run ``rsync`` and snapshot creation with superuser privileges, :data:`False` otherwise.""" return True + @mutable_property + def rsync_verbose_count(self): + """:data: defaults to zero. Represents the number of -V arguments received""" + return 0 + + @mutable_property + def rsync_quiet_count(self): + """:data: defaults to zero. Represents the number of -Q arguments received""" + return 0 + + @mutable_property + def rsync_show_progress(self): + """:data: defaults to False. Will have rsync display transfer progress if True""" + return False + def execute(self): """ Execute the requested actions (backup, snapshot and/or rotate). @@ -514,6 +539,14 @@ def transfer_changes(self): if self.dry_run: rsync_command.append('--dry-run') rsync_command.append('--verbose') + for _ in range(self.rsync_verbose_count): + rsync_command.append('--verbose') + for _ in range(self.rsync_quiet_count): + rsync_command.append('--quiet') + if self.rsync_show_progress: + rsync_command.append('--progress') + if self.checkum: + rsync_command.append('--checksum') # The following rsync options delete files in the backup # destination that no longer exist on the local system. # Due to snapshotting this won't cause data loss. @@ -528,11 +561,8 @@ def transfer_changes(self): rsync_command.append('--xattrs') # The following rsync option avoids including mounted external # drives like USB sticks in system backups. - # - # FIXME This will most likely be problematic for users with fancy - # partitioning schemes that e.g. mount /home to a different - # disk or partition. - rsync_command.append('--one-file-system') + if not self.multi_fs: + rsync_command.append('--one-file-system') # The following rsync options exclude irrelevant directories (to my # subjective mind) from the system backup. for pattern in self.excluded_roots: diff --git a/rsync_system_backup/cli.py b/rsync_system_backup/cli.py index b5a99bc..b79750e 100644 --- a/rsync_system_backup/cli.py +++ b/rsync_system_backup/cli.py @@ -124,6 +124,11 @@ create a backup or snapshot but it does run rsync with the --dry-run option. + --multi-fs + + Allow rsync to cross filesystem boundaries. (has the opposite effect + of rsync option "-x, --one-file-system"). + -x, --exclude=PATTERN Selectively exclude certain files from being included in the backup. @@ -145,11 +150,23 @@ -v, --verbose - Make more noise (increase logging verbosity). Can be repeated. + Make more noise (increase logging verbosity for the python app). Can be repeated. + + -V, --rsync-verbose + + Make the rsync program more noisy. Can be repeated. -q, --quiet - Make less noise (decrease logging verbosity). Can be repeated. + Make less noise (decrease logging verbosity for the python app). Can be repeated. + + -Q, --rsync-quiet + + Make the rsync program less noisy. + + -p, --rsync-progress + + Have rsync show transfer progress. -h, --help @@ -194,10 +211,11 @@ def main(): program_opts = dict() dest_opts = dict() try: - options, arguments = getopt.gnu_getopt(sys.argv[1:], 'bsrm:c:t:i:unx:fvqh', [ + options, arguments = getopt.gnu_getopt(sys.argv[1:], 'bsrm:c:t:i:unx:fvqhVQp', [ 'backup', 'snapshot', 'rotate', 'mount=', 'crypto=', 'tunnel=', 'ionice=', 'no-sudo', 'dry-run', 'exclude=', 'force', - 'disable-notifications', 'verbose', 'quiet', 'help', + 'disable-notifications', 'verbose', 'quiet', 'help', 'multi-fs', + 'rsync-verbose', 'rsync-quiet', 'rsync-progress' ]) for option, value in options: if option in ('-b', '--backup'): @@ -237,12 +255,26 @@ def main(): elif option in ('-x', '--exclude'): program_opts.setdefault('exclude_list', []) program_opts['exclude_list'].append(value) + elif option == '--multi-fs': + program_opts['multi_fs'] = True elif option == '--disable-notifications': program_opts['notifications_enabled'] = False + elif option in ('-V', '--rsync-verbose'): + if 'rsync_verbose_count' not in program_opts: + program_opts['rsync_verbose_count'] = 1 + else: + program_opts['rsync_verbose_count'] = program_opts['rsync_verbose_count'] + 1 + elif option in ('-Q', '--rsync-quiet'): + if 'rsync_quiet_count' not in program_opts: + program_opts['rsync_quiet_count'] = 1 + else: + program_opts['rsync_quiet_count'] = program_opts['rsync_quiet_count'] + 1 elif option in ('-v', '--verbose'): coloredlogs.increase_verbosity() elif option in ('-q', '--quiet'): coloredlogs.decrease_verbosity() + elif option in ('-p', '--rsync-progress'): + program_opts['rsync_show_progress'] = True elif option in ('-h', '--help'): usage(__doc__) return