mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-30 23:59:02 +03:00
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* fix watchdog process restarts reverting to the boot config /api/config/set parses a new FrigateConfig and swaps the API and dispatcher onto it, but FrigateApp.config was never rebound, so the watchdog factories rebuilt a crashed process from the config as of startup. Fix is to read through a ConfigHolder that the swap updates. * fix birdseye camera overrides being clobbered by a global mode change A global birdseye save published only the global object, leaving the output process to infer which cameras were inheriting by comparing against the previous global mode. That cannot tell an inherited value from an explicit one that happens to match, so it overwrote the override until a restart. Publish the per-camera values the config parse already resolved instead. * Reject non-finite numbers in GenAI review descriptions A model returning NaN for confidence or potential_threat_level slipped through the model_construct fallback, which skips validation, and was written into the review segment's JSON data. NaN is not valid JSON, so every subsequent /review request failed with "Out of range float values are not JSON compliant", blanking the review page for any time range containing the poisoned row. * restore fused DetectionOutput in the OpenVINO SSD model conversion * fix rgb swap issue for face dataset testing script
191 lines
6.5 KiB
Python
191 lines
6.5 KiB
Python
import asyncio
|
|
import logging
|
|
import re
|
|
|
|
from fastapi import Depends, FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
from joserfc.jwk import OctKey
|
|
from playhouse.sqliteq import SqliteQueueDatabase
|
|
from slowapi import _rate_limit_exceeded_handler
|
|
from slowapi.errors import RateLimitExceeded
|
|
from slowapi.middleware import SlowAPIMiddleware
|
|
from starlette_context import middleware, plugins
|
|
from starlette_context.plugins import Plugin
|
|
|
|
from frigate.api import app as main_app
|
|
from frigate.api import (
|
|
auth,
|
|
camera,
|
|
chat,
|
|
classification,
|
|
debug_replay,
|
|
event,
|
|
export,
|
|
media,
|
|
motion_search,
|
|
notification,
|
|
preview,
|
|
record,
|
|
review,
|
|
)
|
|
from frigate.api.auth import get_jwt_secret, limiter, require_admin_by_default
|
|
from frigate.comms.dispatcher import Dispatcher
|
|
from frigate.comms.event_metadata_updater import (
|
|
EventMetadataPublisher,
|
|
)
|
|
from frigate.config import FrigateConfig
|
|
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
|
from frigate.config.holder import ConfigHolder
|
|
from frigate.config.profile_manager import ProfileManager
|
|
from frigate.debug_replay import DebugReplayManager, debug_replay_auto_stop_watchdog
|
|
from frigate.embeddings import EmbeddingsContext
|
|
from frigate.genai import GenAIClientManager
|
|
from frigate.ptz.onvif import OnvifController
|
|
from frigate.stats.emitter import StatsEmitter
|
|
from frigate.storage import StorageMaintainer
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def check_csrf(request: Request) -> bool:
|
|
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
|
|
return True
|
|
if "origin" in request.headers and "x-csrf-token" not in request.headers:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
# Used to retrieve the remote-user header: https://starlette-context.readthedocs.io/en/latest/plugins.html#easy-mode
|
|
class RemoteUserPlugin(Plugin):
|
|
key = "Remote-User"
|
|
|
|
|
|
def create_fastapi_app(
|
|
frigate_config: FrigateConfig,
|
|
database: SqliteQueueDatabase,
|
|
embeddings: EmbeddingsContext | None,
|
|
detected_frames_processor,
|
|
storage_maintainer: StorageMaintainer,
|
|
onvif: OnvifController,
|
|
stats_emitter: StatsEmitter,
|
|
event_metadata_updater: EventMetadataPublisher,
|
|
config_publisher: CameraConfigUpdatePublisher,
|
|
replay_manager: DebugReplayManager,
|
|
dispatcher: Dispatcher | None = None,
|
|
profile_manager: ProfileManager | None = None,
|
|
enforce_default_admin: bool = True,
|
|
config_holder: ConfigHolder | None = None,
|
|
):
|
|
logger.info("Starting FastAPI app")
|
|
app = FastAPI(
|
|
debug=False,
|
|
swagger_ui_parameters={"apisSorter": "alpha", "operationsSorter": "alpha"},
|
|
dependencies=[Depends(require_admin_by_default())]
|
|
if enforce_default_admin
|
|
else [],
|
|
)
|
|
|
|
# update the request_address with the x-forwarded-for header from nginx
|
|
# https://starlette-context.readthedocs.io/en/latest/plugins.html#forwarded-for
|
|
app.add_middleware(
|
|
middleware.ContextMiddleware,
|
|
plugins=(plugins.ForwardedForPlugin(),),
|
|
)
|
|
|
|
# Middleware to connect to DB before and close connection after request
|
|
# https://github.com/fastapi/full-stack-fastapi-template/issues/224#issuecomment-737423886
|
|
# https://fastapi.tiangolo.com/tutorial/middleware/#before-and-after-the-response
|
|
@app.middleware("http")
|
|
async def frigate_middleware(request: Request, call_next):
|
|
# Before request
|
|
if not check_csrf(request):
|
|
return JSONResponse(
|
|
content={"success": False, "message": "Missing CSRF header"},
|
|
status_code=401,
|
|
)
|
|
|
|
if database.is_closed():
|
|
database.connect()
|
|
|
|
response = await call_next(request)
|
|
|
|
# After request https://stackoverflow.com/a/75487519
|
|
if not database.is_closed():
|
|
database.close()
|
|
return response
|
|
|
|
@app.on_event("startup")
|
|
async def startup():
|
|
logger.info("FastAPI started")
|
|
asyncio.create_task(
|
|
debug_replay_auto_stop_watchdog(
|
|
replay_manager, frigate_config, config_publisher
|
|
)
|
|
)
|
|
|
|
# Rate limiter (used for login endpoint)
|
|
if frigate_config.auth.failed_login_rate_limit is None:
|
|
limiter.enabled = False
|
|
else:
|
|
auth.rateLimiter.set_limit(frigate_config.auth.failed_login_rate_limit)
|
|
|
|
app.state.limiter = limiter
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
app.add_middleware(SlowAPIMiddleware)
|
|
|
|
# Routes
|
|
# Order of include_router matters: https://fastapi.tiangolo.com/tutorial/path-params/#order-matters
|
|
app.include_router(auth.router)
|
|
app.include_router(camera.router)
|
|
app.include_router(chat.router)
|
|
app.include_router(classification.router)
|
|
app.include_router(review.router)
|
|
app.include_router(main_app.router)
|
|
app.include_router(preview.router)
|
|
app.include_router(notification.router)
|
|
app.include_router(export.router)
|
|
app.include_router(event.router)
|
|
app.include_router(media.router)
|
|
app.include_router(motion_search.router)
|
|
app.include_router(record.router)
|
|
app.include_router(debug_replay.router)
|
|
# App Properties
|
|
app.frigate_config = frigate_config
|
|
app.genai_manager = GenAIClientManager(frigate_config)
|
|
app.embeddings = embeddings
|
|
app.detected_frames_processor = detected_frames_processor
|
|
app.storage_maintainer = storage_maintainer
|
|
app.camera_error_image = None
|
|
app.onvif = onvif
|
|
app.stats_emitter = stats_emitter
|
|
app.event_metadata_updater = event_metadata_updater
|
|
app.config_publisher = config_publisher
|
|
app.replay_manager = replay_manager
|
|
app.dispatcher = dispatcher
|
|
app.profile_manager = profile_manager
|
|
app.config_holder = config_holder
|
|
|
|
if frigate_config.auth.enabled:
|
|
secret = get_jwt_secret()
|
|
key_bytes = None
|
|
if isinstance(secret, str):
|
|
# If the secret looks like hex (e.g., generated by secrets.token_hex), use raw bytes
|
|
if len(secret) % 2 == 0 and re.fullmatch(r"[0-9a-fA-F]+", secret or ""):
|
|
try:
|
|
key_bytes = bytes.fromhex(secret)
|
|
except ValueError:
|
|
key_bytes = secret.encode("utf-8")
|
|
else:
|
|
key_bytes = secret.encode("utf-8")
|
|
elif isinstance(secret, (bytes, bytearray)):
|
|
key_bytes = bytes(secret)
|
|
else:
|
|
key_bytes = str(secret).encode("utf-8")
|
|
|
|
app.jwt_token = OctKey.import_key(key_bytes)
|
|
else:
|
|
app.jwt_token = None
|
|
|
|
return app
|