brief-extractor/backend/run_server.py
2026-03-06 18:42:46 +00:00

123 lines
No EOL
3.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Startup script for Brief Extractor GUI server
"""
import sys
import os
import logging
from pathlib import Path
# Add server and core paths to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'server'))
# Set up logging before importing modules
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
async def async_main():
"""Async main function with proper signal handling"""
import asyncio
import signal
# Import after path setup
from server.app import create_app
from server.config_runtime import server_config
# Validate configuration
if not server_config.validate_auth_config():
if not server_config.DEV_MODE:
logger.error("MSAL authentication configuration is incomplete")
logger.error("Please set MSAL_CLIENT_ID, MSAL_CLIENT_SECRET, and MSAL_TENANT_ID in .env")
sys.exit(1)
else:
logger.warning("Running in DEV_MODE - MSAL authentication bypassed")
# Create application
logger.info("Creating Brief Extractor GUI application...")
app = create_app()
# Import and configure Hypercorn
import hypercorn.asyncio
from hypercorn import Config
config = Config()
config.bind = [f"{server_config.HOST}:{server_config.PORT}"]
config.workers = server_config.WORKERS
config.use_reloader = server_config.DEBUG
config.accesslog = "-" # Log to stdout
config.errorlog = "-" # Log to stderr
# Log startup information
logger.info(f"Starting Brief Extractor GUI server")
logger.info(f"Server: http://{server_config.HOST}:{server_config.PORT}")
logger.info(f"Development mode: {server_config.DEV_MODE}")
logger.info(f"Max concurrent jobs: {server_config.MAX_CONCURRENT_JOBS}")
logger.info(f"Max upload size: {server_config.MAX_UPLOAD_SIZE_MB}MB")
logger.info(f"File retention: {server_config.FILE_RETENTION_HOURS} hours")
logger.info(f"Workers: {server_config.WORKERS}")
# Set up proper signal handling for graceful shutdown
shutdown_event = asyncio.Event()
def signal_handler():
logger.info("Shutdown signal received, stopping server...")
shutdown_event.set()
# Force shutdown after 3 seconds if graceful shutdown fails
def force_shutdown():
import time
time.sleep(3)
logger.warning("Graceful shutdown timed out, forcing exit...")
os._exit(1)
import threading
threading.Thread(target=force_shutdown, daemon=True).start()
# Register signal handlers
if sys.platform != 'win32':
loop = asyncio.get_running_loop()
loop.add_signal_handler(signal.SIGINT, signal_handler)
loop.add_signal_handler(signal.SIGTERM, signal_handler)
try:
# Start server with shutdown trigger
await hypercorn.asyncio.serve(app, config, shutdown_trigger=shutdown_event.wait)
logger.info("Server stopped gracefully")
except asyncio.CancelledError:
logger.info("Server cancelled")
except Exception as e:
logger.error(f"Server error: {e}", exc_info=True)
raise
def main():
"""Main entry point"""
import asyncio
import signal
# Set up immediate signal handling before async loop
def immediate_shutdown(signum, frame):
logger.info(f"Immediate shutdown signal {signum} received")
os._exit(0)
signal.signal(signal.SIGINT, immediate_shutdown)
signal.signal(signal.SIGTERM, immediate_shutdown)
try:
asyncio.run(async_main())
except KeyboardInterrupt:
logger.info("Server stopped by user")
os._exit(0)
except Exception as e:
logger.error(f"Server failed to start: {e}", exc_info=True)
os._exit(1)
if __name__ == '__main__':
main()