mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-10 08:19:23 +00:00
211 lines
8.0 KiB
Python
211 lines
8.0 KiB
Python
# password_manager/backup.py
|
|
|
|
"""
|
|
Backup Manager Module
|
|
|
|
This module implements the BackupManager class, responsible for creating backups,
|
|
restoring from backups, and listing available backups for the encrypted password
|
|
index file. It ensures data integrity and provides mechanisms to recover from
|
|
corrupted or lost data by maintaining timestamped backups.
|
|
|
|
Dependencies:
|
|
- shutil
|
|
- time
|
|
- logging
|
|
- pathlib
|
|
- colorama
|
|
- termcolor
|
|
|
|
Ensure that all dependencies are installed and properly configured in your environment.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import time
|
|
import logging
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
from colorama import Fore
|
|
from termcolor import colored
|
|
|
|
from constants import APP_DIR, INDEX_FILE
|
|
from utils.file_lock import lock_file
|
|
|
|
# Configure logging at the start of the module
|
|
def configure_logging():
|
|
"""
|
|
Configures logging with both file and console handlers.
|
|
Only ERROR and higher-level messages are shown in the terminal, while all messages
|
|
are logged in the log file.
|
|
"""
|
|
# Create the 'logs' folder if it doesn't exist
|
|
if not os.path.exists('logs'):
|
|
os.makedirs('logs')
|
|
|
|
# Create a custom logger
|
|
logger = logging.getLogger()
|
|
logger.setLevel(logging.DEBUG) # Set to DEBUG for detailed output
|
|
|
|
# Create handlers
|
|
c_handler = logging.StreamHandler()
|
|
f_handler = logging.FileHandler(os.path.join('logs', 'backup_manager.log')) # Log files will be in 'logs' folder
|
|
|
|
# Set levels: only errors and critical messages will be shown in the console
|
|
c_handler.setLevel(logging.ERROR) # Terminal will show ERROR and above
|
|
f_handler.setLevel(logging.DEBUG) # File will log everything from DEBUG and above
|
|
|
|
# Create formatters and add them to handlers, include file and line number in log messages
|
|
c_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
|
|
f_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]')
|
|
|
|
c_handler.setFormatter(c_format)
|
|
f_handler.setFormatter(f_format)
|
|
|
|
# Add handlers to the logger
|
|
logger.addHandler(c_handler)
|
|
logger.addHandler(f_handler)
|
|
|
|
# Call the logging configuration function
|
|
configure_logging()
|
|
|
|
class BackupManager:
|
|
"""
|
|
BackupManager Class
|
|
|
|
Handles the creation, restoration, and listing of backups for the encrypted
|
|
password index file. Backups are stored in the application directory with
|
|
timestamped filenames to facilitate easy identification and retrieval.
|
|
"""
|
|
|
|
BACKUP_FILENAME_TEMPLATE = 'passwords_db_backup_{timestamp}.json.enc'
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initializes the BackupManager with the application directory and index file paths.
|
|
"""
|
|
self.app_dir = APP_DIR
|
|
self.index_file = INDEX_FILE
|
|
logging.debug(f"BackupManager initialized with APP_DIR: {self.app_dir} and INDEX_FILE: {self.index_file}")
|
|
|
|
def create_backup(self) -> None:
|
|
"""
|
|
Creates a timestamped backup of the encrypted password index file.
|
|
|
|
The backup file is named using the current Unix timestamp to ensure uniqueness.
|
|
If the index file does not exist, no backup is created.
|
|
|
|
Raises:
|
|
Exception: If the backup process fails due to I/O errors.
|
|
"""
|
|
if not self.index_file.exists():
|
|
logging.warning("Index file does not exist. No backup created.")
|
|
print(colored("Warning: Index file does not exist. No backup created.", 'yellow'))
|
|
return
|
|
|
|
timestamp = int(time.time())
|
|
backup_filename = self.BACKUP_FILENAME_TEMPLATE.format(timestamp=timestamp)
|
|
backup_file = self.app_dir / backup_filename
|
|
|
|
try:
|
|
with lock_file(self.index_file, lock_type=fcntl.LOCK_SH):
|
|
shutil.copy2(self.index_file, backup_file)
|
|
logging.info(f"Backup created successfully at '{backup_file}'.")
|
|
print(colored(f"Backup created successfully at '{backup_file}'.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to create backup: {e}")
|
|
logging.error(traceback.format_exc()) # Log full traceback
|
|
print(colored(f"Error: Failed to create backup: {e}", 'red'))
|
|
|
|
def restore_latest_backup(self) -> None:
|
|
"""
|
|
Restores the encrypted password index file from the latest available backup.
|
|
|
|
The latest backup is determined based on the Unix timestamp in the backup filenames.
|
|
If no backups are found, an error message is displayed.
|
|
|
|
Raises:
|
|
Exception: If the restoration process fails due to I/O errors or missing backups.
|
|
"""
|
|
backup_files = sorted(
|
|
self.app_dir.glob('passwords_db_backup_*.json.enc'),
|
|
key=lambda x: x.stat().st_mtime,
|
|
reverse=True
|
|
)
|
|
|
|
if not backup_files:
|
|
logging.error("No backup files found to restore.")
|
|
print(colored("Error: No backup files found to restore.", 'red'))
|
|
return
|
|
|
|
latest_backup = backup_files[0]
|
|
try:
|
|
with lock_file(latest_backup, lock_type=fcntl.LOCK_SH):
|
|
shutil.copy2(latest_backup, self.index_file)
|
|
logging.info(f"Restored the index file from backup '{latest_backup}'.")
|
|
print(colored(f"Restored the index file from backup '{latest_backup}'.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to restore from backup '{latest_backup}': {e}")
|
|
logging.error(traceback.format_exc()) # Log full traceback
|
|
print(colored(f"Error: Failed to restore from backup '{latest_backup}': {e}", 'red'))
|
|
|
|
def list_backups(self) -> None:
|
|
"""
|
|
Lists all available backups in the application directory, sorted by date.
|
|
|
|
Displays the backups with their filenames and creation dates.
|
|
"""
|
|
backup_files = sorted(
|
|
self.app_dir.glob('passwords_db_backup_*.json.enc'),
|
|
key=lambda x: x.stat().st_mtime,
|
|
reverse=True
|
|
)
|
|
|
|
if not backup_files:
|
|
logging.info("No backup files available.")
|
|
print(colored("No backup files available.", 'yellow'))
|
|
return
|
|
|
|
print(colored("Available Backups:", 'cyan'))
|
|
for backup in backup_files:
|
|
creation_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(backup.stat().st_mtime))
|
|
print(colored(f"- {backup.name} (Created on: {creation_time})", 'cyan'))
|
|
|
|
def restore_backup_by_timestamp(self, timestamp: int) -> None:
|
|
"""
|
|
Restores the encrypted password index file from a backup with the specified timestamp.
|
|
|
|
Parameters:
|
|
timestamp (int): The Unix timestamp of the backup to restore.
|
|
|
|
Raises:
|
|
Exception: If the restoration process fails due to I/O errors or missing backups.
|
|
"""
|
|
backup_filename = self.BACKUP_FILENAME_TEMPLATE.format(timestamp=timestamp)
|
|
backup_file = self.app_dir / backup_filename
|
|
|
|
if not backup_file.exists():
|
|
logging.error(f"No backup found with timestamp {timestamp}.")
|
|
print(colored(f"Error: No backup found with timestamp {timestamp}.", 'red'))
|
|
return
|
|
|
|
try:
|
|
with lock_file(backup_file, lock_type=fcntl.LOCK_SH):
|
|
shutil.copy2(backup_file, self.index_file)
|
|
logging.info(f"Restored the index file from backup '{backup_file}'.")
|
|
print(colored(f"Restored the index file from backup '{backup_file}'.", 'green'))
|
|
except Exception as e:
|
|
logging.error(f"Failed to restore from backup '{backup_file}': {e}")
|
|
logging.error(traceback.format_exc()) # Log full traceback
|
|
print(colored(f"Error: Failed to restore from backup '{backup_file}': {e}", 'red'))
|
|
|
|
# Example usage (to be integrated within the PasswordManager class or other modules):
|
|
|
|
# from password_manager.backup import BackupManager
|
|
|
|
# backup_manager = BackupManager()
|
|
# backup_manager.create_backup()
|
|
# backup_manager.restore_latest_backup()
|
|
# backup_manager.list_backups()
|
|
# backup_manager.restore_backup_by_timestamp(1700000000) # Example timestamp
|