Files
Borealis-Github-Replica/Data/Engine/config/environment.py

219 lines
6.7 KiB
Python

"""Environment detection for the Borealis Engine."""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, Tuple
@dataclass(frozen=True, slots=True)
class DatabaseSettings:
"""SQLite database configuration for the Engine."""
path: Path
apply_migrations: bool
@dataclass(frozen=True, slots=True)
class FlaskSettings:
"""Parameters that influence Flask application behavior."""
secret_key: str
static_root: Path
cors_allowed_origins: Tuple[str, ...]
@dataclass(frozen=True, slots=True)
class SocketIOSettings:
"""Configuration for the optional Socket.IO server."""
cors_allowed_origins: Tuple[str, ...]
@dataclass(frozen=True, slots=True)
class ServerSettings:
"""HTTP server binding configuration."""
host: str
port: int
@dataclass(frozen=True, slots=True)
class GitHubSettings:
"""Configuration surface for GitHub repository interactions."""
default_repo: str
default_branch: str
refresh_interval_seconds: int
cache_root: Path
@property
def cache_file(self) -> Path:
"""Location of the persisted repository-head cache."""
return self.cache_root / "repo_hash_cache.json"
@dataclass(frozen=True, slots=True)
class EngineSettings:
"""Immutable container describing the Engine runtime configuration."""
project_root: Path
debug: bool
database: DatabaseSettings
flask: FlaskSettings
socketio: SocketIOSettings
server: ServerSettings
github: GitHubSettings
@property
def logs_root(self) -> Path:
"""Return the directory where Engine-specific logs should live."""
return self.project_root / "Logs" / "Server"
@property
def database_path(self) -> Path:
"""Convenience accessor for the database file path."""
return self.database.path
@property
def apply_migrations(self) -> bool:
"""Return whether schema migrations should run at bootstrap."""
return self.database.apply_migrations
def _resolve_project_root() -> Path:
candidate = os.getenv("BOREALIS_ROOT")
if candidate:
return Path(candidate).expanduser().resolve()
# ``environment.py`` lives under ``Data/Engine/config``. The project
# root is three levels above this module (the repository checkout). The
# previous implementation only walked up two levels which incorrectly
# treated ``Data/`` as the root, breaking all filesystem discovery logic
# that expects peers such as ``Data/Server`` to be available.
return Path(__file__).resolve().parents[3]
def _resolve_database_path(project_root: Path) -> Path:
candidate = os.getenv("BOREALIS_DATABASE_PATH")
if candidate:
return Path(candidate).expanduser().resolve()
return (project_root / "database.db").resolve()
def _should_apply_migrations() -> bool:
raw = os.getenv("BOREALIS_ENGINE_AUTO_MIGRATE", "true")
return raw.lower() in {"1", "true", "yes", "on"}
def _resolve_static_root(project_root: Path) -> Path:
candidate = os.getenv("BOREALIS_STATIC_ROOT")
if candidate:
return Path(candidate).expanduser().resolve()
candidates = (
project_root / "Engine" / "web-interface" / "build",
project_root / "Engine" / "web-interface" / "dist",
project_root / "Engine" / "web-interface",
project_root / "Data" / "Engine" / "web-interface" / "build",
project_root / "Data" / "Engine" / "web-interface",
project_root / "Server" / "web-interface" / "build",
project_root / "Server" / "web-interface",
project_root / "Data" / "Server" / "WebUI" / "build",
project_root / "Data" / "Server" / "WebUI",
project_root / "Data" / "Server" / "web-interface" / "build",
project_root / "Data" / "Server" / "web-interface",
project_root / "Data" / "WebUI" / "build",
project_root / "Data" / "WebUI",
)
for path in candidates:
resolved = path.resolve()
if resolved.is_dir():
return resolved
# Fall back to the first candidate even if it does not yet exist so the
# Flask factory still initialises; individual requests will surface 404s
# until an asset build is available, matching the legacy behaviour.
return candidates[0].resolve()
def _resolve_github_cache_root(project_root: Path) -> Path:
candidate = os.getenv("BOREALIS_CACHE_DIR")
if candidate:
return Path(candidate).expanduser().resolve()
return (project_root / "Data" / "Engine" / "cache").resolve()
def _parse_refresh_interval(raw: str | None) -> int:
if not raw:
return 60
try:
value = int(raw)
except ValueError:
value = 60
return max(30, min(value, 3600))
def _parse_origins(raw: str | None) -> Tuple[str, ...]:
if not raw:
return ("*",)
parts: Iterable[str] = (segment.strip() for segment in raw.split(","))
filtered = tuple(part for part in parts if part)
return filtered or ("*",)
def load_environment() -> EngineSettings:
"""Load Engine settings from environment variables and filesystem hints."""
project_root = _resolve_project_root()
database = DatabaseSettings(
path=_resolve_database_path(project_root),
apply_migrations=_should_apply_migrations(),
)
cors_allowed_origins = _parse_origins(os.getenv("BOREALIS_CORS_ALLOWED_ORIGINS"))
flask_settings = FlaskSettings(
secret_key=os.getenv("BOREALIS_FLASK_SECRET_KEY", "change-me"),
static_root=_resolve_static_root(project_root),
cors_allowed_origins=cors_allowed_origins,
)
socket_settings = SocketIOSettings(cors_allowed_origins=cors_allowed_origins)
debug = os.getenv("BOREALIS_DEBUG", "false").lower() in {"1", "true", "yes", "on"}
host = os.getenv("BOREALIS_HOST", "127.0.0.1")
try:
port = int(os.getenv("BOREALIS_PORT", "5000"))
except ValueError:
port = 5000
server_settings = ServerSettings(host=host, port=port)
github_settings = GitHubSettings(
default_repo=os.getenv("BOREALIS_REPO", "bunny-lab-io/Borealis"),
default_branch=os.getenv("BOREALIS_REPO_BRANCH", "main"),
refresh_interval_seconds=_parse_refresh_interval(os.getenv("BOREALIS_REPO_HASH_REFRESH")),
cache_root=_resolve_github_cache_root(project_root),
)
return EngineSettings(
project_root=project_root,
debug=debug,
database=database,
flask=flask_settings,
socketio=socket_settings,
server=server_settings,
github=github_settings,
)
__all__ = [
"DatabaseSettings",
"EngineSettings",
"FlaskSettings",
"GitHubSettings",
"SocketIOSettings",
"ServerSettings",
"load_environment",
]