6 Commits
3.3.0 ... 3.4.1

Author SHA1 Message Date
4d23bde906 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.
2023-06-15 18:44:22 +02:00
ff70358496 Version bump 2023-06-15 17:38:00 +02:00
6f1e91e2cd Add more return codes 2023-06-15 17:14:17 +02:00
b3fee0d022 Add some meaningful return codes 2023-06-15 16:58:56 +02:00
d63eb8f771 Improve handling of missing inputs 2023-06-15 15:34:00 +02:00
56df958c5b Add expansion of params in config file
Allow using wildcards (i.e. * to match any character and ~ to match the
user's home directory) in inputs and ouput variables in config file
2023-06-04 12:09:30 +02:00
4 changed files with 84 additions and 18 deletions

View File

@ -62,6 +62,22 @@ Don't use systemd journal for logging
.SH CONFIGURATION .SH CONFIGURATION
An example configuration file is provided at \(aq/etc/simple_backup/simple_backup.conf\(aq. Copy it to the default location 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. ($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
.TP
.B 3
Permission denied to access the output directory
.TP
.B 4
rsync error (rsync returned a non-zero value)
.SH SEE ALSO .SH SEE ALSO
.BR rsync (1) .BR rsync (1)
.SH AUTHORS .SH AUTHORS

View File

@ -1,3 +1,3 @@
"""Init.""" """Init."""
__version__ = '3.3.0' __version__ = '3.4.1'

View File

@ -1,14 +1,14 @@
# Example config file for simple_backup # Example config file for simple_backup
[default] [default]
#Input directories. Use a comma to separate items # Files and directories to backup. Multiple items can be separated using a comma (','). It is possible to use wildcards (i.e. '*' to match multiple characters and '~' for the user's home directory).
inputs=/home/my_home,/etc inputs=/home/my_home,/etc
#Output directory # Output directory.
backup_dir=/media/Backup backup_dir=/media/Backup
#Exclude patterns. Use a comma to separate items # Files, directories and patterns to exclude from the backup. Multiple items can be separated using a comma.
exclude=.gvfs,.local/share/gvfs-metadata,.cache,.dbus,.Trash,.local/share/Trash,.macromedia,.adobe,.recently-used,.recently-used.xbel,.thumbnails exclude=*.bak
#Number of snapshots to keep (use -1 to keep all) # Number of old backups (i.e. excluding the one that's being created) to keep (use -1 to keep all)
keep=-1 keep=-1

View File

@ -1,6 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
# Import libraries # Import libraries
import sys
import os import os
from functools import wraps from functools import wraps
from shutil import rmtree from shutil import rmtree
@ -12,6 +13,8 @@ from timeit import default_timer
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
from datetime import datetime from datetime import datetime
from tempfile import mkstemp from tempfile import mkstemp
from glob import glob
from dotenv import load_dotenv from dotenv import load_dotenv
try: try:
@ -32,6 +35,7 @@ if euid == 0:
user = os.getenv('SUDO_USER') user = os.getenv('SUDO_USER')
homedir = os.path.expanduser(f'~{user}') homedir = os.path.expanduser(f'~{user}')
else: else:
user = os.getenv('USER')
homedir = os.getenv('HOME') homedir = os.getenv('HOME')
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@ -91,26 +95,26 @@ class Backup:
def check_params(self): def check_params(self):
if self.inputs is None or len(self.inputs) == 0: 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 return 1
if self.output is None: if self.output is None:
logger.critical('No output path specified. Use -o argument or specify output path in configuration file') 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): if not os.path.isdir(self.output):
logger.critical('Output path for backup does not exist') logger.critical('Output path for backup does not exist')
return False return 2
self.output = os.path.abspath(self.output) self.output = os.path.abspath(self.output)
if self.keep is None: if self.keep is None:
self.keep = -1 self.keep = -1
return True return 0
# Function to create the actual backup directory # Function to create the actual backup directory
def create_backup_dir(self): def create_backup_dir(self):
@ -153,6 +157,15 @@ class Backup:
logger.info('No previous backups available') logger.info('No previous backups available')
return 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: try:
self._last_backup = dirs[-1] self._last_backup = dirs[-1]
@ -173,7 +186,7 @@ class Backup:
self.create_backup_dir() self.create_backup_dir()
_, self._inputs_path = mkstemp(prefix='tmp_inputs', text=True) _, 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: with open(self._inputs_path, 'w') as fp:
for i in self.inputs: for i in self.inputs:
@ -182,6 +195,19 @@ class Backup:
else: else:
fp.write(i) fp.write(i)
fp.write('\n') 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: with open(self._exclude_path, 'w') as fp:
if self.exclude is not None: if self.exclude is not None:
@ -229,12 +255,16 @@ class Backup:
notify('Backup finished with errors (check log for details)') notify('Backup finished with errors (check log for details)')
except NameError: except NameError:
pass pass
return 4
else: else:
try: try:
notify('Backup finished') notify('Backup finished')
except NameError: except NameError:
pass pass
return 0
def _parse_arguments(): def _parse_arguments():
parser = argparse.ArgumentParser(prog='simple_backup', parser = argparse.ArgumentParser(prog='simple_backup',
@ -259,6 +289,23 @@ def _parse_arguments():
return args return args
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(i_ex)
return expanded_inputs
def _read_config(config_file): def _read_config(config_file):
if not os.path.isfile(config_file): if not os.path.isfile(config_file):
logger.warning(f'Config file {config_file} does not exist') logger.warning(f'Config file {config_file} does not exist')
@ -270,7 +317,10 @@ def _read_config(config_file):
inputs = config.get('default', 'inputs') inputs = config.get('default', 'inputs')
inputs = inputs.split(',') inputs = inputs.split(',')
inputs = _expand_inputs(inputs)
inputs = list(set(inputs))
output = config.get('default', 'backup_dir') output = config.get('default', 'backup_dir')
output = os.path.expanduser(output.replace('~', f'~{user}'))
exclude = config.get('default', 'exclude') exclude = config.get('default', 'exclude')
exclude = exclude.split(',') exclude = exclude.split(',')
keep = config.getint('default', 'keep') keep = config.getint('default', 'keep')
@ -326,12 +376,12 @@ def simple_backup():
backup = Backup(inputs, output, exclude, keep, backup_options, remove_before=args.remove_before_backup) backup = Backup(inputs, output, exclude, keep, backup_options, remove_before=args.remove_before_backup)
if backup.check_params(): return_code = backup.check_params()
backup.run()
return 0 if return_code == 0:
return backup.run()
return 1 return return_code
if __name__ == '__main__': if __name__ == '__main__':