Refactor backend and add registry build workflow
Build and publish Docker image / build-and-publish (push) Successful in 2m45s

This commit is contained in:
wfalkowski committed 2026-10-20 16:44:49 +00:00
commit c177d7981d
43 files changed
+18124

No files matched your search

+244
View File
@@ -0,0 +1,244 @@
"""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")