mirror of
				https://github.com/bunny-lab-io/Borealis.git
				synced 2025-10-26 15:21:57 -06:00 
			
		
		
		
	Merge pull request #144 from bunny-lab-io:codex/review-code-migration-instructions
Stage 2 - Port configuration and dependency loading into the Engine factory Implemented
This commit is contained in:
		| @@ -13,11 +13,11 @@ Lastly, everytime that you complete a stage, you will create a pull request name | |||||||
|   - [x] Scaffold Data/Engine/server.py with the create_app(config) factory and stub service registration hooks. |   - [x] Scaffold Data/Engine/server.py with the create_app(config) factory and stub service registration hooks. | ||||||
|   - [x] Return a shared context object containing handles such as the database path, logger, and scheduler. |   - [x] Return a shared context object containing handles such as the database path, logger, and scheduler. | ||||||
|   - [x] Update project tooling so the Engine runtime can be launched alongside the legacy path. |   - [x] Update project tooling so the Engine runtime can be launched alongside the legacy path. | ||||||
| - [ ] **Stage 2 — Port configuration and dependency loading into the Engine factory** | - [x] **Stage 2 — Port configuration and dependency loading into the Engine factory** | ||||||
|   - [ ] Extract configuration loading logic from Data/Server/server.py into Data/Engine/config.py helpers. |   - [x] Extract configuration loading logic from Data/Server/server.py into Data/Engine/config.py helpers. | ||||||
|   - [ ] Verify context parity between Engine and legacy startup. |   - [x] Verify context parity between Engine and legacy startup. | ||||||
|   - [ ] Initialize logging to Logs/Server/server.log when Engine mode is active. |   - [x] Initialize logging to Logs/Server/server.log when Engine mode is active. | ||||||
|   - [ ] Document Engine launch paths and configuration requirements in module docstrings. |   - [x] Document Engine launch paths and configuration requirements in module docstrings. | ||||||
| - [ ] **Stage 3 — Introduce API blueprints and service adapters** | - [ ] **Stage 3 — Introduce API blueprints and service adapters** | ||||||
|   - [ ] Create domain-focused API blueprints and register_api entry point. |   - [ ] Create domain-focused API blueprints and register_api entry point. | ||||||
|   - [ ] Mirror route behaviour from the legacy server via service adapters. |   - [ ] Mirror route behaviour from the legacy server via service adapters. | ||||||
| @@ -41,3 +41,7 @@ Lastly, everytime that you complete a stage, you will create a pull request name | |||||||
|   - [ ] Provide register_realtime hook for the Engine factory. |   - [ ] Provide register_realtime hook for the Engine factory. | ||||||
|   - [ ] Add integration tests or smoke checks for key events. |   - [ ] Add integration tests or smoke checks for key events. | ||||||
|   - [ ] Update legacy server to consume Engine WebSocket registration. |   - [ ] Update legacy server to consume Engine WebSocket registration. | ||||||
|  |  | ||||||
|  | ## Current Status | ||||||
|  | - **Stage:** Stage 2 — Port configuration and dependency loading into the Engine factory (completed) | ||||||
|  | - **Active Task:** Awaiting next stage instructions. | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| """Borealis Engine runtime package. | """Borealis Engine runtime package. | ||||||
|  |  | ||||||
| This package houses the next-generation server runtime that will gradually | This package houses the next-generation server runtime that will gradually | ||||||
| replace :mod:`Data.Server.server`. Stage 1 focuses on providing a skeleton | replace :mod:`Data.Server.server`. Stage 1 delivered the structural skeleton | ||||||
| application factory and service placeholders so later stages can port | for the Flask/Socket.IO factory; Stage 2 layers in configuration loading and | ||||||
| features incrementally. | logging parity via :mod:`Data.Engine.config` so Engine launches honour the | ||||||
|  | same environment variables and log destinations as the legacy server. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from .server import create_app, EngineContext  # re-export for convenience | from .server import create_app, EngineContext  # re-export for convenience | ||||||
|   | |||||||
| @@ -1,4 +1,11 @@ | |||||||
| """Command-line bootstrapper for the Stage 1 Engine runtime.""" | """Entrypoint helpers for running the Borealis Engine runtime. | ||||||
|  |  | ||||||
|  | The bootstrapper assembles configuration via :func:`Data.Engine.config.load_runtime_config` | ||||||
|  | before delegating to :func:`Data.Engine.server.create_app`.  It mirrors the | ||||||
|  | legacy server defaults by binding to ``0.0.0.0:5001`` and honouring the | ||||||
|  | ``BOREALIS_ENGINE_*`` environment overrides for bind host/port. | ||||||
|  | """ | ||||||
|  |  | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import os | import os | ||||||
|   | |||||||
							
								
								
									
										254
									
								
								Data/Engine/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								Data/Engine/config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | |||||||
