aboutsummaryrefslogtreecommitdiff
path: root/restic.py
blob: daf4c40094fba9f6626201319967168d12952040 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
BSD 2-Clause License
restic subprocess wrapper
Copyright (c) 2018, Max Resnick <max@ofmax.li>
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}
# TODO should be a configurable location
logging.basicConfig(filename='~/.local/logs/restic.log',
                    level=LOGLEVELS[os.getenv('RESTIC_LOGLEVEL', 'WARNING')])
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('restic.ini')

# baked commands
HOME_DIR = os.path.expanduser('~')
PASSWORD_FILE = '{}/.ssh/resticpass'.format(HOME_DIR)
BACKUP_CMD = sh.restic.bake('backup',
                            '--password-file', PASSWORD_FILE,
                            '--exclude-file', cfg['restic']['exclude'],
                            '--exclude-caches',
                            '--repo', cfg['restic']['repo_uri'])
PRUNE_CMD = sh.restic.bake('forget',
                           '--password-file', PASSWORD_FILE,
                           '--repo', cfg['restic']['repo_uri'],
                           '--keep-daily', 7,
                           '--keep-weekly', 5,
                           '--keep-monthly', 12,
                           '--keep-yearly', 75)
CHECK_CMD = sh.restic.bake('check',
                           '--password-file', PASSWORD_FILE,
                           '--repo', cfg['restic']['repo_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()