diff options
| -rwxr-xr-x | tarsnap-backup.py | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/tarsnap-backup.py b/tarsnap-backup.py new file mode 100755 index 0000000..e85617b --- /dev/null +++ b/tarsnap-backup.py @@ -0,0 +1,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)) |
