"""Background schedulers, cleanup loops, and development fixture setup.""" from currency_service import fetch_exchange_rates from database_service import database_connection, dict_from_row from domain_runtime import domain_manager from domain_service import cleanup_orphaned_ingressroutes from kubernetes_service import k8s_manager import os from runtime_config import ( CACHE_DURATION, CHUNK_UPLOAD_DIR, chunked_uploads, logger, ) import schedule from server_demo import cleanup_expired_demo_servers from server_provisioning import sync_server_ports_from_k8s, sync_server_status_from_k8s import threading import time from user_service import create_user, get_user_by_username __all__ = ['cleanup_orphaned_uploads', 'initialize_sample_data', 'start_demo_cleanup_scheduler', 'start_exchange_rate_updater', 'start_ingress_cleanup_thread', 'start_status_sync_thread', 'start_upload_cleanup_scheduler', 'sync_all_servers_status'] def cleanup_orphaned_uploads(): """Clean up orphaned chunk upload files and sessions older than 1 hour""" try: current_time = time.time() max_age = 3600 # 1 hour # Clean up old upload sessions uploads_to_remove = [] for upload_id, upload_info in chunked_uploads.items(): start_time = upload_info.get('start_time', current_time) if current_time - start_time > max_age: uploads_to_remove.append(upload_id) for upload_id in uploads_to_remove: upload_info = chunked_uploads[upload_id] temp_file = upload_info.get('temp_file') if temp_file and os.path.exists(temp_file): try: os.remove(temp_file) logger.info(f"Cleaned up orphaned upload temp file: {temp_file}") except Exception as e: logger.warning(f"Failed to remove orphaned temp file {temp_file}: {e}") del chunked_uploads[upload_id] logger.info(f"Cleaned up orphaned upload session: {upload_id}") # Clean up any remaining temp files older than 1 hour try: for filename in os.listdir(CHUNK_UPLOAD_DIR): filepath = os.path.join(CHUNK_UPLOAD_DIR, filename) if os.path.isfile(filepath): file_age = current_time - os.path.getmtime(filepath) if file_age > max_age: try: os.remove(filepath) logger.info(f"Cleaned up old temp file: {filepath}") except Exception as e: logger.warning(f"Failed to remove old temp file {filepath}: {e}") except Exception as e: logger.warning(f"Error cleaning up temp files directory: {e}") except Exception as e: logger.error(f"Error in cleanup_orphaned_uploads: {e}") def start_upload_cleanup_scheduler(): """Start scheduler to clean up orphaned uploads""" def run_scheduler(): while True: schedule.run_pending() time.sleep(60) # Check every minute # Run cleanup every 30 minutes schedule.every(30).minutes.do(cleanup_orphaned_uploads) thread = threading.Thread(target=run_scheduler, daemon=True) thread.start() logger.info("๐Ÿงน Upload cleanup scheduler started") def start_demo_cleanup_scheduler(): """Start scheduler to clean up expired demo servers""" def run_scheduler(): while True: schedule.run_pending() time.sleep(60) # Check every minute # Run cleanup every minute to ensure immediate deletion of expired demo servers schedule.every(1).minutes.do(cleanup_expired_demo_servers) thread = threading.Thread(target=run_scheduler, daemon=True) thread.start() logger.info("๐Ÿงน Demo cleanup scheduler started (runs every minute)") def start_exchange_rate_updater(): """Background thread to update exchange rates periodically""" def update_loop(): # Initial fetch fetch_exchange_rates() while True: try: time.sleep(CACHE_DURATION) # Wait 1 hour fetch_exchange_rates() except Exception as e: logger.error(f"Error in exchange rate update loop: {e}") time.sleep(60) # Wait 1 minute before retrying thread = threading.Thread(target=update_loop, daemon=True) thread.start() logger.info("โœ“ Started exchange rate updater thread") def sync_all_servers_status(): """Sync status for all servers""" if not k8s_manager.connected: return try: with database_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT id, owner_id FROM servers WHERE status IN ('Creating', 'Starting', 'Stopping', 'Restarting')") servers = [dict_from_row(row) for row in cursor.fetchall()] for server in servers: try: sync_server_status_from_k8s(server['id'], server['owner_id']) sync_server_ports_from_k8s(server['id'], server['owner_id']) except Exception as e: logger.error(f"Error syncing server {server['id']}: {e}") except Exception as e: logger.error(f"Error syncing all servers: {e}") def start_status_sync_thread(): """Start background status sync""" def sync_loop(): while True: try: sync_all_servers_status() time.sleep(30) except Exception as e: logger.error(f"Error in sync loop: {e}") time.sleep(60) thread = threading.Thread(target=sync_loop, daemon=True) thread.start() logger.info("Started background status sync") def initialize_sample_data(): """Create development fixtures only when explicitly enabled.""" if os.environ.get('ENABLE_SAMPLE_DATA', '').casefold() != 'true': logger.info("Skipping sample data; set ENABLE_SAMPLE_DATA=true to opt in") return sample_password = os.environ.get('SAMPLE_USER_PASSWORD') if not sample_password: logger.warning("Skipping sample user creation; SAMPLE_USER_PASSWORD is not configured") return logger.info("Initializing sample data...") k8s_manager.test_connection() existing_user = get_user_by_username('testuser') if not existing_user: sample_user = create_user( username='testuser', email='test@example.com', password=sample_password, credits=100 ) if sample_user: logger.info(f"โœ“ Sample user created: {sample_user['username']}") else: logger.info(f"โœ“ Sample user exists: {existing_user['username']}") # CREATE USER DOMAIN IN DNS IF IT DOESN'T EXIST if domain_manager and existing_user: try: logger.info(f"Checking/Creating domain for user: u-{existing_user['username']}.{domain_manager.base_domain}") # Check if user domain exists user_domain = f"u-{existing_user['username']}.{domain_manager.base_domain}" domain_exists = domain_manager._check_domain_exists(user_domain) if domain_exists is None: logger.error(f"Error checking domain {user_domain}") elif not domain_exists: logger.info(f"User domain doesn't exist, creating: {user_domain}") success = domain_manager._create_domain(user_domain) if success: logger.info(f"โœ“ Created user domain: {user_domain}") # SETUP DNSSEC DELEGATION for new domain logger.info("Setting up DNSSEC delegation...") dnssec_result = domain_manager.setup_dnssec_delegation(existing_user['username']) if dnssec_result.get('success'): logger.info(f"โœ“ DNSSEC delegation configured") else: logger.warning(f"โš  DNSSEC setup issues: {dnssec_result.get('message')}") # Also create a default subdomain for testing test_result = domain_manager.create_subdomain(existing_user['username'], 'test') if test_result.get('success'): logger.info(f"โœ“ Created test subdomain for user") else: logger.error(f"โœ— Failed to create test subdomain: {test_result.get('message')}") else: logger.error(f"โœ— Failed to create user domain: {user_domain}") else: logger.info(f"โœ“ User domain already exists: {user_domain}") # ENSURE DNSSEC IS PROPERLY CONFIGURED for existing domain logger.info("Ensuring DNSSEC is properly configured...") dnssec_result = domain_manager.setup_dnssec_delegation(existing_user['username']) if dnssec_result.get('success'): logger.info(f"โœ“ DNSSEC properly configured") else: logger.warning(f"โš  DNSSEC configuration issues: {dnssec_result.get('message')}") except Exception as e: logger.error(f"Error creating user domain: {e}") if k8s_manager.connected: try: k8s_manager.create_namespace('user-testuser') logger.info("โœ“ Sample namespace created") except Exception as e: logger.error(f"โš  Could not create namespace: {e}") def start_ingress_cleanup_thread(): """Start background thread to clean up orphaned IngressRoutes""" def cleanup_loop(): while True: try: cleanup_orphaned_ingressroutes() time.sleep(300) # Run every 5 minutes except Exception as e: logger.error(f"Error in ingress cleanup loop: {e}") time.sleep(60) thread = threading.Thread(target=cleanup_loop, daemon=True) thread.start() logger.info("Started background IngressRoute cleanup")