Created
September 25, 2021 14:52
-
-
Save Sevaarcen/01fca3bf1ce1c85354564e461e68b196 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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