"""Background scheduler for periodic SFTP backups.""" import os import threading import time from datetime import datetime, timezone, timedelta _scheduler_thread = None _scheduler_running = False def start_backup_scheduler(app): """Start the background backup scheduler.""" global _scheduler_thread, _scheduler_running if _scheduler_running: return _scheduler_running = True def scheduler_loop(): while _scheduler_running: try: with app.app_context(): _check_and_run_backups(app) except Exception as e: print(f'[Backup Scheduler] Error: {e}') # Check every 60 seconds for _ in range(60): if not _scheduler_running: break time.sleep(1) _scheduler_thread = threading.Thread(target=scheduler_loop, daemon=True) _scheduler_thread.start() def stop_backup_scheduler(): global _scheduler_running _scheduler_running = False def _check_and_run_backups(app): """Check all active backup targets and run if due.""" from app.extensions import db from app.models.backup_target import BackupTarget from app.services.sftp_backup import create_backup_zip, upload_backup_to_sftp targets = BackupTarget.query.filter_by(is_active=True).all() now = datetime.now(timezone.utc) for target in targets: if not target.backup_interval_minutes or target.backup_interval_minutes <= 0: continue # Check if backup is due if target.last_backup_at: next_due = target.last_backup_at + timedelta(minutes=target.backup_interval_minutes) if now < next_due: continue # Run backup db_uri = app.config['SQLALCHEMY_DATABASE_URI'] db_path = db_uri.replace('sqlite:///', '') upload_path = app.config['UPLOAD_PATH'] zip_path = None try: zip_path = create_backup_zip(db_path, upload_path) version = upload_backup_to_sftp(target, zip_path, app) target.last_backup_at = now target.last_backup_status = 'success' target.last_backup_message = f'Version {version} erfolgreich hochgeladen' db.session.commit() print(f'[Backup] {target.name}: {version} OK') except Exception as e: target.last_backup_at = now target.last_backup_status = 'error' target.last_backup_message = str(e)[:500] db.session.commit() print(f'[Backup] {target.name}: FEHLER - {e}') finally: if zip_path and os.path.exists(zip_path): os.unlink(zip_path)