73 lines
2.0 KiB
Python
73 lines
2.0 KiB
Python
"""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()
|