diff --git a/bin/backup b/bin/backup index 94aea79..503ae65 100644 --- a/bin/backup +++ b/bin/backup @@ -4,22 +4,33 @@ Run a backup. Command-Line Arguments ====================== +* -c, --use-checksums use rsync's checksum feature to detect file changes +* -d, --dry-run make this backup a dry run +* -s, --run-post-sync run sync syscall after backup +* -v, --verbose increase script verbosity + +On each run of this script, a new snapshot is made and any unchanged +files are hardlinked into this new snapshot. """ import argparse import logging -import os.path -import rbackup.rsync as rsync +import os +import sys -from rbackup.hierarchy import Repository, Snapshot +from rbackup.hierarchy.repository import Repository +from rbackup.rsync import rsync # ========== Constants ========== +LOGFORMAT = "==> %(levelname)s %(message)s" RSYNC_DEFAULT_OPTS = [ + "--acls", "--archive", - "--hard-links", - "--prune-dirs", "--backup", - "--xattrs", + "--hard-links", "--ignore-missing-args", + "--prune-dirs", + "--suffix=.old", + "--xattrs", ] EXTRA_RSYNC_OPTS = { "dry_run": "--dry-run", @@ -28,9 +39,11 @@ EXTRA_RSYNC_OPTS = { "update": "--update", } +ETC_INCLUDE_FILE = "/etc/rbackup/etc-include.conf" + # ========== Logging Setup ========== console_formatter = logging.Formatter(LOGFORMAT) -syslog = logging.getLogger("packaging_scripts") +syslog = logging.getLogger("rbackup") syslog.setLevel(logging.DEBUG) stdout_handler = logging.StreamHandler(sys.stdout) @@ -45,6 +58,48 @@ stderr_handler.setFormatter(console_formatter) syslog.addHandler(stdout_handler) syslog.addHandler(stderr_handler) + +# ========== Functions ========== +def backup_all(s, rsync_opts): + raise NotImplementedError + + +def backup_etc(s, rsync_opts): + """Create a backup of /etc + + :param s: snapshot to back up to + :type s: Snapshot object + :param opts: options to pass to rsync + :type opts: list + """ + local_opts = rsync_opts + local_opts.append(f"--files-from={ETC_INCLUDE_FILE}") + + syslog.debug("Creating directory {s.etc_path}") + s.etc_dir.mkdir(parents=True, exist_ok=False) + rsync(*local_opts, "/etc/", str(s.etc_path)) + + +def backup_home(): + raise NotImplementedError + + +def backup_pkgmanager(): + raise NotImplementedError + + +def backup_system(): + raise NotImplementedError + + +DISPATCHER = { + "all": backup_all, + "etc": backup_etc, + "home": backup_home, + "system": backup_system, +} + + # ========== Main Script ========== if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -54,7 +109,7 @@ if __name__ == "__main__": action="store_const", dest="extra_rsync_opts", const=EXTRA_RSYNC_OPTS["checksum"], - help="use rsync's checksumming feature to look for changed files" + help="use rsync's checksumming feature to look for changed files", ) parser.add_argument( "-d", @@ -65,25 +120,60 @@ if __name__ == "__main__": help="pass --dry-run to rsync", ) parser.add_argument( - "-v", "--verbose", action="store_true", help="increase script verbosity" + "-s", + "--run-post-sync", + action="store_true", + help="run sync operation after backup is complete", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="increase script verbosity", + ) + parser.add_argument( + "operation", + choices=["all", "etc", "home", "pkg", "system"], + help="the operation to perform", + metavar="op", + ) + parser.add_argument( + "repository", help="repository to back up to", metavar="repo" ) - parser.add_argument("operation", help="the operation to perform", metavar="op") - parser.add_argument("repository", help="repository to back up to", metavar="repo") - args = parser.parser_args() + args = parser.parse_args() + + rsync_opts = [] + if args.extra_rsync_opts is not None: + rsync_opts.extend(args.extra_rsync_opts) + + args = parser.parse_args() repo = Repository(args.repository) - link_dest = hier.prev_snapshot - if args.verbose: stdout_handler.setLevel(logging.DEBUG) - # Handle empty repository + if repo.empty: + prev_snapshot = None + else: + prev_snapshot = repo.curr_snapshot - # else handle regular repository + repo.create_snapshot() + curr_snapshot = repo.curr_snapshot - # Backup to hier.curr_snapshot + rsync_opts.append(f"--link-dest={prev_snapshot}") + rsync_opts.append("--backup-dir=backup") - # Relink 'prev' to point to latest snapshot - os.remove(hier.prev_snapshot_link) - os.symlink(curr_snapshot, hier.prev_snapshot_link, target_is_directory=True) + DISPATCHER[args.operation](curr_snapshot, rsync_opts) + + snapshot_symlink = repo.path / "current" + + try: + snapshot_symlink.unlink() + except FileNotFoundError: + pass + + snapshot_symlink.symlink_to(curr_snapshot, target_is_directory=True) + + if args.run_post_sync: + os.sync()