import os from collections.abc import Mapping from pathlib import Path from typing import Any from flask import Flask from werkzeug.middleware.proxy_fix import ProxyFix from config import CONFIGS from .extensions import csrf, db, login_manager, migrate from .core.security import configure_security_headers def create_app( config_name: str | None = None, config_overrides: Mapping[str, Any] | None = None, ) -> Flask: """Create and configure an isolated Steady application instance.""" app = Flask(__name__, instance_relative_config=True) selected_config = config_name or os.environ.get("STEADY_ENV", "development") try: app.config.from_object(CONFIGS[selected_config]) except KeyError as exc: valid_names = ", ".join(sorted(CONFIGS)) raise ValueError( f"Unknown configuration {selected_config!r}; choose {valid_names}." ) from exc if config_overrides: app.config.from_mapping(config_overrides) Path(app.instance_path).mkdir(parents=True, exist_ok=True) _validate_config(app) _configure_proxy_headers(app) _initialize_extensions(app) _register_blueprints(app) configure_security_headers(app) return app def _validate_config(app: Flask) -> None: if not app.config.get("SECRET_KEY"): raise RuntimeError("SECRET_KEY must be set outside development and testing.") def _configure_proxy_headers(app: Flask) -> None: """Trust one reverse proxy only when explicitly enabled. The production container is not published on a host port, so its only HTTP peer on the Docker network is the Caddy reverse proxy. """ if app.config.get("TRUST_PROXY_HEADERS"): app.wsgi_app = ProxyFix( app.wsgi_app, x_for=1, x_proto=1, x_host=1, ) def _initialize_extensions(app: Flask) -> None: db.init_app(app) migrate.init_app(app, db) login_manager.init_app(app) csrf.init_app(app) login_manager.login_view = "auth.login" login_manager.login_message_category = "info" def _register_blueprints(app: Flask) -> None: # Imports stay local so feature modules do not create circular imports. from .auth import bp as auth_blueprint from .core import bp as core_blueprint from .settings import bp as settings_blueprint from .tasks import bp as tasks_blueprint app.register_blueprint(core_blueprint) app.register_blueprint(auth_blueprint, url_prefix="/auth") app.register_blueprint(tasks_blueprint, url_prefix="/tasks") app.register_blueprint(settings_blueprint, url_prefix="/settings") if app.config["FEATURE_ADMIN"]: from .admin import bp as admin_blueprint app.register_blueprint(admin_blueprint, url_prefix="/admin")