111 lines
3.2 KiB
Python
Executable File
111 lines
3.2 KiB
Python
Executable File
#!/bin/env python3
|
|
|
|
import argparse
|
|
import sys
|
|
import os
|
|
import json
|
|
import subprocess
|
|
|
|
from loguru import logger
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('-r', '--repo', default='main')
|
|
|
|
parser.add_argument('--quiet', action='store_true')
|
|
parser.add_argument('--config', default='/usr/local/etc/restic.json')
|
|
parser.add_argument('--log-level', default='INFO')
|
|
parser.add_argument('--dry-run', action='store_true')
|
|
parser.add_argument('--no-excludes', action='store_true')
|
|
parser.add_argument('--log-file',
|
|
default='/var/log/backup/restic-backups.log')
|
|
|
|
args, restic_args = parser.parse_known_args()
|
|
return (args, restic_args)
|
|
|
|
|
|
def setup_logging(repo_name, log_file, args_log_level, quiet):
|
|
log_level = os.environ.get("SUDOIS_LOG_LEVEL", args_log_level.upper())
|
|
log_format = " ".join([
|
|
"<green><dim>{time:YYYY-MM-DDT}</dim>{time:HH:mm:ss}<dim>{time:Z}</dim></green>", # noqa
|
|
"<dim><yellow>repo={extra[repo]}</yellow></dim>",
|
|
"<dim><level>{level: >8}</level></dim>",
|
|
"<level>{message}</level>"
|
|
])
|
|
handlers = [{
|
|
'sink': sys.stderr,
|
|
'format': log_format,
|
|
'level': "ERROR" if quiet else log_level
|
|
},
|
|
{
|
|
'sink': log_file,
|
|
'format': log_format,
|
|
'level': log_level,
|
|
'rotation': "4 weeks",
|
|
'retention': "1 year",
|
|
'serialize': True
|
|
}
|
|
]
|
|
logger.configure(handlers=handlers, extra={'repo': repo_name})
|
|
|
|
|
|
def read_config(config_path):
|
|
with open(config_path, 'r') as f:
|
|
return json.load(f)
|
|
|
|
|
|
def restic_password_command(repo_name):
|
|
return f"/usr/local/bin/restic-password.py {repo_name}"
|
|
|
|
|
|
def prepare_env(repo_name, repo_config):
|
|
restic_env = dict()
|
|
|
|
passwd_cmd = restic_password_command(repo_name)
|
|
restic_env.update({'RESTIC_PASSWORD_COMMAND': passwd_cmd})
|
|
|
|
if 'env' in repo_config:
|
|
restic_env.update(repo_config['env'])
|
|
|
|
logger.debug(f"setting env vars: {' '.join(restic_env.keys())}")
|
|
|
|
# its also possible to pass subprocess.run(env=dict()) but then
|
|
# we dont get the parent process' env with us
|
|
os.environ.update(restic_env)
|
|
|
|
|
|
def run_restic(repo_url, restic_args, no_excludes, dry_run):
|
|
restic_cmd = ["restic", "-r", repo_url]
|
|
restic_cmd.extend(restic_args)
|
|
|
|
if "backup" in restic_args and not no_excludes:
|
|
restic_cmd.append("--exclude-file")
|
|
restic_cmd.append("/usr/local/etc/backup-excludes.txt")
|
|
|
|
logger.debug(" ".join(restic_cmd))
|
|
if dry_run:
|
|
logger.warning("this was a dry run, no changes have been made")
|
|
return
|
|
|
|
# .run(env={}) is possible (but then child doesnt inherit parent env)
|
|
return subprocess.run(restic_cmd, check=True)
|
|
|
|
def find_nobackup(config):
|
|
pass
|
|
|
|
def main():
|
|
args, restic_args = parse_args()
|
|
setup_logging(args.repo, args.log_file, args.log_level, args.quiet)
|
|
config = read_config(args.config)
|
|
|
|
# sets the RESTIC_PASSWORD_COMMAND and other env vars as needed
|
|
repo_config = config[args.repo]
|
|
prepare_env(args.repo, repo_config)
|
|
return run_restic(
|
|
repo_config['url'], restic_args, args.no_excludes, args.dry_run)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|