#!/usr/bin/python3 # queries a bacula database for volume names # and then removed old volumes from disk that are no longer # referenced in the database # Copyright 2010, 2011, 2013, 2017 Peter Palfrader # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import argparse import psycopg2 import psycopg2.extras import os import re import sys from datetime import datetime, timedelta parser = argparse.ArgumentParser() parser.add_argument('-d', '--db-connect-string', metavar='connect-string', dest='db', help='Database connect string') parser.add_argument('-D', '--db-connect-string-file', metavar='FILE', dest='dbfile', default='/etc/dsa/bacula-reader-database', help='File to read database connect string from (/etc/dsa/bacula-reader-database)') parser.add_argument('-r', '--rootdir', metavar='DIR', dest='root', default='/srv/bacula', help='Directory to start walk in') parser.add_argument('-a', '--age', metavar='DAYS', dest='age', default=60.0, type=float, help='Min age to delete files') parser.add_argument('-v', '--verbose', dest='verbose', action='store_true') parser.add_argument('-n', '--no-do', dest='nodo', action='store_true') args = parser.parse_args() whitelist = ['.nobackup'] if args.db is not None: pass elif args.dbfile is not None: args.db = open(args.dbfile).read().rstrip() else: print >>sys.stderr, 'Need one of -d or -D.' sys.exit(1) cutoff = datetime.now() - timedelta(days=args.age) conn = psycopg2.connect(args.db) cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cursor.execute(""" SELECT volumename FROM media """, {}) media = set(r['volumename'] for r in cursor.fetchall()) for path, dirs, files in os.walk(args.root): dirs.sort() files.sort() for f in files: if f in media: continue if f in whitelist: continue full = os.path.join(path, f) st = os.stat(full) mtime = datetime.fromtimestamp(int(st.st_mtime)) if mtime >= cutoff: continue if not args.nodo: os.unlink(full) if args.verbose: print("%s %7d %s"%(mtime, st.st_size, full)) else: if args.verbose: print("Would delete %s %7d %s"%(mtime, st.st_size, full))