mirror of
				https://github.com/bunny-lab-io/Borealis.git
				synced 2025-10-26 15:21:57 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			223 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Stage 2 Borealis Engine application factory.
 | |
| 
 | |
| Stage 1 introduced the structural skeleton for the Engine runtime.  Stage 2
 | |
| builds upon that foundation by centralising configuration handling and logging
 | |
| initialisation so the Engine mirrors the legacy server's start-up behaviour.
 | |
| The factory delegates configuration resolution to :mod:`Data.Engine.config`
 | |
| and emits structured logs to ``Logs/Engine/engine.log`` (with an accompanying
 | |
| error log) to align with the project's operational practices.
 | |
| """
 | |
| from __future__ import annotations
 | |
| 
 | |
| import importlib.util
 | |
| import logging
 | |
| import ssl
 | |
| import sys
 | |
| from dataclasses import dataclass
 | |
| from pathlib import Path
 | |
| from typing import Any, Mapping, Optional, Sequence, Tuple
 | |
| 
 | |
| 
 | |
| def _require_dependency(module: str, friendly_name: str) -> None:
 | |
|     if importlib.util.find_spec(module) is None:  # pragma: no cover - import check
 | |
|         raise RuntimeError(
 | |
|             f"{friendly_name} (Python module '{module}') is required for the Borealis Engine runtime. "
 | |
|             "Install the packaged dependencies by running Borealis.ps1 or ensure the module is present in the active environment."
 | |
|         )
 | |
| 
 | |
| 
 | |
| _require_dependency("eventlet", "Eventlet")
 | |
| _require_dependency("flask", "Flask")
 | |
| _require_dependency("flask_socketio", "Flask-SocketIO")
 | |
| 
 | |
| import eventlet  # type: ignore  # noqa: E402  # pragma: no cover - import guarded above
 | |
| from eventlet import wsgi as eventlet_wsgi  # type: ignore  # noqa: E402  # pragma: no cover
 | |
| 
 | |
| from flask import Flask, request  # noqa: E402
 | |
| from flask_cors import CORS  # noqa: E402
 | |
| from flask_socketio import SocketIO  # noqa: E402
 | |
| from werkzeug.middleware.proxy_fix import ProxyFix  # noqa: E402
 | |
| 
 | |
| eventlet.monkey_patch(thread=False)  # pragma: no cover - aligns with legacy runtime
 | |
| 
 | |
| HttpProtocol = getattr(eventlet_wsgi, "HttpProtocol", None)
 | |
| if HttpProtocol is not None:  # pragma: no branch - attribute exists in supported versions
 | |
|     _original_handle_one_request = HttpProtocol.handle_one_request
 | |
| 
 | |
|     def _quiet_tls_http_mismatch(self):  # type: ignore[override]
 | |
|         """Mirror the legacy suppression of noisy TLS handshake errors."""
 | |
| 
 | |
|         def _close_connection_quietly():
 | |
|             try:
 | |
|                 self.close_connection = True  # type: ignore[attr-defined]
 | |
|             except Exception:
 | |
|                 pass
 | |
|             try:
 | |
|                 conn = getattr(self, "socket", None) or getattr(self, "connection", None)
 | |
|                 if conn:
 | |
|                     conn.close()
 | |
|             except Exception:
 | |
|                 pass
 | |
| 
 | |
|         try:
 | |
|             return _original_handle_one_request(self)
 | |
|         except ssl.SSLError as exc:  # type: ignore[arg-type]
 | |
|             reason = getattr(exc, "reason", "")
 | |
|             reason_text = str(reason).lower() if reason else ""
 | |
|             message = " ".join(str(arg) for arg in exc.args if arg).lower()
 | |
|             if (
 | |
|                 "http_request" in message
 | |
|                 or reason_text == "http request"
 | |
|                 or "unknown ca" in message
 | |
|                 or reason_text == "unknown ca"
 | |
|                 or "unknown_ca" in message
 | |
|             ):
 | |
|                 _close_connection_quietly()
 | |
|                 return None
 | |
|             raise
 | |
|         except ssl.SSLEOFError:
 | |
|             _close_connection_quietly()
 | |
|             return None
 | |
|         except ConnectionAbortedError:
 | |
|             _close_connection_quietly()
 | |
|             return None
 | |
| 
 | |
|     HttpProtocol.handle_one_request = _quiet_tls_http_mismatch  # type: ignore[assignment]
 | |
| 
 | |
| _SOCKETIO_ASYNC_MODE = "eventlet"
 | |
| 
 | |
| 
 | |
| # Ensure the legacy ``Modules`` package is importable when running from the
 | |
| # Engine deployment directory.
 | |
| _ENGINE_DIR = Path(__file__).resolve().parent
 | |
| _SEARCH_ROOTS = [
 | |
|     _ENGINE_DIR.parent / "Server",
 | |
|     _ENGINE_DIR.parent.parent / "Data" / "Server",
 | |
|     _ENGINE_DIR.parent.parent.parent / "Data" / "Server",
 | |
| ]
 | |
| for root in _SEARCH_ROOTS:
 | |
|     modules_dir = root / "Modules"
 | |
|     if modules_dir.is_dir():
 | |
|         root_str = str(root)
 | |
|         if root_str not in sys.path:
 | |
|             sys.path.insert(0, root_str)
 | |
| 
 | |
| from .config import EngineSettings, initialise_engine_logger, load_runtime_config
 | |
| 
 | |
| 
 | |
| @dataclass
 | |
| class EngineContext:
 | |
|     """Shared handles that Engine services will consume."""
 | |
| 
 | |
|     database_path: str
 | |
|     logger: logging.Logger
 | |
|     scheduler: Any
 | |
|     tls_cert_path: Optional[str]
 | |
|     tls_key_path: Optional[str]
 | |
|     tls_bundle_path: Optional[str]
 | |
|     config: Mapping[str, Any]
 | |
|     api_groups: Sequence[str]
 | |
| 
 | |
| 
 | |
| __all__ = ["EngineContext", "create_app", "register_engine_api"]
 | |
| 
 | |
| 
 | |
| def _build_engine_context(settings: EngineSettings, logger: logging.Logger) -> EngineContext:
 | |
|     return EngineContext(
 | |
|         database_path=settings.database_path,
 | |
|         logger=logger,
 | |
|         scheduler=None,
 | |
|         tls_cert_path=settings.tls_cert_path,
 | |
|         tls_key_path=settings.tls_key_path,
 | |
|         tls_bundle_path=settings.tls_bundle_path,
 | |
|         config=settings.as_dict(),
 | |
|         api_groups=settings.api_groups,
 | |
|     )
 | |
| 
 | |
| 
 | |
| def _attach_transition_logging(app: Flask, context: EngineContext, logger: logging.Logger) -> None:
 | |
|     tracked = {group.strip().lower() for group in context.api_groups if group}
 | |
|     if not tracked:
 | |
|         tracked = {"tokens", "enrollment"}
 | |
| 
 | |
|     existing = getattr(app, "_engine_api_tracked_blueprints", set())
 | |
|     if existing:
 | |
|         tracked.update(existing)
 | |
|     setattr(app, "_engine_api_tracked_blueprints", tracked)
 | |
| 
 | |
|     if getattr(app, "_engine_api_logging_installed", False):
 | |
|         return
 | |
| 
 | |
|     @app.before_request
 | |
|     def _log_engine_api_bridge() -> None:  # pragma: no cover - integration behaviour exercised in higher-level tests
 | |
|         blueprint = (request.blueprint or "").lower()
 | |
|         if blueprint and blueprint in getattr(app, "_engine_api_tracked_blueprints", tracked):
 | |
|             logger.info(
 | |
|                 "Engine handling API request via legacy bridge: %s %s",
 | |
|                 request.method,
 | |
|                 request.path,
 | |
|             )
 | |
| 
 | |
|     setattr(app, "_engine_api_logging_installed", True)
 | |
| 
 | |
| 
 | |
| def create_app(config: Optional[Mapping[str, Any]] = None) -> Tuple[Flask, SocketIO, EngineContext]:
 | |
|     """Create the Stage 2 Engine Flask application."""
 | |
| 
 | |
|     settings: EngineSettings = load_runtime_config(config)
 | |
|     logger = initialise_engine_logger(settings)
 | |
| 
 | |
|     static_folder = settings.static_folder
 | |
|     app = Flask(__name__, static_folder=static_folder, static_url_path="")
 | |
|     app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
 | |
| 
 | |
|     cors_origins = settings.cors_origins
 | |
|     if cors_origins:
 | |
|         CORS(app, supports_credentials=True, origins=cors_origins)
 | |
|     else:
 | |
|         CORS(app, supports_credentials=True)
 | |
| 
 | |
|     app.secret_key = settings.secret_key
 | |
| 
 | |
|     app.config.update(settings.to_flask_config())
 | |
| 
 | |
|     socketio = SocketIO(
 | |
|         app,
 | |
|         cors_allowed_origins="*",
 | |
|         async_mode=_SOCKETIO_ASYNC_MODE,
 | |
|         engineio_options={
 | |
|             "max_http_buffer_size": 100_000_000,
 | |
|             "max_websocket_message_size": 100_000_000,
 | |
|         },
 | |
|     )
 | |
| 
 | |
|     context = _build_engine_context(settings, logger)
 | |
| 
 | |
|     from .services import API, WebSocket, WebUI  # Local import to avoid circular deps during bootstrap
 | |
| 
 | |
|     API.register_api(app, context)
 | |
|     WebUI.register_web_ui(app, context)
 | |
|     WebSocket.register_realtime(socketio, context)
 | |
| 
 | |
|     logger.debug("Engine application factory completed initialisation.")
 | |
| 
 | |
|     return app, socketio, context
 | |
| 
 | |
| 
 | |
| def register_engine_api(app: Flask, *, config: Optional[Mapping[str, Any]] = None) -> EngineContext:
 | |
|     """Register Engine-managed API blueprints onto an existing Flask app."""
 | |
| 
 | |
|     settings: EngineSettings = load_runtime_config(config)
 | |
|     logger = initialise_engine_logger(settings)
 | |
|     context = _build_engine_context(settings, logger)
 | |
| 
 | |
|     from .services import API  # Local import avoids circular dependency at module import time
 | |
| 
 | |
|     API.register_api(app, context)
 | |
|     _attach_transition_logging(app, context, logger)
 | |
| 
 | |
|     groups_display = ", ".join(context.api_groups) if context.api_groups else "none"
 | |
|     logger.info("Engine API delegation activated for groups: %s", groups_display)
 | |
| 
 | |
|     return context
 |