""" BSD 2-Clause License restic subprocess wrapper Copyright (c) 2018, Max Resnick All rights reserved. restic-py is simple wrapper to restic binary for backups and monitoring """ import os import tempfile import configparser import glob import argparse import logging import sys import time import io import sh # logging LOGLEVELS = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR} HOME_DIR = os.path.expanduser('~') DEFAULT_CONFIG = '{}/.local/etc/restic-wrapper/restic.ini'.format(HOME_DIR) CONFIG = os.environ.get('RESTIC_CONFIG', DEFAULT_CONFIG) # TODO should be a configurable location logging.basicConfig(filename='{}/.local/logs/restic.log'.format(HOME_DIR), level=LOGLEVELS[os.getenv('RESTIC_LOGLEVEL', 'INFO')]) log = logging.getLogger(__name__) # cmd line parser parser = argparse.ArgumentParser(description='wrapper to restic') parser.add_argument('cmd', type=str, choices=('backup', 'prune', 'check', 'watch'), help='run backup') # config cfg = configparser.ConfigParser() cfg.read(CONFIG) PASSWORD_FILE = '{}/.ssh/resticpass'.format(HOME_DIR) exclude_file = os.path.expanduser(cfg['restic']['exclude']) restic_uri = cfg['restic']['repo_uri'] BACKUP_CMD = sh.restic.bake('backup', '--password-file', PASSWORD_FILE, '--exclude-file', exclude_file, '--exclude-caches', '--repo', restic_uri) PRUNE_CMD = sh.restic.bake('forget', '--password-file', PASSWORD_FILE, '--repo', restic_uri, '--keep-daily', 7, '--keep-weekly', 5, '--keep-monthly', 12, '--keep-yearly', 75) CHECK_CMD = sh.restic.bake('check', '--password-file', PASSWORD_FILE, '--repo', restic_uri) def _check_auth(): try: os.environ['SSH_AUTH_SOCK'] except KeyError: log.error('ssh agent not running') def run_backup(): directories = [] for chunk in glob.iglob('{}/*'.format(HOME_DIR)): if os.path.isdir(chunk): try: out = BACKUP_CMD(chunk) directories.append(chunk) log.info('{} {}'.format('restic-run', chunk)) except sh.ErrorReturnCode_1 as e: log.error('{} {} {}'.format('restic-run', chunk, e)) # backs up non chunked files with tempfile.NamedTemporaryFile(delete=True) as tmp: tmp.write('\n'.join(directories).encode()) out = BACKUP_CMD('--exclude-file', tmp.name, HOME_DIR) log.info('{} {}'.format('restic-run', HOME_DIR)) def run_prune(): try: out = PRUNE_CMD() except sh.ErrorReturnCode_1 as e: log.error('{} {}'.format('restic-prune', e)) def run_check(): try: out = CHECK_CMD() except sh.ErrorReturnCode_1 as e: log.error('{} {}'.format('restic-check', e)) def follow(logfile, pattern): # catch up for line in logfile.readlines(): if pattern in line: yield line logfile.seek(0, 2) while True: line = logfile.readline() if not line: time.sleep(0.1) continue if pattern in line: yield line def run_watch(): logfile = open("restic.log","r") loglines = follow(logfile, 'ERROR') # TODO notification for line in loglines: print(line) def main(): _check_auth() cmd_arg = parser.parse_args() cmd = 'run_{}'.format(cmd_arg.cmd) try: run = globals()[cmd] except KeyError: log.error('command {} not found'.format(cmd)) sys.exit(1) run()