Files
Metamorph/backend/app/main.py

73 lines
2.0 KiB
Python
Raw Normal View History

2026-05-11 06:16:00 +02:00
"""Flask application factory and WSGI entry point."""
from __future__ import annotations
import logging
from flask import Flask
from flask_cors import CORS
from app.api.v1 import bp as v1_bp
from app.core.config import settings
from app.core.install_token import (
ensure_install_token,
log_install_token_banner,
)
from app.core.logging import configure_logging
from app.core.rate_limit import limiter
from app.services.bootstrap import ensure_system_groups
from app.services.permissions_seed import seed_all as seed_permissions_and_bindings
def _try_bootstrap_at_boot(log: logging.Logger) -> None:
"""Best-effort: seed system groups + mint an install token if needed.
Wrapped in try/except because the DB may not be ready (or schema not
migrated yet) at the very first boot gunicorn must still come up so the
operator can run `make migrate` and curl /setup afterwards.
"""
try:
ensure_system_groups()
seed_permissions_and_bindings()
token = ensure_install_token()
if token is not None:
log_install_token_banner(token)
else:
log.info("metamorph.bootstrap.skipped")
except Exception as e:
log.warning("metamorph.bootstrap.deferred", extra={"error": str(e)})
def create_app() -> Flask:
configure_logging(settings.LOG_LEVEL)
log = logging.getLogger("metamorph.boot")
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024 # 64 MB hard cap; per-file limit is 25 MB.
CORS(
app,
origins=settings.cors_origins,
supports_credentials=True,
max_age=600,
)
limiter.init_app(app)
app.register_blueprint(v1_bp)
log.info(
"metamorph.api.boot",
extra={
"cors_origins": settings.cors_origins,
"log_level": settings.LOG_LEVEL,
"evidence_dir": settings.EVIDENCE_DIR,
},
)
_try_bootstrap_at_boot(log)
return app
# WSGI entry point used by gunicorn (`gunicorn app.main:app`).
app = create_app()