Force connection HTTPS, proxy-inversé, WSGI de production, reset cookie de conncection à chaque reconnection, limite sur les mdp, fichiers et One on One par minute, verification d'injection de SQL dans les champs d'entrées. renommage des fichiers lors du téléchargement, fichier de backup quotidien pour la bd et j'ai oublié quelque chose :(
184 lines
5.5 KiB
Python
184 lines
5.5 KiB
Python
"""Database backup script for the Team Tryouts application.
|
|
|
|
This module provides a simple backup mechanism for the SQLite database
|
|
and uploaded contract documents. Designed to be run as a scheduled task
|
|
(Windows Task Scheduler) or cron job.
|
|
|
|
Usage:
|
|
python backup.py
|
|
|
|
Configuration via environment variables:
|
|
BACKUP_DIR: Directory to store backups (default: ./backups)
|
|
BACKUP_RETENTION_DAYS: Number of days to keep backups (default: 30)
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import sqlite3
|
|
from datetime import datetime, timedelta
|
|
|
|
# Configuration
|
|
BACKUP_DIR = os.getenv('BACKUP_DIR', os.path.join(os.getcwd(), 'backups'))
|
|
BACKUP_RETENTION_DAYS = int(os.getenv('BACKUP_RETENTION_DAYS', 30))
|
|
DATABASE_PATH = os.getenv('DATABASE_PATH', os.path.join(os.getcwd(), 'instance', 'team_tryouts.db'))
|
|
DOCUMENTS_DIR = os.path.join(os.getcwd(), 'documents')
|
|
|
|
|
|
def create_backup_dir():
|
|
"""Create the backup directory if it doesn't exist."""
|
|
os.makedirs(BACKUP_DIR, exist_ok=True)
|
|
|
|
|
|
def backup_database():
|
|
"""Backup the SQLite database using sqlite3's built-in backup API.
|
|
|
|
Returns:
|
|
str: Path to the created backup file, or None if failed.
|
|
"""
|
|
if not os.path.exists(DATABASE_PATH):
|
|
print(f'[WARNING] Database not found at {DATABASE_PATH}. Skipping database backup.')
|
|
return None
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
backup_filename = f'db_backup_{timestamp}.db'
|
|
backup_path = os.path.join(BACKUP_DIR, backup_filename)
|
|
|
|
try:
|
|
source = sqlite3.connect(DATABASE_PATH)
|
|
destination = sqlite3.connect(backup_path)
|
|
source.backup(destination)
|
|
source.close()
|
|
destination.close()
|
|
print(f'[OK] Database backed up to: {backup_path}')
|
|
return backup_path
|
|
except Exception as e:
|
|
print(f'[ERROR] Database backup failed: {e}')
|
|
return None
|
|
|
|
|
|
def backup_documents():
|
|
"""Backup the uploaded contract documents directory.
|
|
|
|
Returns:
|
|
str: Path to the created archive, or None if no documents exist.
|
|
"""
|
|
if not os.path.exists(DOCUMENTS_DIR):
|
|
print('[INFO] No documents directory found. Skipping document backup.')
|
|
return None
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
archive_basename = f'documents_backup_{timestamp}'
|
|
archive_path = os.path.join(BACKUP_DIR, archive_basename)
|
|
|
|
try:
|
|
shutil.make_archive(archive_path, 'zip', DOCUMENTS_DIR)
|
|
zip_path = f'{archive_path}.zip'
|
|
print(f'[OK] Documents backed up to: {zip_path}')
|
|
return zip_path
|
|
except Exception as e:
|
|
print(f'[ERROR] Document backup failed: {e}')
|
|
return None
|
|
|
|
|
|
def cleanup_old_backups():
|
|
"""Remove backup files older than BACKUP_RETENTION_DAYS."""
|
|
if not os.path.exists(BACKUP_DIR):
|
|
return
|
|
|
|
cutoff = datetime.now() - timedelta(days=BACKUP_RETENTION_DAYS)
|
|
removed_count = 0
|
|
|
|
for filename in os.listdir(BACKUP_DIR):
|
|
file_path = os.path.join(BACKUP_DIR, filename)
|
|
if os.path.isfile(file_path):
|
|
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
|
|
if file_time < cutoff:
|
|
try:
|
|
os.remove(file_path)
|
|
removed_count += 1
|
|
print(f'[CLEANUP] Removed old backup: {filename}')
|
|
except OSError as e:
|
|
print(f'[WARNING] Could not remove {filename}: {e}')
|
|
|
|
if removed_count > 0:
|
|
print(f'[CLEANUP] Removed {removed_count} old backup(s).')
|
|
else:
|
|
print('[CLEANUP] No old backups to remove.')
|
|
|
|
|
|
def verify_backup(backup_path):
|
|
"""Verify a database backup by running a quick integrity check.
|
|
|
|
Args:
|
|
backup_path: Path to the backup file to verify.
|
|
|
|
Returns:
|
|
bool: True if backup is valid, False otherwise.
|
|
"""
|
|
if not backup_path or not os.path.exists(backup_path):
|
|
return False
|
|
|
|
try:
|
|
conn = sqlite3.connect(backup_path)
|
|
cursor = conn.cursor()
|
|
cursor.execute('PRAGMA integrity_check')
|
|
result = cursor.fetchone()
|
|
conn.close()
|
|
is_valid = result[0] == 'ok'
|
|
if is_valid:
|
|
print(f'[OK] Backup integrity verified: {backup_path}')
|
|
else:
|
|
print(f'[ERROR] Backup integrity check failed: {backup_path} - {result[0]}')
|
|
return is_valid
|
|
except Exception as e:
|
|
print(f'[ERROR] Backup verification failed: {e}')
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Run the full backup process.
|
|
|
|
Steps:
|
|
1. Create backup directory
|
|
2. Backup database
|
|
3. Backup documents (if any)
|
|
4. Verify database backup
|
|
5. Clean up old backups
|
|
|
|
Returns:
|
|
int: 0 on success, 1 on failure.
|
|
"""
|
|
print(f'=== Team Tryouts Backup ===')
|
|
print(f'Started at: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
|
print(f'Backup directory: {BACKUP_DIR}')
|
|
print(f'Retention period: {BACKUP_RETENTION_DAYS} days')
|
|
print()
|
|
|
|
create_backup_dir()
|
|
|
|
# 1. Backup database
|
|
db_backup_path = backup_database()
|
|
success = True
|
|
|
|
# 2. Verify database backup
|
|
if db_backup_path:
|
|
if not verify_backup(db_backup_path):
|
|
success = False
|
|
|
|
# 3. Backup documents
|
|
backup_documents()
|
|
|
|
# 4. Cleanup old backups
|
|
cleanup_old_backups()
|
|
|
|
print()
|
|
if success:
|
|
print('=== Backup completed successfully ===')
|
|
else:
|
|
print('=== Backup completed with warnings ===')
|
|
|
|
return 0 if success else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main()) |