diff --git a/files/rsync-backup b/files/rsync-backup index 76c7a74..f7e553f 100755 --- a/files/rsync-backup +++ b/files/rsync-backup @@ -5,30 +5,40 @@ import subprocess import os import sys import shlex +import json from argparse import ArgumentParser -VERSION = "1.1" +VERSION = "1.2" class RSB: def __init__(self, options): + self.options = options + self.lock = options.lock self.base_folder = options.base_folder self.backup_source = options.backup_source self.rsync_args = options.rsync_args self.delete_older = options.del_older self.keep_n = options.keep_n self.delete_disk_percentage = options.del_used + self.config_file = os.path.join(self.base_folder, "rsync-backup.txt") + self.lock_file = os.path.join(self.base_folder, "rsync-backup.lock") if not self.backup_source.endswith("/"): self.backup_source += "/" self.current = os.path.join(self.base_folder, "current") self.folder_format = "backup-%Y%m%d-%H%M%S" os.makedirs(self.current, exist_ok=True) + + self.lock_check() + self.write_config() self.clean_old() success = self.make_backup() if success: self.make_hardlinks() - else: + + self.lock_clean() + if not success: print("Program exited with errors") sys.exit(1) @@ -107,6 +117,46 @@ class RSB: success = exitcode in (0, 23, 24, 25) return success + def write_config(self): + + conf = json.dumps(vars(self.options)) + with open(self.config_file, "wt") as fp: + fp.write( + """# RSync Backup version {version} +# {conf} +rsync-backup \\ + --del-older {older} \\ + --keep-number {number} \\ + --del-disk-used {used} \\ + --rsync-args '{args}' \\ + {source} \\ + {base} +""".format( + conf=conf, + version=VERSION, + older=self.delete_older, + number=self.keep_n, + used=self.delete_disk_percentage, + args=self.rsync_args, + source=self.backup_source, + base=self.base_folder, + ) + ) + + def lock_check(self): + if self.options.lock: + if os.path.exists(self.lock_file): + raise FileExistsError("Lock file '{}' exists".format(self.lock_file)) + with open(self.lock_file, "wt") as fp: + fp.write(str(os.getpid())) + + def lock_clean(self): + if self.options.lock: + try: + os.remove(self.lock_file) + except FileNotFoundError: + pass + def get_opts(): @@ -145,6 +195,13 @@ def get_opts(): default="-aP --del", help="Rsync arguments. Add excludes here (example '-vxP --exclude folder/'). Defaults: '%(default)s'", ) + parser.add_argument( + "--lock", + dest="lock", + action="store_true", + default=False, + help="Use locking to prevent concurrent backups", + ) parser.add_argument("--version", action="version", version=VERSION) parser.add_argument( "backup_source", action="store", help="Source URL to backup with rsync"