|  | """Configuration helpers for the Borealis Engine runtime. | ||||||
|  |  | ||||||
|  | Stage 2 of the migration focuses on lifting the legacy configuration loading | ||||||
|  | behaviour from :mod:`Data.Server.server` into reusable helpers so the Engine | ||||||
|  | start-up path honours the same environment variables, filesystem layout, and | ||||||
|  | logging expectations.  This module documents the supported launch parameters | ||||||
|  | and exposes typed helpers that the application factory consumes. | ||||||
|  |  | ||||||
|  | Launch overview | ||||||
|  | --------------- | ||||||
|  | The Engine can be started via :func:`Data.Engine.bootstrapper.main` or by | ||||||
|  | invoking :func:`Data.Engine.server.create_app` manually.  Configuration is | ||||||
|  | assembled from (in precedence order): | ||||||
|  |  | ||||||
|  | ``config`` mapping overrides provided to :func:`load_runtime_config`, | ||||||
|  | environment variables prefixed with ``BOREALIS_``, and finally built-in | ||||||
|  | defaults that mirror the legacy server runtime.  Key environment variables are | ||||||
|  |  | ||||||
|  | ``BOREALIS_DATABASE_PATH``  path to the SQLite database file.  Defaults to | ||||||
|  | ``<ProjectRoot>/database.db``. | ||||||
|  | ``BOREALIS_CORS_ORIGINS``   comma separated list of allowed origins for CORS. | ||||||
|  | ``BOREALIS_SECRET``         Flask session secret key. | ||||||
|  | ``BOREALIS_COOKIE_*``       Session cookie policies (``SAMESITE``, ``SECURE``, | ||||||
|  |                             ``DOMAIN``). | ||||||
|  | ``BOREALIS_TLS_*``          TLS certificate, private key, and bundle paths. | ||||||
|  |  | ||||||
|  | When TLS values are not provided explicitly the Engine falls back to the | ||||||
|  | certificate helper shipped with the legacy server, ensuring bundling parity. | ||||||
|  | Logs are written to ``Logs/Server/server.log`` with daily rotation so the new | ||||||
|  | runtime integrates with existing operational practices. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | import os | ||||||
|  | from dataclasses import asdict, dataclass, field | ||||||
|  | from logging.handlers import TimedRotatingFileHandler | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Any, List, Mapping, MutableMapping, Optional, Sequence | ||||||
|  |  | ||||||
|  | try:  # pragma: no-cover - optional dependency during early migration stages. | ||||||
|  |     from Modules.crypto import certificates  # type: ignore | ||||||
|  | except Exception:  # pragma: no-cover - Engine configuration still works without it. | ||||||
|  |     certificates = None  # type: ignore[assignment] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ENGINE_DIR = Path(__file__).resolve().parent | ||||||
|  | PROJECT_ROOT = ENGINE_DIR.parent.parent | ||||||
|  | DEFAULT_DATABASE_PATH = PROJECT_ROOT / "database.db" | ||||||
|  | LOG_FILE_PATH = PROJECT_ROOT / "Logs" / "Server" / "server.log" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _ensure_parent(path: Path) -> None: | ||||||
|  |     try: | ||||||
|  |         path.parent.mkdir(parents=True, exist_ok=True) | ||||||
|  |     except Exception: | ||||||
|  |         # Directory creation failure is non-fatal; subsequent file operations | ||||||
|  |         # will surface the issue with clearer context. | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _resolve_static_folder() -> str: | ||||||
|  |     candidates = [ | ||||||
|  |         ENGINE_DIR / "web-interface" / "build", | ||||||
|  |         ENGINE_DIR / "web-interface" / "dist", | ||||||
|  |         ENGINE_DIR / "web-interface", | ||||||
|  |     ] | ||||||
|  |     for candidate in candidates: | ||||||
|  |         absolute = candidate.resolve() | ||||||
|  |         if absolute.is_dir(): | ||||||
|  |             return str(absolute) | ||||||
|  |     return str(candidates[0].resolve()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _parse_origins(raw: Optional[Any]) -> Optional[List[str]]: | ||||||
|  |     if raw is None: | ||||||
|  |         return None | ||||||
|  |     if isinstance(raw, str): | ||||||
|  |         parts = [part.strip() for part in raw.split(",")] | ||||||
|  |     elif isinstance(raw, Sequence): | ||||||
|  |         parts = [str(part).strip() for part in raw] | ||||||
|  |     else: | ||||||
|  |         return None | ||||||
|  |     origins = [part for part in parts if part] | ||||||
|  |     return origins or None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _parse_bool(raw: Any, *, default: bool = False) -> bool: | ||||||
|  |     if raw is None: | ||||||
|  |         return default | ||||||
|  |     if isinstance(raw, bool): | ||||||
|  |         return raw | ||||||
|  |     lowered = str(raw).strip().lower() | ||||||
|  |     if lowered in {"1", "true", "yes", "on"}: | ||||||
|  |         return True | ||||||
|  |     if lowered in {"0", "false", "no", "off"}: | ||||||
|  |         return False | ||||||
|  |     return default | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _discover_tls_material(config: Mapping[str, Any]) -> Sequence[Optional[str]]: | ||||||
|  |     cert_path = config.get("TLS_CERT_PATH") or os.environ.get("BOREALIS_TLS_CERT") or None | ||||||
|  |     key_path = config.get("TLS_KEY_PATH") or os.environ.get("BOREALIS_TLS_KEY") or None | ||||||
|  |     bundle_path = config.get("TLS_BUNDLE_PATH") or os.environ.get("BOREALIS_TLS_BUNDLE") or None | ||||||
|  |  | ||||||
|  |     if certificates and not all([cert_path, key_path, bundle_path]): | ||||||
|  |         try: | ||||||
|  |             auto_cert, auto_key, auto_bundle = certificates.certificate_paths() | ||||||
|  |         except Exception: | ||||||
|  |             auto_cert = auto_key = auto_bundle = None | ||||||
|  |         else: | ||||||
|  |             cert_path = cert_path or auto_cert | ||||||
|  |             key_path = key_path or auto_key | ||||||
|  |             bundle_path = bundle_path or auto_bundle | ||||||
|  |  | ||||||
|  |     if cert_path: | ||||||
|  |         os.environ.setdefault("BOREALIS_TLS_CERT", str(cert_path)) | ||||||
|  |     if key_path: | ||||||
|  |         os.environ.setdefault("BOREALIS_TLS_KEY", str(key_path)) | ||||||
|  |     if bundle_path: | ||||||
|  |         os.environ.setdefault("BOREALIS_TLS_BUNDLE", str(bundle_path)) | ||||||
|  |  | ||||||
|  |     return cert_path, key_path, bundle_path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class EngineSettings: | ||||||
|  |     """Resolved configuration values for the Engine runtime.""" | ||||||
|  |  | ||||||
|  |     database_path: str | ||||||
|  |     static_folder: str | ||||||
|  |     cors_origins: Optional[List[str]] | ||||||
|  |     secret_key: str | ||||||
|  |     session_cookie_samesite: str | ||||||
|  |     session_cookie_secure: bool | ||||||
|  |     session_cookie_domain: Optional[str] | ||||||
|  |     tls_cert_path: Optional[str] | ||||||
|  |     tls_key_path: Optional[str] | ||||||
|  |     tls_bundle_path: Optional[str] | ||||||
|  |     log_file: str | ||||||
|  |     raw: MutableMapping[str, Any] = field(default_factory=dict) | ||||||
|  |  | ||||||
|  |     def to_flask_config(self) -> MutableMapping[str, Any]: | ||||||
|  |         config: MutableMapping[str, Any] = { | ||||||
|  |             "SESSION_COOKIE_HTTPONLY": True, | ||||||
|  |             "SESSION_COOKIE_SAMESITE": self.session_cookie_samesite, | ||||||
|  |             "SESSION_COOKIE_SECURE": self.session_cookie_secure, | ||||||
|  |             "PREFERRED_URL_SCHEME": "https", | ||||||
|  |         } | ||||||
|  |         if self.session_cookie_domain: | ||||||
|  |             config["SESSION_COOKIE_DOMAIN"] = self.session_cookie_domain | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     def as_dict(self) -> MutableMapping[str, Any]: | ||||||
|  |         data = asdict(self) | ||||||
|  |         data["raw"] = dict(self.raw) | ||||||
|  |         return data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_runtime_config(overrides: Optional[Mapping[str, Any]] = None) -> EngineSettings: | ||||||
|  |     """Resolve Engine configuration values. | ||||||
|  |  | ||||||
|  |     Parameters | ||||||
|  |     ---------- | ||||||
|  |     overrides: | ||||||
|  |         Optional mapping of explicit configuration values.  These take | ||||||
|  |         precedence over environment variables and built-in defaults. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     runtime_config: MutableMapping[str, Any] = dict(overrides or {}) | ||||||
|  |  | ||||||
|  |     database_path = str( | ||||||
|  |         runtime_config.get("DATABASE_PATH") | ||||||
|  |         or os.environ.get("BOREALIS_DATABASE_PATH") | ||||||
|  |         or DEFAULT_DATABASE_PATH | ||||||
|  |     ) | ||||||
|  |     database_path = os.path.abspath(database_path) | ||||||
|  |     _ensure_parent(Path(database_path)) | ||||||
|  |  | ||||||
|  |     static_folder = str(runtime_config.get("STATIC_FOLDER") or _resolve_static_folder()) | ||||||
|  |  | ||||||
|  |     cors_origins = _parse_origins( | ||||||
|  |         runtime_config.get("CORS_ORIGINS") or os.environ.get("BOREALIS_CORS_ORIGINS") | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     secret_key = str(runtime_config.get("SECRET_KEY") or os.environ.get("BOREALIS_SECRET") or "borealis-dev-secret") | ||||||
|  |  | ||||||
|  |     session_cookie_samesite = str( | ||||||
|  |         runtime_config.get("SESSION_COOKIE_SAMESITE") | ||||||
|  |         or os.environ.get("BOREALIS_COOKIE_SAMESITE") | ||||||
|  |         or "Lax" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     session_cookie_secure = _parse_bool( | ||||||
|  |         runtime_config.get("SESSION_COOKIE_SECURE"), | ||||||
|  |         default=_parse_bool(os.environ.get("BOREALIS_COOKIE_SECURE"), default=False), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     session_cookie_domain = runtime_config.get("SESSION_COOKIE_DOMAIN") or os.environ.get("BOREALIS_COOKIE_DOMAIN") | ||||||
|  |     session_cookie_domain = str(session_cookie_domain) if session_cookie_domain else None | ||||||
|  |  | ||||||
|  |     tls_cert_path, tls_key_path, tls_bundle_path = _discover_tls_material(runtime_config) | ||||||
|  |  | ||||||
|  |     log_file = str(runtime_config.get("LOG_FILE") or LOG_FILE_PATH) | ||||||
|  |     _ensure_parent(Path(log_file)) | ||||||
|  |  | ||||||
|  |     settings = EngineSettings( | ||||||
|  |         database_path=database_path, | ||||||
|  |         static_folder=static_folder, | ||||||
|  |         cors_origins=cors_origins, | ||||||
|  |         secret_key=secret_key, | ||||||
|  |         session_cookie_samesite=session_cookie_samesite, | ||||||
|  |         session_cookie_secure=session_cookie_secure, | ||||||
|  |         session_cookie_domain=session_cookie_domain, | ||||||
|  |         tls_cert_path=tls_cert_path if tls_cert_path else None, | ||||||
|  |         tls_key_path=tls_key_path if tls_key_path else None, | ||||||
|  |         tls_bundle_path=tls_bundle_path if tls_bundle_path else None, | ||||||
|  |         log_file=str(log_file), | ||||||
|  |         raw=runtime_config, | ||||||
|  |     ) | ||||||
|  |     return settings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def initialise_engine_logger(settings: EngineSettings, name: str = "borealis.engine") -> logging.Logger: | ||||||
|  |     """Configure the Engine logger to write to the shared server log.""" | ||||||
|  |  | ||||||
|  |     logger = logging.getLogger(name) | ||||||
|  |     if not logger.handlers: | ||||||
|  |         formatter = logging.Formatter("%(asctime)s-%(name)s-%(levelname)s: %(message)s") | ||||||
|  |  | ||||||
|  |         stream_handler = logging.StreamHandler() | ||||||
|  |         stream_handler.setFormatter(formatter) | ||||||
|  |         logger.addHandler(stream_handler) | ||||||
|  |  | ||||||
|  |         file_handler = TimedRotatingFileHandler( | ||||||
|  |             settings.log_file, | ||||||
|  |             when="midnight", | ||||||
|  |             backupCount=0, | ||||||
|  |             encoding="utf-8", | ||||||
|  |         ) | ||||||
|  |         file_handler.setFormatter(formatter) | ||||||
|  |         logger.addHandler(file_handler) | ||||||
|  |  | ||||||
|  |     logger.setLevel(logging.INFO) | ||||||
|  |     logger.propagate = False | ||||||
|  |     return logger | ||||||
|  |  | ||||||
|  |  | ||||||
|  | __all__ = [ | ||||||
|  |     "EngineSettings", | ||||||
|  |     "initialise_engine_logger", | ||||||
|  |     "load_runtime_config", | ||||||
|  | ] | ||||||
| @@ -1,18 +1,20 @@ | |||||||
| """Stage 1 Borealis Engine application factory. | """Stage 2 Borealis Engine application factory. | ||||||
|  |  | ||||||
| This module establishes the foundational structure for the Engine runtime so | Stage 1 introduced the structural skeleton for the Engine runtime.  Stage 2 | ||||||
| subsequent migration stages can progressively assume responsibility for the | builds upon that foundation by centralising configuration handling and logging | ||||||
| API, WebUI, and WebSocket layers. | 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/Server/server.log`` to align with the | ||||||
|  | project's operational practices. | ||||||
| """ | """ | ||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
| import os |  | ||||||
| import ssl | import ssl | ||||||
| import sys | import sys | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Any, Mapping, MutableMapping, Optional, Tuple | from typing import Any, Mapping, Optional, Tuple | ||||||
|  |  | ||||||
| import eventlet | import eventlet | ||||||
| from flask import Flask | from flask import Flask | ||||||
| @@ -87,10 +89,7 @@ for root in _SEARCH_ROOTS: | |||||||
|         if root_str not in sys.path: |         if root_str not in sys.path: | ||||||
|             sys.path.insert(0, root_str) |             sys.path.insert(0, root_str) | ||||||
|  |  | ||||||
| try:  # pragma: no-cover - optional during Stage 1 scaffolding. | from .config import EngineSettings, initialise_engine_logger, load_runtime_config | ||||||
|     from Modules.crypto import certificates  # type: ignore |  | ||||||
| except Exception:  # pragma: no-cover - Engine can start without certificate helpers. |  | ||||||
|     certificates = None  # type: ignore[assignment] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| @@ -109,103 +108,27 @@ class EngineContext: | |||||||
| __all__ = ["EngineContext", "create_app"] | __all__ = ["EngineContext", "create_app"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def _resolve_static_folder() -> str: |  | ||||||
|     candidates = [ |  | ||||||
|         _ENGINE_DIR / "web-interface" / "build", |  | ||||||
|         _ENGINE_DIR / "web-interface" / "dist", |  | ||||||
|         _ENGINE_DIR / "web-interface", |  | ||||||
|     ] |  | ||||||
|     for candidate in candidates: |  | ||||||
|         absolute = candidate.resolve() |  | ||||||
|         if absolute.is_dir(): |  | ||||||
|             return str(absolute) |  | ||||||
|     # Fall back to the first candidate to maintain parity with the legacy |  | ||||||
|     # behaviour where the folder may not exist yet. |  | ||||||
|     return str(candidates[0].resolve()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _initialise_logger(name: str = "borealis.engine") -> logging.Logger: |  | ||||||
|     logger = logging.getLogger(name) |  | ||||||
|     if not logger.handlers: |  | ||||||
|         handler = logging.StreamHandler() |  | ||||||
|         formatter = logging.Formatter("%(asctime)s-%(name)s-%(levelname)s: %(message)s") |  | ||||||
|         handler.setFormatter(formatter) |  | ||||||
|         logger.addHandler(handler) |  | ||||||
|     logger.setLevel(logging.INFO) |  | ||||||
|     logger.propagate = False |  | ||||||
|     return logger |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _discover_tls_material(config: Mapping[str, Any]) -> Tuple[Optional[str], Optional[str], Optional[str]]: |  | ||||||
|     cert_path = str(config.get("TLS_CERT_PATH") or os.environ.get("BOREALIS_TLS_CERT") or "") or None |  | ||||||
|     key_path = str(config.get("TLS_KEY_PATH") or os.environ.get("BOREALIS_TLS_KEY") or "") or None |  | ||||||
|     bundle_path = str(config.get("TLS_BUNDLE_PATH") or os.environ.get("BOREALIS_TLS_BUNDLE") or "") or None |  | ||||||
|  |  | ||||||
|     if certificates and not all([cert_path, key_path, bundle_path]): |  | ||||||
|         try: |  | ||||||
|             auto_cert, auto_key, auto_bundle = certificates.certificate_paths() |  | ||||||
|         except Exception: |  | ||||||
|             auto_cert = auto_key = auto_bundle = None |  | ||||||
|         else: |  | ||||||
|             cert_path = cert_path or auto_cert |  | ||||||
|             key_path = key_path or auto_key |  | ||||||
|             bundle_path = bundle_path or auto_bundle |  | ||||||
|  |  | ||||||
|     if cert_path: |  | ||||||
|         os.environ.setdefault("BOREALIS_TLS_CERT", cert_path) |  | ||||||
|     if key_path: |  | ||||||
|         os.environ.setdefault("BOREALIS_TLS_KEY", key_path) |  | ||||||
|     if bundle_path: |  | ||||||
|         os.environ.setdefault("BOREALIS_TLS_BUNDLE", bundle_path) |  | ||||||
|     return cert_path, key_path, bundle_path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _coerce_config(source: Optional[Mapping[str, Any]]) -> MutableMapping[str, Any]: |  | ||||||
|     if source is None: |  | ||||||
|         return {} |  | ||||||
|     return dict(source) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_app(config: Optional[Mapping[str, Any]] = None) -> Tuple[Flask, SocketIO, EngineContext]: | def create_app(config: Optional[Mapping[str, Any]] = None) -> Tuple[Flask, SocketIO, EngineContext]: | ||||||
|     """Create the Stage 1 Engine Flask application.""" |     """Create the Stage 2 Engine Flask application.""" | ||||||
|  |  | ||||||
|     runtime_config: MutableMapping[str, Any] = _coerce_config(config) |     settings: EngineSettings = load_runtime_config(config) | ||||||
|     logger = _initialise_logger() |     logger = initialise_engine_logger(settings) | ||||||
|  |  | ||||||
|     database_path = runtime_config.get("DATABASE_PATH") or os.path.abspath( |     database_path = settings.database_path | ||||||
|         os.path.join(_ENGINE_DIR, "..", "..", "database.db") |  | ||||||
|     ) |  | ||||||
|     os.makedirs(os.path.dirname(database_path), exist_ok=True) |  | ||||||
|  |  | ||||||
|     static_folder = _resolve_static_folder() |     static_folder = settings.static_folder | ||||||
|     app = Flask(__name__, static_folder=static_folder, static_url_path="") |     app = Flask(__name__, static_folder=static_folder, static_url_path="") | ||||||
|     app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) |     app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) | ||||||
|  |  | ||||||
|     cors_origins = runtime_config.get("CORS_ORIGINS") or os.environ.get("BOREALIS_CORS_ORIGINS") |     cors_origins = settings.cors_origins | ||||||
|     if cors_origins: |     if cors_origins: | ||||||
|         origins = [origin.strip() for origin in str(cors_origins).split(",") if origin.strip()] |         CORS(app, supports_credentials=True, origins=cors_origins) | ||||||
|         CORS(app, supports_credentials=True, origins=origins) |  | ||||||
|     else: |     else: | ||||||
|         CORS(app, supports_credentials=True) |         CORS(app, supports_credentials=True) | ||||||
|  |  | ||||||
|     app.secret_key = runtime_config.get("SECRET_KEY") or os.environ.get("BOREALIS_SECRET", "borealis-dev-secret") |     app.secret_key = settings.secret_key | ||||||
|  |  | ||||||
|     app.config.update( |     app.config.update(settings.to_flask_config()) | ||||||
|         SESSION_COOKIE_HTTPONLY=True, |  | ||||||
|         SESSION_COOKIE_SAMESITE=runtime_config.get( |  | ||||||
|             "SESSION_COOKIE_SAMESITE", |  | ||||||
|             os.environ.get("BOREALIS_COOKIE_SAMESITE", "Lax"), |  | ||||||
|         ), |  | ||||||
|         SESSION_COOKIE_SECURE=bool( |  | ||||||
|             str(runtime_config.get("SESSION_COOKIE_SECURE", os.environ.get("BOREALIS_COOKIE_SECURE", "0"))).lower() |  | ||||||
|             in {"1", "true", "yes"} |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     app.config.setdefault("PREFERRED_URL_SCHEME", "https") |  | ||||||
|  |  | ||||||
|     cookie_domain = runtime_config.get("SESSION_COOKIE_DOMAIN") or os.environ.get("BOREALIS_COOKIE_DOMAIN") |  | ||||||
|     if cookie_domain: |  | ||||||
|         app.config["SESSION_COOKIE_DOMAIN"] = cookie_domain |  | ||||||
|  |  | ||||||
|     socketio = SocketIO( |     socketio = SocketIO( | ||||||
|         app, |         app, | ||||||
| @@ -217,7 +140,11 @@ def create_app(config: Optional[Mapping[str, Any]] = None) -> Tuple[Flask, Socke | |||||||
|         }, |         }, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     tls_cert_path, tls_key_path, tls_bundle_path = _discover_tls_material(runtime_config) |     tls_cert_path, tls_key_path, tls_bundle_path = ( | ||||||
|  |         settings.tls_cert_path, | ||||||
|  |         settings.tls_key_path, | ||||||
|  |         settings.tls_bundle_path, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     context = EngineContext( |     context = EngineContext( | ||||||
|         database_path=database_path, |         database_path=database_path, | ||||||
| @@ -226,7 +153,7 @@ def create_app(config: Optional[Mapping[str, Any]] = None) -> Tuple[Flask, Socke | |||||||
|         tls_cert_path=tls_cert_path, |         tls_cert_path=tls_cert_path, | ||||||
|         tls_key_path=tls_key_path, |         tls_key_path=tls_key_path, | ||||||
|         tls_bundle_path=tls_bundle_path, |         tls_bundle_path=tls_bundle_path, | ||||||
|         config=runtime_config, |         config=settings.as_dict(), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     from .services import API, WebSocket, WebUI  # Local import to avoid circular deps during bootstrap |     from .services import API, WebSocket, WebUI  # Local import to avoid circular deps during bootstrap | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user