Third prototype of backup script

This commit is contained in:
Eric Torres 2019-04-10 19:21:00 -07:00
parent 89c2cbe1ac
commit 4351387f68

View File

@ -1,32 +1,35 @@
#!/usr/bin/python3 #!/usr/bin/python3
""" """
Run a backup. Run a backup, creating a snapshot in the process.
Command-Line Arguments Command-Line Arguments
====================== ======================
* -c, --use-checksums use rsync's checksum feature to detect file changes * -c, --use-checksums use rsync's checksum feature to detect file changes
* -d, --dry-run make this backup a dry run * -d, --dry-run make this backup a dry run
* --debug show debug messages
* -n, --name name to give to the backup snapshot
* -s, --run-post-sync run sync syscall after backup * -s, --run-post-sync run sync syscall after backup
* -v, --verbose increase script verbosity * -v, --verbose show info messages
On each run of this script, a new snapshot is made and any unchanged On each run of this script, a new snapshot is made and any unchanged
files are hardlinked into this new snapshot. files are hardlinked into the new snapshot.
""" """
import argparse import argparse
import logging import logging
import os import os
import sys import sys
from rbackup.hierarchy.repository import Repository
from rbackup.rsync import rsync
from subprocess import CalledProcessError from subprocess import CalledProcessError
from rbackup.rsync import rsync
from rbackup.struct.repository import Repository
# ========== Constants ========== # ========== Constants ==========
LOGFORMAT = "==> %(levelname)s %(message)s" LOGFORMAT = "==> %(levelname)s %(message)s"
RSYNC_DEFAULT_OPTS = [ RSYNC_DEFAULT_OPTS = [
"--acls", "--acls",
"--archive", "--archive",
"--backup", "--backup",
"--backup-dir=backup",
"--hard-links", "--hard-links",
"--ignore-missing-args", "--ignore-missing-args",
"--prune-empty-dirs", "--prune-empty-dirs",
@ -40,12 +43,18 @@ EXTRA_RSYNC_OPTS = {
"update": "--update", "update": "--update",
} }
# ----- File Options -----
CONFIG_DIR = "/etc/rbackup" CONFIG_DIR = "/etc/rbackup"
ETC_INCLUDE_FILE = f"{CONFIG_DIR}/etc-include.conf" FILE_OPTS = [
HOME_EXCLUDE_FILE = f"{CONFIG_DIR}/home-exclude.conf" f"--include-from={CONFIG_DIR}/etc-include.conf",
f"--include-from={CONFIG_DIR}/system-include.conf",
f"--exclude-from{CONFIG_DIR}/home-exclude.conf",
]
# ----- Error Codes ----- # ----- Error Codes -----
E_FAILED_PROCESS = 1 E_FAILED_PROCESS = 1
E_INVALID_SNAPSHOT_NAME = 2
# ========== Logging Setup ========== # ========== Logging Setup ==========
console_formatter = logging.Formatter(LOGFORMAT) console_formatter = logging.Formatter(LOGFORMAT)
@ -89,6 +98,10 @@ def parse_cmdline_arguments(*args, **kwargs):
const=EXTRA_RSYNC_OPTS["dry_run"], const=EXTRA_RSYNC_OPTS["dry_run"],
help="pass --dry-run to rsync", help="pass --dry-run to rsync",
) )
parser.add_argument("--debug", action="store_true", help="log debug messages")
parser.add_argument(
"-n", "--name", default=None, help="name to give to the snapshot"
)
parser.add_argument( parser.add_argument(
"-s", "-s",
"--run-post-sync", "--run-post-sync",
@ -96,48 +109,44 @@ def parse_cmdline_arguments(*args, **kwargs):
help="run sync operation after backup is complete", help="run sync operation after backup is complete",
) )
parser.add_argument( parser.add_argument(
"-v", "-v", "--verbose", action="store_true", help="log info messages"
"--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("repository", help="repository to back up to", metavar="repo")
args = parser.parse_args() return parser.parse_args(*args, **kwargs)
# ========== Main Script ==========
if __name__ == "__main__":
args = parse_cmdline_arguments()
repo = Repository(args.repository)
rsync_opts = RSYNC_DEFAULT_OPTS.copy()
rsync_opts = RSYNC_DEFAULT_OPTS
if args.extra_rsync_opts is not None: if args.extra_rsync_opts is not None:
rsync_opts.extend(args.extra_rsync_opts) rsync_opts.extend(args.extra_rsync_opts)
args = parser.parse_args()
repo = Repository(args.repository)
if args.verbose: if args.verbose:
stdout_handler.setLevel(logging.INFO)
if args.debug:
stdout_handler.setLevel(logging.DEBUG) stdout_handler.setLevel(logging.DEBUG)
if repo.empty: # We want to iterate through the repository and create the --link-dest
prev_snapshot = None # options before creating the new snapshot
else: link_dests = tuple(f"--link-dest={s.path}" for s in repo)
prev_snapshot = repo.curr_snapshot
repo.create_snapshot() curr_snapshot = None
curr_snapshot = repo.curr_snapshot
rsync_opts.append("--backup-dir=backup")
try: try:
DISPATCHER[args.operation](curr_snapshot, prev_snapshot, rsync_opts) curr_snapshot = repo.create_snapshot(args.name)
rsync(*rsync_opts, *FILE_OPTS, *link_dests, "/", str(curr_snapshot.path))
except ValueError as e:
syslog.critical(e)
exit(E_INVALID_SNAPSHOT_NAME)
except CalledProcessError as e: except CalledProcessError as e:
syslog.error("Backup process failed") syslog.critical("Backup process failed")
syslog.info(f"Failing command: {e.cmd}") syslog.critical(f"Failing command: {e.cmd}")
exit(E_FAILED_PROCESS) exit(E_FAILED_PROCESS)
snapshot_symlink = repo.path / "current" snapshot_symlink = repo.path / "current"
@ -147,7 +156,7 @@ def parse_cmdline_arguments(*args, **kwargs):
except FileNotFoundError: except FileNotFoundError:
pass pass
snapshot_symlink.symlink_to(curr_snapshot.path, target_is_directory=True) snapshot_symlink.symlink_to(curr_snapshot, target_is_directory=True)
if args.run_post_sync: if args.run_post_sync:
os.sync() os.sync()