Skip to content

Instantly share code, notes, and snippets.

@Sevaarcen
Created September 25, 2021 14:52
Show Gist options
  • Save Sevaarcen/01fca3bf1ce1c85354564e461e68b196 to your computer and use it in GitHub Desktop.
Save Sevaarcen/01fca3bf1ce1c85354564e461e68b196 to your computer and use it in GitHub Desktop.
import os
import sys
import logging
import datetime
import time
# Sets up environment
if len(sys.argv) < 2:
print('MUST SPECIFY THE BASE KEEPASS DIRECTORY')
print('Usage: python KeePass_Backup.py [directory_path]')
exit(1)
BASE_PATH = sys.argv[1]
if not os.path.isdir(BASE_PATH):
print(f'{BASE_PATH} is not a directory!')
exit(1)
BACKUP_REL_PATH = "_BACKUPS"
BACKUP_ABS_PATH = f'{BASE_PATH}\\{BACKUP_REL_PATH}'
LOG_REL_PATH = "_LOGS"
LOG_ABS_PATH = f'{BACKUP_ABS_PATH}\\{LOG_REL_PATH}'
# These are global constants
STORE_TIME_GLOBAL = 7
STORE_TIME_LOGS = 30
LOG_FORMAT = '%(levelname)s %(asctime)s - %(message)s'
log_name = f"KeePass_Backup_{datetime.datetime.now().strftime('%Y%m%d')}.log"
# THE FOLLOWING CREATES THE FILE STRUCTURE IF IT DOESN'T EXIST YET
# Create backup folder if it doesn't exist yet
try:
os.chdir(BACKUP_ABS_PATH)
except FileNotFoundError:
os.chdir(BASE_PATH)
os.makedirs(BACKUP_REL_PATH)
os.chdir(BACKUP_REL_PATH)
# Create Log folder if it doesn't exist yet
try:
os.chdir(LOG_ABS_PATH)
except FileNotFoundError:
os.chdir(BACKUP_ABS_PATH)
os.makedirs(LOG_REL_PATH)
os.chdir(LOG_REL_PATH)
# Set up logging
logging.basicConfig(filename=log_name, level=logging.INFO, format=LOG_FORMAT)
logging.info(f'Script launched at {datetime.datetime.now()}')
logging.debug(f'Working from directory: {BASE_PATH}')
os.chdir(BASE_PATH) # Set working directory so I can use relative paths
def main():
logging.info(f'Processing contents of {BASE_PATH}')
for filename in os.listdir():
logging.debug(f'processing {filename}')
if filename[filename.rfind("."):] == ".kdbx" or filename[filename.rfind("."):] == ".kdb": #KeePass database formats
logging.debug(f'Found KeePass2 Database: {filename}')
backup_file(filename)
logging.info(f'Clearing backups older than {STORE_TIME_GLOBAL} days')
clear_old_files(BACKUP_ABS_PATH, STORE_TIME_GLOBAL)
logging.info(f'Clearing logs older than {STORE_TIME_LOGS} days')
clear_old_files(LOG_ABS_PATH, STORE_TIME_LOGS)
def backup_file(database_name):
logging.info(f'Backing up {database_name} now')
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M')
database_name_base = database_name[:database_name.rfind(".")]
database_name_ext = database_name[database_name.rfind("."):]
backup_tag = f'_BACKUP_{timestamp}'
try:
with open(database_name, "rb") as original, open(BACKUP_ABS_PATH+"\\"+database_name_base+backup_tag+database_name_ext, "wb") as copy:
logging.info(f'KeePass database {original.name} is being backed up to {copy.name}')
copy.write(original.read())
logging.debug('... done')
try: #Attempts to backup matching XML file, which stores the 2FA data. The name must be the same.
with open(database_name_base+".xml", "rb") as original, open(BACKUP_ABS_PATH+"\\"+database_name_base+backup_tag+".xml", "wb") as copy:
logging.info(f'Matching XML data found as {original.name} and will be backed up to {copy.name}')
copy.write(original.read())
logging.debug('... done')
except FileNotFoundError:
logging.warning(f'No matching XML file found for {database_name}')
except Exception:
logging.critical(f'Uhhhhh... Something went very wrong when a known trying to backup {database_name}')
exit(1)
def clear_old_files(directory, max_age):
if not os.path.isdir(directory):
logging.critical(f'clear_old_files() expected a directory, was given a {type(directory)}')
logging.warning(f'{directory} will not be cleared!')
return
logging.info(f'Clearing old files from {directory}')
os.chdir(directory)
for filename in os.listdir(directory):
logging.debug(f'Determining fate of {filename}')
if os.path.isdir(filename):
logging.debug(f'{filename} is a directory and is being ignored')
continue
epoch_difference = time.time() - os.path.getmtime(filename)
days_old = epoch_difference/60/60/24
logging.debug(f'{filename} is {days_old:.2f} days old')
if days_old > max_age:
logging.info(f'{filename} is {days_old:.2f} days old and will be deleted.')
try:
os.remove(filename)
logging.debug('... done')
except Exception:
logging.critical(f'{filename} could not be deleted!')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment