From d63eb8f771f7e947bae7da7b762e9e815ae57246 Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 15:34:00 +0200 Subject: [PATCH 1/6] Improve handling of missing inputs --- simple_backup/simple_backup.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index e9ef4f0..c12b4eb 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -95,7 +95,7 @@ class Backup: def check_params(self): if self.inputs is None or len(self.inputs) == 0: - logger.info('No files or directory specified for backup.') + logger.info('No existing files or directories specified for backup. Nothing to do') return False @@ -181,11 +181,8 @@ class Backup: with open(self._inputs_path, 'w') as fp: for i in self.inputs: - if not os.path.exists(i): - logger.warning(f'Input {i} not found. Skipping') - else: - fp.write(i) - fp.write('\n') + fp.write(i) + fp.write('\n') with open(self._exclude_path, 'w') as fp: if self.exclude is not None: @@ -267,12 +264,15 @@ def _expand_inputs(inputs): expanded_inputs = [] for i in inputs: + if i == '': + continue + i_ex = glob(os.path.expanduser(i.replace('~', f'~{user}'))) if len(i_ex) == 0: logger.warning(f'No file or directory matching input {i}. Skipping...') else: - expanded_inputs.extend(glob(os.path.expanduser(i.replace('~', f'~{user}')))) + expanded_inputs.extend(i_ex) return expanded_inputs From b3fee0d02274fef11f730e1501d41d96366ace1c Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 16:58:56 +0200 Subject: [PATCH 2/6] Add some meaningful return codes --- man/simple_backup.1 | 10 ++++++++++ simple_backup/simple_backup.py | 15 ++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/man/simple_backup.1 b/man/simple_backup.1 index 9c5f125..c7d31d3 100644 --- a/man/simple_backup.1 +++ b/man/simple_backup.1 @@ -62,6 +62,16 @@ Don't use systemd journal for logging .SH CONFIGURATION An example configuration file is provided at \(aq/etc/simple_backup/simple_backup.conf\(aq. Copy it to the default location ($HOME/.config/simple_backup) and edit it as needed. +.SH EXIT STATUS +.TP +.B 0 +The backup was completed without errors +.TP +.B 1 +No valid inputs selected for backup +.TP +.B 2 +Backup failed because output directory for storing the backup does not exist .SH SEE ALSO .BR rsync (1) .SH AUTHORS diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index c12b4eb..3989f0b 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -16,7 +16,6 @@ from glob import glob from dotenv import load_dotenv - try: from systemd import journal except ImportError: @@ -97,24 +96,24 @@ class Backup: if self.inputs is None or len(self.inputs) == 0: logger.info('No existing files or directories specified for backup. Nothing to do') - return False + return 1 if self.output is None: logger.critical('No output path specified. Use -o argument or specify output path in configuration file') - return False + return 2 if not os.path.isdir(self.output): logger.critical('Output path for backup does not exist') - return False + return 2 self.output = os.path.abspath(self.output) if self.keep is None: self.keep = -1 - return True + return 0 # Function to create the actual backup directory def create_backup_dir(self): @@ -347,12 +346,14 @@ def simple_backup(): backup = Backup(inputs, output, exclude, keep, backup_options, remove_before=args.remove_before_backup) - if backup.check_params(): + return_code = backup.check_params() + + if return_code == 0: backup.run() return 0 - return 1 + return return_code if __name__ == '__main__': From 6f1e91e2cd69c6285790705b41d9c234179e7ac7 Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 17:14:17 +0200 Subject: [PATCH 3/6] Add more return codes --- man/simple_backup.1 | 6 ++++++ simple_backup/simple_backup.py | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/man/simple_backup.1 b/man/simple_backup.1 index c7d31d3..c68f70b 100644 --- a/man/simple_backup.1 +++ b/man/simple_backup.1 @@ -72,6 +72,12 @@ No valid inputs selected for backup .TP .B 2 Backup failed because output directory for storing the backup does not exist +.TP +.B 3 +Permission denied to access the output directory +.TP +.B 4 +rsync error (rsync returned a non-zero value) .SH SEE ALSO .BR rsync (1) .SH AUTHORS diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index 3989f0b..c6496d9 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 # Import libraries +import sys import os from functools import wraps from shutil import rmtree @@ -156,6 +157,15 @@ class Backup: logger.info('No previous backups available') return + except PermissionError: + logger.critical('Cannot access the backup directory. Permission denied') + + try: + notify('Backup failed (check log for details)') + except NameError: + pass + + sys.exit(3) try: self._last_backup = dirs[-1] @@ -229,12 +239,16 @@ class Backup: notify('Backup finished with errors (check log for details)') except NameError: pass + + return 4 else: try: notify('Backup finished') except NameError: pass + return 0 + def _parse_arguments(): parser = argparse.ArgumentParser(prog='simple_backup', @@ -349,9 +363,7 @@ def simple_backup(): return_code = backup.check_params() if return_code == 0: - backup.run() - - return 0 + return backup.run() return return_code From ff703584966ff054d0c4cb0ca29a3aa868da55dc Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 17:38:00 +0200 Subject: [PATCH 4/6] Version bump --- simple_backup/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple_backup/__init__.py b/simple_backup/__init__.py index bdb76e1..8b6e509 100644 --- a/simple_backup/__init__.py +++ b/simple_backup/__init__.py @@ -1,3 +1,3 @@ """Init.""" -__version__ = '3.3.0' +__version__ = '3.4.0' From 4d23bde906825ce5fc51c86c1463c0acda8dde3e Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 18:44:22 +0200 Subject: [PATCH 5/6] Check that inputs from command line exist Check that the inputs specified on the command line (i.e. with the option '-i' or '--input') exist and print a warning when they don't. If no valid inputs are found, exit. --- simple_backup/__init__.py | 2 +- simple_backup/simple_backup.py | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/simple_backup/__init__.py b/simple_backup/__init__.py index 8b6e509..773d4e5 100644 --- a/simple_backup/__init__.py +++ b/simple_backup/__init__.py @@ -1,3 +1,3 @@ """Init.""" -__version__ = '3.4.0' +__version__ = '3.4.1' diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index c6496d9..583e535 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -186,12 +186,28 @@ class Backup: self.create_backup_dir() _, self._inputs_path = mkstemp(prefix='tmp_inputs', text=True) - _, self._exclude_path = mkstemp(prefix='tmp_exclude', text=True) + count = 0 with open(self._inputs_path, 'w') as fp: for i in self.inputs: - fp.write(i) - fp.write('\n') + if not os.path.exists(i): + logger.warning(f'Input {i} not found. Skipping') + else: + fp.write(i) + fp.write('\n') + count += 1 + + if count == 0: + logger.info('No existing files or directories specified for backup. Nothing to do') + + try: + notify('Backup finished. No files copied') + except NameError: + pass + + return 1 + + _, self._exclude_path = mkstemp(prefix='tmp_exclude', text=True) with open(self._exclude_path, 'w') as fp: if self.exclude is not None: From 970d3dde1efdb1f0add4ed98d5fe26f9b56217e9 Mon Sep 17 00:00:00 2001 From: Fuxino Date: Thu, 15 Jun 2023 21:30:43 +0200 Subject: [PATCH 6/6] Add manual selection of rsync options --- man/simple_backup.1 | 19 +++++++++++++++++++ simple_backup/__init__.py | 2 +- simple_backup/simple_backup.py | 20 ++++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/man/simple_backup.1 b/man/simple_backup.1 index c68f70b..f45db44 100644 --- a/man/simple_backup.1 +++ b/man/simple_backup.1 @@ -59,6 +59,25 @@ Default behavior is to remove old backups after successfully completing the back .TP .B \-\-no\-syslog Don't use systemd journal for logging +.TP +.B \-\-rsync\-options OPTIONS [OPTION...] +By default, the following rsync options are used: +.RS +.PP +\-a \-r \-c \-v \-h \-H \-X +.PP +Using \-\-rsync\-options it is possible to manually select which options to use. Supported values are the following: +.PP +\-a, \-l, \-p, \-t, \-g, \-o, \-c, \-h, \-D, \-H, \-X +.PP +Options \-r and \-v are used in any case. Not that options must be specified without dash (\-), for example: +.PP +.EX +simple_backup \-\-rsync\-options a l p +.EE +.TP +Check rsync(1) for details about the options. +.RE .SH CONFIGURATION An example configuration file is provided at \(aq/etc/simple_backup/simple_backup.conf\(aq. Copy it to the default location ($HOME/.config/simple_backup) and edit it as needed. diff --git a/simple_backup/__init__.py b/simple_backup/__init__.py index 773d4e5..0b093c1 100644 --- a/simple_backup/__init__.py +++ b/simple_backup/__init__.py @@ -1,3 +1,3 @@ """Init.""" -__version__ = '3.4.1' +__version__ = '3.5.0' diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index 583e535..980bf59 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -283,6 +283,9 @@ def _parse_arguments(): parser.add_argument('--remove-before-backup', action='store_true', help='Remove old backups before executing the backup, instead of after') parser.add_argument('--no-syslog', action='store_true', help='Disable systemd journal logging') + parser.add_argument('--rsync-options', nargs='+', + choices=['a', 'l', 'p', 't', 'g', 'o', 'c', 'h', 'D', 'H', 'X'], + help='Specify options for rsync') args = parser.parse_args() @@ -369,12 +372,21 @@ def simple_backup(): if args.keep is not None: keep = args.keep - if args.checksum: - backup_options = '-arcvh -H -X' + if args.rsync_options is None: + if args.checksum: + rsync_options = '-arcvh -H -X' + else: + rsync_options = '-arvh -H -X' else: - backup_options = '-arvh -H -X' + rsync_options = '-r -v ' - backup = Backup(inputs, output, exclude, keep, backup_options, remove_before=args.remove_before_backup) + for ro in args.rsync_options: + rsync_options = rsync_options + f'-{ro} ' + + if '-c ' not in rsync_options and args.checksum: + rsync_options = rsync_options + '-c' + + backup = Backup(inputs, output, exclude, keep, rsync_options, remove_before=args.remove_before_backup) return_code = backup.check_params()