summaryrefslogtreecommitdiff
path: root/tarsnap-backup.py
blob: e85617be93b6ce8e8ad304d70120ff7248e83c0f (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
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from datetime import datetime
import argparse
import calendar
import logging
import os
import re
import shlex
import StringIO
import subprocess
import sys

class MyArgumentParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        super(MyArgumentParser, self).__init__(*args, **kwargs)

    def convert_arg_line_to_args(self, line):
        for arg in shlex.split(line, comments=True):
            if not arg.strip():
                continue
            yield arg

argparser = MyArgumentParser(fromfile_prefix_chars='@')
argparser.add_argument('-v', '--verbose', action='count',
    help='Produce verbose output (can be specified more than once)')
argparser.add_argument('-n', '--dry-run', action='store_true',
    help='Do not modify anything')
argparser.add_argument('-d', '--dir', action='append', required=True,
    help='Specify a directory to backup (can be given multiple times)')
argparser.add_argument('--daily', type=int, default=7,
    help='Number of daily backups to keep (default: 7)')
argparser.add_argument('--weekly', type=int, default=4,
    help='Number of weekly backups to keep (default: 4)')
argparser.add_argument('--monthly', type=int, default=2,
    help='Number of monthly backups to keep (default: 2)')
argparser.add_argument('--weekly-day', type=int, default=0,
    help='Which day to do weekly backups on (0=monday, 6=sunday)')
argparser.add_argument('--monthly-day', type=int, default=1,
    help='Which day to do monthly backups on (1-31)')

cmdline_args = argparser.parse_args()

if cmdline_args.verbose == 1:
    loglevel = logging.INFO
elif cmdline_args.verbose >= 2:
    loglevel = logging.DEBUG
else:
    loglevel = logging.WARNING

log = logging.getLogger(__name__)
logging.basicConfig(format="%(asctime)s %(levelname)-8s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S", level=loglevel)

now = datetime.now()

backup_name = now.strftime('%Y%m%d-%H%M%S')
backup_type = None

if now.day == cmdline_args.monthly_day:
    backup_type = 'monthly'
elif calendar.weekday(now.year, now.month, now.day) == cmdline_args.weekly_day:
    backup_type = 'weekly'
else:
    backup_type = 'daily'

backup_name += '-{}'.format(backup_type)

log.debug('backup type: %s', backup_type)
log.debug('backup basename: %s', backup_name)

dirs = set(cmdline_args.dir)

def tarsnap_cmd(*args):
    cmd = ['tarsnap']
    cmd += args
    return cmd

def exec_cmd(cmd, **kwargs):
    log.debug('executing %s', ' '.join(cmd))
    subprocess.check_call(cmd, **kwargs)

# create the backups
for dir in dirs:
    cmd = tarsnap_cmd('-c', '-f', '{}-{}'.format(backup_name, dir),
        dir)
    log.info('backing up %s', dir)
    if not cmdline_args.dry_run:
        exec_cmd(cmd)

# retrieve a list of archives and remove old ones
archives = os.tmpfile()

exec_cmd(tarsnap_cmd('--list-archives'), stdout=archives,
    stderr=archives)

if cmdline_args.dry_run:
    for dir in dirs:
        archives.write('{}-{}\n'.format(backup_name, dir))

delete_archives = []

def get_oldest(lst, keep):
    return sorted(lst, reverse=True)[keep:]

for dir in dirs:
    archives.seek(0)

    re_daily = re.compile(r'^\d{8}-\d{6}-daily-' + dir)
    re_weekly = re.compile(r'^\d{8}-\d{6}-weekly-' + dir)
    re_monthly = re.compile(r'^\d{8}-\d{6}-monthly-' + dir)

    daily_archives = []
    weekly_archives = []
    monthly_archives = []

    for line in archives:
        line = line.strip()
        if re_daily.match(line):
            daily_archives.append(line)
        elif re_weekly.match(line):
            weekly_archives.append(line)
        elif re_monthly.match(line):
            monthly_archives.append(line)

    delete_archives += get_oldest(daily_archives,
        cmdline_args.daily)
    delete_archives += get_oldest(weekly_archives,
        cmdline_args.weekly)
    delete_archives += get_oldest(monthly_archives,
        cmdline_args.monthly)

archives.close()

for archive in delete_archives:
    log.info('deleting archive %s', archive)

    if not cmdline_args.dry_run:
        exec_cmd(tarsnap_cmd('-d', '-f', archive))