# steward/config.py from __future__ import annotations import logging import os import secrets from pathlib import Path from typing import Any logger = logging.getLogger(__name__) _SECRET_KEY_FILE = Path("/data/secret.key") def _env(suffix: str) -> str | None: return os.environ.get(f"STEWARD_{suffix}") def load_bootstrap(config_path: Path | str | None = None) -> dict[str, str]: """Return the minimum bootstrap config: database_url and secret_key. This is the only config read from files/env vars at startup. Everything else is stored in the app_settings DB table. config_path is optional — used only for backwards compatibility with existing config.yaml deployments. The file is not required. """ from dotenv import load_dotenv load_dotenv() raw: dict[str, Any] = {} if config_path is None: config_path = Path("config.yaml") config_path = Path(config_path) if config_path.exists(): import yaml with config_path.open() as f: raw = yaml.safe_load(f) or {} database_url = ( _env("DATABASE_URL") or _env("DATABASE__URL") or raw.get("database", {}).get("url") ) if not database_url: raise ValueError( "Database URL is required. Set STEWARD_DATABASE_URL env var " "or add 'database.url' to config.yaml." ) secret_key = _resolve_secret_key(raw) plugin_dir = ( _env("PLUGIN_DIR") or raw.get("plugin_dir", "plugins") ) return { "database_url": database_url, "secret_key": secret_key, "plugin_dir": plugin_dir, } def _resolve_secret_key(raw: dict) -> str: """Resolve secret_key: env var → file → auto-generate.""" from_env = _env("SECRET_KEY") or raw.get("secret_key") if from_env: return from_env if _SECRET_KEY_FILE.exists(): key = _SECRET_KEY_FILE.read_text().strip() if key: return key key = secrets.token_hex(32) try: _SECRET_KEY_FILE.parent.mkdir(parents=True, exist_ok=True) _SECRET_KEY_FILE.write_text(key) logger.info("Generated new secret key and saved to %s", _SECRET_KEY_FILE) except OSError as exc: logger.warning( "Could not write secret key to %s (%s). " "Key will not persist across restarts.", _SECRET_KEY_FILE, exc, ) return key