mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-19 21:22:50 +03:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96fa39bbf7 | ||
|
|
0fc0170461 | ||
|
|
8dccc937e6 | ||
|
|
3373b1cd05 | ||
|
|
31c3638767 | ||
|
|
0aa7cf9de9 | ||
|
|
4fbde770d7 | ||
|
|
ab8b41934c |
@@ -122,14 +122,12 @@ updates:
|
|||||||
# Intermittent failure in the UI settings import spec, cause not found
|
# Intermittent failure in the UI settings import spec, cause not found
|
||||||
- dependency-name: "zod"
|
- dependency-name: "zod"
|
||||||
update-types: ["version-update:semver-major"]
|
update-types: ["version-update:semver-major"]
|
||||||
# Every later version clamps the pan when the player hides or drops
|
# Needs an on-device pinch test before moving
|
||||||
# pinch gestures near the edges, 4.2.0 included
|
|
||||||
- dependency-name: "react-zoom-pan-pinch"
|
- dependency-name: "react-zoom-pan-pinch"
|
||||||
versions: [">3.4.4"]
|
update-types: ["version-update:semver-major"]
|
||||||
# 0.54 pins a vulnerable dompurify; 0.55+ breaks YAML validation until
|
# Build fails until monaco-worker-manager releases its PR 4
|
||||||
# monaco-worker-manager releases its PR 4
|
|
||||||
- dependency-name: "monaco-editor"
|
- dependency-name: "monaco-editor"
|
||||||
versions: [">=0.54.0"]
|
versions: [">=0.55.0"]
|
||||||
# Later versions need Node 22 and extract false keys
|
# Later versions need Node 22 and extract false keys
|
||||||
- dependency-name: "i18next-cli"
|
- dependency-name: "i18next-cli"
|
||||||
versions: [">1.5.11"]
|
versions: [">1.5.11"]
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
numpy
|
numpy
|
||||||
openvino >= 2026.4.1
|
openvino >= 2026.2.0
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pandas == 3.0.*
|
|||||||
peewee == 4.5.*
|
peewee == 4.5.*
|
||||||
peewee_migrate == 2.3.*
|
peewee_migrate == 2.3.*
|
||||||
psutil == 7.2.*
|
psutil == 7.2.*
|
||||||
pydantic == 2.14.*
|
pydantic == 2.13.*
|
||||||
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
||||||
tzdata == 2026.5
|
tzdata == 2026.5
|
||||||
pyzmq == 27.2.*
|
pyzmq == 27.2.*
|
||||||
@@ -42,16 +42,16 @@ opencv-python-headless == 4.11.0.*
|
|||||||
opencv-contrib-python == 4.11.0.*
|
opencv-contrib-python == 4.11.0.*
|
||||||
scipy == 1.17.*
|
scipy == 1.17.*
|
||||||
# OpenVino & ONNX
|
# OpenVino & ONNX
|
||||||
openvino == 2026.4.*
|
openvino == 2025.4.*
|
||||||
onnxruntime == 1.30.*
|
onnxruntime == 1.30.*
|
||||||
# Embeddings
|
# Embeddings
|
||||||
transformers == 5.19.*
|
transformers == 5.19.*
|
||||||
# Generative AI
|
# Generative AI
|
||||||
google-genai == 2.31.*
|
google-genai == 2.29.*
|
||||||
ollama == 0.6.*
|
ollama == 0.6.*
|
||||||
openai == 3.26.*
|
openai == 3.26.*
|
||||||
# push notifications
|
# push notifications
|
||||||
py-vapid == 1.9.6
|
py-vapid == 1.9.4
|
||||||
pywebpush == 2.5.*
|
pywebpush == 2.5.*
|
||||||
# alpr
|
# alpr
|
||||||
pyclipper == 1.4.*
|
pyclipper == 1.4.*
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
scikit-build == 0.19.*
|
scikit-build == 0.18.*
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ location /auth {
|
|||||||
# this cannot be spoofed
|
# this cannot be spoofed
|
||||||
proxy_set_header X-Go2rtc-Stream-Proxy $go2rtc_stream_proxy;
|
proxy_set_header X-Go2rtc-Stream-Proxy $go2rtc_stream_proxy;
|
||||||
proxy_set_header X-Server-Port $server_port;
|
proxy_set_header X-Server-Port $server_port;
|
||||||
|
# inputs for the cross-origin check on websocket and go2rtc stream requests
|
||||||
|
proxy_set_header Origin $http_origin;
|
||||||
|
proxy_set_header X-Original-Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-Host $forwarded_host;
|
||||||
|
proxy_set_header X-Original-Upgrade $http_upgrade;
|
||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
# Pass along auth related info
|
# Pass along auth related info
|
||||||
proxy_set_header Authorization $http_authorization;
|
proxy_set_header Authorization $http_authorization;
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ http {
|
|||||||
default 1;
|
default 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# keep an outer proxy's X-Forwarded-Host so the websocket servers can
|
||||||
|
# match a browser's Origin when that proxy rewrites Host
|
||||||
|
map $http_x_forwarded_host $forwarded_host {
|
||||||
|
"" $http_host;
|
||||||
|
default $http_x_forwarded_host;
|
||||||
|
}
|
||||||
|
|
||||||
upstream frigate_api {
|
upstream frigate_api {
|
||||||
server 127.0.0.1:5001;
|
server 127.0.0.1:5001;
|
||||||
keepalive 1024;
|
keepalive 1024;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ proxy_set_header Upgrade $http_upgrade;
|
|||||||
proxy_set_header Connection "Upgrade";
|
proxy_set_header Connection "Upgrade";
|
||||||
proxy_set_header X-Original-URL $request_uri;
|
proxy_set_header X-Original-URL $request_uri;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $forwarded_host;
|
||||||
proxy_set_header X-Forwarded-URI $request_uri;
|
proxy_set_header X-Forwarded-URI $request_uri;
|
||||||
proxy_set_header X-Forwarded-Ssl on;
|
proxy_set_header X-Forwarded-Ssl on;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|||||||
+50
-11
@@ -2,9 +2,32 @@
|
|||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG ROCM=1
|
||||||
ARG HSA_OVERRIDE_GFX_VERSION
|
ARG HSA_OVERRIDE_GFX_VERSION
|
||||||
ARG HSA_OVERRIDE
|
ARG HSA_OVERRIDE
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
FROM wget AS rocm
|
||||||
|
|
||||||
|
ARG ROCM
|
||||||
|
|
||||||
|
RUN apt update -qq && \
|
||||||
|
apt install -y wget gpg && \
|
||||||
|
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/7.2.3/ubuntu/jammy/amdgpu-install_7.2.3.70203-1_all.deb && \
|
||||||
|
apt install -y ./rocm.deb && \
|
||||||
|
apt update && \
|
||||||
|
apt install -qq -y rocm
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib
|
||||||
|
RUN cd /opt/rocm-$ROCM/lib && \
|
||||||
|
cp -dpr libMIOpen*.so* libamd*.so* libhip*.so* libhsa*.so* libmigraphx*.so* librocm*.so* librocblas*.so* libroctracer*.so* librocsolver*.so* librocfft*.so* librocprofiler*.so* libroctx*.so* librocroller.so* /opt/rocm-dist/opt/rocm-$ROCM/lib/ && \
|
||||||
|
mkdir -p /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib && \
|
||||||
|
cp -dpr migraphx/lib/* /opt/rocm-dist/opt/rocm-$ROCM/lib/migraphx/lib
|
||||||
|
RUN cd /opt/rocm-dist/opt/ && ln -s rocm-$ROCM rocm
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/rocm-dist/etc/ld.so.conf.d/
|
||||||
|
RUN echo /opt/rocm/lib|tee /opt/rocm-dist/etc/ld.so.conf.d/rocm.conf
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
FROM deps AS deps-prelim
|
FROM deps AS deps-prelim
|
||||||
|
|
||||||
@@ -19,35 +42,51 @@ RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.
|
|||||||
rm -f /etc/apt/sources.list.d/trixie.list && \
|
rm -f /etc/apt/sources.list.d/trixie.list && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/frigate
|
||||||
|
COPY --from=rootfs / /
|
||||||
|
|
||||||
RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
|
RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
|
||||||
&& sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \
|
&& sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \
|
||||||
&& python3 get-pip.py "pip" --break-system-packages
|
&& python3 get-pip.py "pip" --break-system-packages
|
||||||
RUN python3 -m pip config set global.break-system-packages true
|
RUN python3 -m pip config set global.break-system-packages true
|
||||||
|
|
||||||
# ROCm installs from AMD's wheels ahead of Frigate's code so code changes
|
|
||||||
# reuse this layer, and is pruned in the same layer so the removed files never
|
|
||||||
# reach the image. /opt/rocm links to the runtime for rocminfo and HIP headers.
|
|
||||||
COPY docker/rocm/requirements-wheels-rocm.txt /requirements.txt
|
COPY docker/rocm/requirements-wheels-rocm.txt /requirements.txt
|
||||||
RUN --mount=type=bind,source=docker/rocm/prune_rocm.sh,target=/deps/prune_rocm.sh \
|
RUN pip3 uninstall -y onnxruntime \
|
||||||
pip3 uninstall -y onnxruntime \
|
&& pip3 install -r /requirements.txt
|
||||||
&& pip3 install --no-cache-dir -r /requirements.txt \
|
|
||||||
&& ROCM_SDK_CORE="$(python3 -c 'import _rocm_sdk_core, os; print(os.path.dirname(_rocm_sdk_core.__file__))')" \
|
|
||||||
&& /deps/prune_rocm.sh "$(dirname "${ROCM_SDK_CORE}")" \
|
|
||||||
&& ln -s "${ROCM_SDK_CORE}" /opt/rocm
|
|
||||||
|
|
||||||
WORKDIR /opt/frigate
|
#######################################################################
|
||||||
COPY --from=rootfs / /
|
FROM scratch AS rocm-dist
|
||||||
|
|
||||||
|
ARG ROCM
|
||||||
|
|
||||||
|
# Copy HIP headers required for MIOpen JIT (BuildHip) / HIPRTC at runtime
|
||||||
|
COPY --from=rocm /opt/rocm-${ROCM}/include/ /opt/rocm-${ROCM}/include/
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/bin/rocminfo /opt/rocm-$ROCM/bin/migraphx-driver /opt/rocm-$ROCM/bin/
|
||||||
|
# Copy MIOpen database files for gfx10xx, gfx11xx, and gfx12xx only (RDNA2/RDNA3/RDNA4)
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx10* /opt/rocm-$ROCM/share/miopen/db/
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx11* /opt/rocm-$ROCM/share/miopen/db/
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx12* /opt/rocm-$ROCM/share/miopen/db/
|
||||||
|
# Copy rocBLAS library files for gfx10xx, gfx11xx, and gfx12xx only
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx10* /opt/rocm-$ROCM/lib/rocblas/library/
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx11* /opt/rocm-$ROCM/lib/rocblas/library/
|
||||||
|
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*gfx12* /opt/rocm-$ROCM/lib/rocblas/library/
|
||||||
|
COPY --from=rocm /opt/rocm-dist/ /
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
FROM deps-prelim AS rocm-prelim-hsa-override0
|
FROM deps-prelim AS rocm-prelim-hsa-override0
|
||||||
ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1
|
ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1
|
||||||
ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1
|
ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1
|
||||||
ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1
|
ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1
|
||||||
|
ENV MIGRAPHX_ENABLE_HIPRTC_WORKAROUNDS=1
|
||||||
ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx
|
ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx
|
||||||
ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx
|
ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx
|
||||||
ENV AMD_COMGR_CACHE=1
|
ENV AMD_COMGR_CACHE=1
|
||||||
ENV AMD_COMGR_CACHE_DIR=/config/model_cache/migraphx
|
ENV AMD_COMGR_CACHE_DIR=/config/model_cache/migraphx
|
||||||
|
|
||||||
|
COPY --from=rocm-dist / /
|
||||||
|
|
||||||
|
RUN ldconfig
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
FROM rocm-prelim-hsa-override0 as rocm-prelim-hsa-override1
|
FROM rocm-prelim-hsa-override0 as rocm-prelim-hsa-override1
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Remove the parts of AMD's ROCm wheels that MIGraphX inference never loads:
|
|
||||||
# compilers, debuggers, profilers, management tools, and the math libraries
|
|
||||||
# (sparse, solver, FFT, random, RCCL, image processing) outside MIGraphX's
|
|
||||||
# dependencies. Runs in the same layer as the install so the files never
|
|
||||||
# reach the image.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
site_packages="$1"
|
|
||||||
core="${site_packages}/_rocm_sdk_core"
|
|
||||||
libraries="${site_packages}/_rocm_sdk_libraries"
|
|
||||||
|
|
||||||
# rocminfo stays for checking that the GPU is visible in the container
|
|
||||||
find "${core}/bin" -mindepth 1 -maxdepth 1 \
|
|
||||||
! -name rocminfo ! -name rocm_agent_enumerator -exec rm -rf {} +
|
|
||||||
|
|
||||||
# hiprtc compiles MIGraphX's kernels through comgr, which needs libLLVM,
|
|
||||||
# libclang-cpp, the clang resource headers, and the amdgcn device libraries
|
|
||||||
rm -rf "${core}/lib/llvm/bin" "${core}/lib/llvm/include"
|
|
||||||
find "${core}/lib/llvm/lib" -mindepth 1 -maxdepth 1 \
|
|
||||||
! -name 'libLLVM.so*' ! -name 'libclang-cpp.so*' ! -name clang \
|
|
||||||
-exec rm -rf {} +
|
|
||||||
rm -rf "${core}"/lib/llvm/lib/clang/*/lib
|
|
||||||
|
|
||||||
rm -rf \
|
|
||||||
"${core}/lib/host-math" \
|
|
||||||
"${core}"/lib/python3* \
|
|
||||||
"${core}/lib/rocprofiler-sdk" \
|
|
||||||
"${core}"/lib/librocprofiler-sdk*.so* \
|
|
||||||
"${core}"/lib/librocprof-trace-decoder.so* \
|
|
||||||
"${core}"/lib/librocm-dbgapi.so* \
|
|
||||||
"${core}"/lib/librocm-debug-agent.so* \
|
|
||||||
"${core}"/lib/libamd_smi*.so* \
|
|
||||||
"${core}"/lib/libgoamdsmi_shim64.so* \
|
|
||||||
"${core}"/lib/librocdecode.so* \
|
|
||||||
"${core}"/lib/librocjpeg.so* \
|
|
||||||
"${core}"/lib/librocdxg.so* \
|
|
||||||
"${core}"/lib/librocjitsu_*.so* \
|
|
||||||
"${core}"/lib/libhsa_hotswap_rocjitsu.so* \
|
|
||||||
"${core}"/lib/*.a
|
|
||||||
|
|
||||||
rm -rf \
|
|
||||||
"${libraries}"/lib/librpp.so* \
|
|
||||||
"${libraries}"/lib/librocsparse.so* \
|
|
||||||
"${libraries}"/lib/libhipsparse.so* \
|
|
||||||
"${libraries}"/lib/libhipsparselt.so* \
|
|
||||||
"${libraries}/lib/rocsparse" \
|
|
||||||
"${libraries}"/lib/librocsolver.so* \
|
|
||||||
"${libraries}"/lib/libhipsolver.so* \
|
|
||||||
"${libraries}"/lib/libhipblas.so* \
|
|
||||||
"${libraries}"/lib/librocrand.so* \
|
|
||||||
"${libraries}"/lib/libhiprand.so* \
|
|
||||||
"${libraries}"/lib/librocfft.so* \
|
|
||||||
"${libraries}"/lib/libhipfft.so* \
|
|
||||||
"${libraries}"/lib/libhipfftw.so* \
|
|
||||||
"${libraries}"/lib/librccl.so* \
|
|
||||||
"${libraries}"/.kpack/sparse_lib_*.kpack \
|
|
||||||
"${libraries}"/.kpack/solver_lib_*.kpack \
|
|
||||||
"${libraries}"/.kpack/rand_lib_*.kpack \
|
|
||||||
"${libraries}"/.kpack/fft_lib_*.kpack \
|
|
||||||
"${libraries}"/.kpack/rccl_lib_*.kpack
|
|
||||||
@@ -1,19 +1 @@
|
|||||||
# ROCm 10.1.0, MIGraphX, and its ONNX Runtime plugin provider from AMD's wheels.
|
onnxruntime-migraphx @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v7.2.3-1/onnxruntime_migraphx-1.24.4-cp311-cp311-linux_x86_64.whl
|
||||||
onnxruntime == 1.29.*
|
|
||||||
onnxruntime-ep-migraphx @ https://stable.repo.amd.com/rocm/onnxruntime/whl-next/onnxruntime-ep-migraphx/onnxruntime_ep_migraphx-1.0.0%2Brocm10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl
|
|
||||||
migraphx-libs @ https://stable.repo.amd.com/rocm/migraphx/whl-next/migraphx-libs/migraphx_libs-2.18.0%2Brocm10.1.0-py3-none-manylinux_2_28_x86_64.whl
|
|
||||||
rocm-sdk-core @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-core/rocm_sdk_core-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-libraries @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-libraries/rocm_sdk_libraries-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
# Kernels for each supported GPU target. MIGraphX has no gfx1031-gfx1036 kernels,
|
|
||||||
# so those GPUs run as gfx1030 with HSA_OVERRIDE_GFX_VERSION=10.3.0
|
|
||||||
rocm-sdk-device-gfx1030 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1030/rocm_sdk_device_gfx1030-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1100 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1100/rocm_sdk_device_gfx1100-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1101 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1101/rocm_sdk_device_gfx1101-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1102 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1102/rocm_sdk_device_gfx1102-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1103 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1103/rocm_sdk_device_gfx1103-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1150 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1150/rocm_sdk_device_gfx1150-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1151 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1151/rocm_sdk_device_gfx1151-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1152 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1152/rocm_sdk_device_gfx1152-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1153 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1153/rocm_sdk_device_gfx1153-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1200 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1200/rocm_sdk_device_gfx1200-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
rocm-sdk-device-gfx1201 @ https://stable.repo.amd.com/rocm/whl-next/rocm-sdk-device-gfx1201/rocm_sdk_device_gfx1201-10.1.0-py3-none-linux_x86_64.whl
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
variable "ROCM" {
|
||||||
|
default = "7.2.3"
|
||||||
|
}
|
||||||
variable "HSA_OVERRIDE_GFX_VERSION" {
|
variable "HSA_OVERRIDE_GFX_VERSION" {
|
||||||
default = ""
|
default = ""
|
||||||
}
|
}
|
||||||
@@ -5,6 +8,12 @@ variable "HSA_OVERRIDE" {
|
|||||||
default = "1"
|
default = "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target wget {
|
||||||
|
dockerfile = "docker/main/Dockerfile"
|
||||||
|
platforms = ["linux/amd64"]
|
||||||
|
target = "wget"
|
||||||
|
}
|
||||||
|
|
||||||
target deps {
|
target deps {
|
||||||
dockerfile = "docker/main/Dockerfile"
|
dockerfile = "docker/main/Dockerfile"
|
||||||
platforms = ["linux/amd64"]
|
platforms = ["linux/amd64"]
|
||||||
@@ -21,10 +30,12 @@ target rocm {
|
|||||||
dockerfile = "docker/rocm/Dockerfile"
|
dockerfile = "docker/rocm/Dockerfile"
|
||||||
contexts = {
|
contexts = {
|
||||||
deps = "target:deps",
|
deps = "target:deps",
|
||||||
|
wget = "target:wget",
|
||||||
rootfs = "target:rootfs"
|
rootfs = "target:rootfs"
|
||||||
}
|
}
|
||||||
platforms = ["linux/amd64"]
|
platforms = ["linux/amd64"]
|
||||||
args = {
|
args = {
|
||||||
|
ROCM = ROCM,
|
||||||
HSA_OVERRIDE_GFX_VERSION = HSA_OVERRIDE_GFX_VERSION,
|
HSA_OVERRIDE_GFX_VERSION = HSA_OVERRIDE_GFX_VERSION,
|
||||||
HSA_OVERRIDE = HSA_OVERRIDE
|
HSA_OVERRIDE = HSA_OVERRIDE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ For example, in Nginx:
|
|||||||
|
|
||||||
```
|
```
|
||||||
location /frigate {
|
location /frigate {
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Ingress-Path /frigate;
|
proxy_set_header X-Ingress-Path /frigate;
|
||||||
proxy_pass http://frigate_backend;
|
proxy_pass http://frigate_backend;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,22 +394,6 @@ Note that the labelmap uses a subset of the complete COCO label set that has onl
|
|||||||
|
|
||||||
Support for AMD GPUs is provided using the [ONNX detector](#onnx). In order to utilize the AMD GPU for object detection use a frigate docker image with `-rocm` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-rocm`.
|
Support for AMD GPUs is provided using the [ONNX detector](#onnx). In order to utilize the AMD GPU for object detection use a frigate docker image with `-rocm` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-rocm`.
|
||||||
|
|
||||||
### Supported GPUs {#supported-gpus-rocm}
|
|
||||||
|
|
||||||
The `-rocm` image includes ROCm support for these GPU chipsets, which work without any chipset override:
|
|
||||||
|
|
||||||
| Chipset | Example GPUs |
|
|
||||||
| -------------------------------------------- | ------------------------------------------------------------------------ |
|
|
||||||
| gfx1030 (RDNA2) | Radeon RX 6800 / 6800 XT / 6900 XT / 6950 XT, Radeon PRO W6800 |
|
|
||||||
| gfx1100, gfx1101, gfx1102 (RDNA3) | Radeon RX 7900 / 7800 / 7700 / 7600 series, Radeon PRO W7000 series |
|
|
||||||
| gfx1103 (RDNA3 integrated) | Radeon 780M / 760M in Ryzen 7040, 8040, 8000G, and 200 series processors |
|
|
||||||
| gfx1150, gfx1151, gfx1152, gfx1153 (RDNA3.5) | Integrated GPUs in Ryzen AI 300 / 400 series and Ryzen AI Max processors |
|
|
||||||
| gfx1200, gfx1201 (RDNA4) | Radeon RX 9060 / 9070 series, Radeon AI PRO R9700 |
|
|
||||||
|
|
||||||
Other RDNA2 GPUs (gfx1031 through gfx1036), such as the Radeon RX 6700 / 6600 / 6500 series and the Radeon 680M / 660M integrated GPUs, run on the gfx1030 support by [overriding the chipset](#docker-settings-for-overriding-the-gpu-chipset) to `10.3.0`.
|
|
||||||
|
|
||||||
Older GPUs, including RDNA1 (Radeon RX 5000 series) and Vega based dedicated and integrated GPUs, are not supported.
|
|
||||||
|
|
||||||
### Docker settings for GPU access
|
### Docker settings for GPU access
|
||||||
|
|
||||||
ROCm needs access to the `/dev/kfd` and `/dev/dri` devices. When docker or frigate is not run under root then also `video` (and possibly `render` and `ssl/_ssl`) groups should be added.
|
ROCm needs access to the `/dev/kfd` and `/dev/dri` devices. When docker or frigate is not run under root then also `video` (and possibly `render` and `ssl/_ssl`) groups should be added.
|
||||||
@@ -436,10 +420,19 @@ For reference on recommended settings see [running ROCm/pytorch in Docker](https
|
|||||||
|
|
||||||
### Docker settings for overriding the GPU chipset
|
### Docker settings for overriding the GPU chipset
|
||||||
|
|
||||||
A GPU whose chipset isn't in the [supported GPUs](#supported-gpus-rocm) list can run on the support for a closely related chipset by setting the `HSA_OVERRIDE_GFX_VERSION` environment variable. RDNA2 GPUs other than gfx1030 (gfx1031 through gfx1036) need this, using `10.3.0`:
|
Your GPU might work just fine without any special configuration but in many cases they need manual settings. AMD/ROCm software stack comes with a limited set of GPU drivers and for newer or missing models you will have to override the chipset version to an older/generic version to get things working.
|
||||||
|
|
||||||
|
Also AMD/ROCm does not "officially" support integrated GPUs. It still does work with most of them just fine but requires special settings. One has to configure the `HSA_OVERRIDE_GFX_VERSION` environment variable. See the [ROCm bug report](https://github.com/ROCm/ROCm/issues/1743) for context and examples.
|
||||||
|
|
||||||
|
For the rocm frigate build there is some automatic detection:
|
||||||
|
|
||||||
|
- gfx1031 -> 10.3.0
|
||||||
|
- gfx1103 -> 11.0.0
|
||||||
|
|
||||||
|
If you have something else you might need to override the `HSA_OVERRIDE_GFX_VERSION` at Docker launch. Suppose the version you want is `10.0.0`, then you should configure it from command line as:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker run -e HSA_OVERRIDE_GFX_VERSION=10.3.0 \
|
$ docker run -e HSA_OVERRIDE_GFX_VERSION=10.0.0 \
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -450,17 +443,16 @@ services:
|
|||||||
frigate:
|
frigate:
|
||||||
...
|
...
|
||||||
environment:
|
environment:
|
||||||
HSA_OVERRIDE_GFX_VERSION: "10.3.0"
|
HSA_OVERRIDE_GFX_VERSION: "10.0.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
GPUs in the supported list don't need an override. An override that was set for the Radeon 780M / 760M (gfx1103) on an earlier version of Frigate is no longer needed and can be removed.
|
Figuring out what version you need can be complicated as you can't tell the chipset name and driver from the AMD brand name.
|
||||||
|
|
||||||
You can't tell the chipset from the AMD brand name, so check it in the container:
|
- first make sure that rocm environment is running properly by running `/opt/rocm/bin/rocminfo` in the frigate container -- it should list both the CPU and the GPU with their properties
|
||||||
|
- find the chipset version you have (gfxNNN) from the output of the `rocminfo` (see below)
|
||||||
- first make sure that the ROCm environment is running properly by running `/opt/rocm/bin/rocminfo` in the Frigate container. It should list both the CPU and the GPU with their properties
|
- use a search engine to query what `HSA_OVERRIDE_GFX_VERSION` you need for the given gfx name ("gfxNNN ROCm HSA_OVERRIDE_GFX_VERSION")
|
||||||
- find the chipset version you have (gfxNNN) from the output of `rocminfo` (see below)
|
- override the `HSA_OVERRIDE_GFX_VERSION` with relevant value
|
||||||
- if the chipset isn't in the supported list, set `HSA_OVERRIDE_GFX_VERSION` to the closest supported chipset, written as major.minor.stepping (for example gfx1030 is `10.3.0`)
|
- if things are not working check the frigate docker logs
|
||||||
- if things are not working check the Frigate docker logs
|
|
||||||
|
|
||||||
#### Figuring out if AMD/ROCm is working and found your GPU
|
#### Figuring out if AMD/ROCm is working and found your GPU
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Frigate supports multiple different detectors that work on different types of ha
|
|||||||
|
|
||||||
**AMD**
|
**AMD**
|
||||||
|
|
||||||
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD discrete and recent integrated GPUs to provide efficient object detection
|
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
|
||||||
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
|
- [Supports limited model architectures](../../configuration/object_detectors#amdrocm-gpu-detector)
|
||||||
- Runs best on discrete AMD GPUs
|
- Runs best on discrete AMD GPUs
|
||||||
- <CommunityBadge /> [XDNA2 (Ryzen AI)](#amd-xdna2): AMD XDNA2 NPU (sub-watt power AI/ML processor separate to the GPU) inside Strix and other "AI" branded AMD platforms
|
- <CommunityBadge /> [XDNA2 (Ryzen AI)](#amd-xdna2): AMD XDNA2 NPU (sub-watt power AI/ML processor separate to the GPU) inside Strix and other "AI" branded AMD platforms
|
||||||
@@ -240,7 +240,7 @@ Apple Silicon can not run within a container, so a ZMQ proxy is utilized to comm
|
|||||||
|
|
||||||
### ROCm - AMD GPU
|
### ROCm - AMD GPU
|
||||||
|
|
||||||
With the [ROCm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many AMD discrete and integrated GPUs, see the [supported GPUs](../configuration/object_detectors.md#supported-gpus-rocm).
|
With the [ROCm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
|
||||||
|
|
||||||
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
|
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
|
||||||
| -------------- | --------------------------- | ------------------------- | ---------------------- |
|
| -------------- | --------------------------- | ------------------------- | ---------------------- |
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ Your reverse proxy must be configured to forward the `Upgrade` and `Connection`
|
|||||||
|
|
||||||
Note that some proxies disable WebSocket support by default. For example, Nginx Proxy Manager has a "Websockets Support" toggle that must be enabled.
|
Note that some proxies disable WebSocket support by default. For example, Nginx Proxy Manager has a "Websockets Support" toggle that must be enabled.
|
||||||
|
|
||||||
|
Frigate rejects WebSocket connections opened from a page on another site. Your proxy must either preserve the original `Host` header or send it in `X-Forwarded-Host`, otherwise Frigate cannot tell the connection came from its own UI.
|
||||||
|
|
||||||
## Proxies
|
## Proxies
|
||||||
|
|
||||||
There are many solutions available to implement reverse proxies and the community is invited to help out documenting others through a contribution to this page.
|
There are many solutions available to implement reverse proxies and the community is invited to help out documenting others through a contribution to this page.
|
||||||
@@ -161,6 +163,7 @@ The settings below enabled connection upgrade, sets up logging (optional) and pr
|
|||||||
error_log /data/logs/proxy-host-40_error.log warn;
|
error_log /data/logs/proxy-host-40_error.log warn;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $http_connection;
|
proxy_set_header Connection $http_connection;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ To install the optional companion Lovelace card, please see the [separate
|
|||||||
installation instructions](https://github.com/dermotduffy/frigate-hass-card) for
|
installation instructions](https://github.com/dermotduffy/frigate-hass-card) for
|
||||||
that card.
|
that card.
|
||||||
|
|
||||||
|
Frigate rejects browser connections to its live streams that come from another
|
||||||
|
origin, such as a Home Assistant dashboard. Configure cards to stream through the
|
||||||
|
integration's proxy rather than pointing them directly at Frigate's port.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
When configuring the integration, you will be asked for the `URL` of your Frigate instance which can be pointed at the internal unauthenticated port (`5000`) or the authenticated port (`8971`) for your instance. This may look like `http://<host>:5000/`.
|
When configuring the integration, you will be asked for the `URL` of your Frigate instance which can be pointed at the internal unauthenticated port (`5000`) or the authenticated port (`8971`) for your instance. This may look like `http://<host>:5000/`.
|
||||||
|
|||||||
+27
-1
@@ -39,6 +39,7 @@ from frigate.config import AuthConfig, ProxyConfig
|
|||||||
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
||||||
from frigate.models import User
|
from frigate.models import User
|
||||||
from frigate.notices import raise_notice
|
from frigate.notices import raise_notice
|
||||||
|
from frigate.util.origin import origin_allowed
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -689,6 +690,17 @@ def auth(request: Request):
|
|||||||
|
|
||||||
success_response = Response("", status_code=202)
|
success_response = Response("", status_code=202)
|
||||||
|
|
||||||
|
original_url = request.headers.get("x-original-url")
|
||||||
|
|
||||||
|
if _is_cross_origin_stream_request(original_url, request):
|
||||||
|
logger.warning(
|
||||||
|
"Rejected cross-origin request: origin=%s, host=%s, x-forwarded-host=%s",
|
||||||
|
request.headers.get("origin"),
|
||||||
|
request.headers.get("x-original-host"),
|
||||||
|
request.headers.get("x-forwarded-host"),
|
||||||
|
)
|
||||||
|
return Response("", status_code=403)
|
||||||
|
|
||||||
# dont require auth if the request is on the internal port
|
# dont require auth if the request is on the internal port
|
||||||
# this header is set by Frigate's nginx proxy, so it cant be spoofed.
|
# this header is set by Frigate's nginx proxy, so it cant be spoofed.
|
||||||
# the port is the boot-time snapshot rather than the live config value:
|
# the port is the boot-time snapshot rather than the live config value:
|
||||||
@@ -712,7 +724,6 @@ def auth(request: Request):
|
|||||||
logger.debug("X-Proxy-Secret header does not match configured secret value")
|
logger.debug("X-Proxy-Secret header does not match configured secret value")
|
||||||
return fail_response
|
return fail_response
|
||||||
|
|
||||||
original_url = request.headers.get("x-original-url")
|
|
||||||
frigate_config = request.app.frigate_config
|
frigate_config = request.app.frigate_config
|
||||||
|
|
||||||
# if auth is disabled, just apply the proxy header map and return success
|
# if auth is disabled, just apply the proxy header map and return success
|
||||||
@@ -1285,6 +1296,21 @@ def _is_go2rtc_stream_proxy_request(path: str, request: Request) -> bool:
|
|||||||
return any(normalized.startswith(p) for p in GO2RTC_STREAM_PROXY_PATHS)
|
return any(normalized.startswith(p) for p in GO2RTC_STREAM_PROXY_PATHS)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_cross_origin_stream_request(original_url: str | None, request: Request) -> bool:
|
||||||
|
# the internal port grants admin, so a browser on another site must be
|
||||||
|
# stopped before that early return
|
||||||
|
upgrade = request.headers.get("x-original-upgrade", "").lower()
|
||||||
|
path = split_request_uri(original_url or "")[0]
|
||||||
|
if upgrade != "websocket" and not _is_go2rtc_stream_proxy_request(path, request):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not origin_allowed(
|
||||||
|
request.headers.get("origin"),
|
||||||
|
request.headers.get("x-original-host"),
|
||||||
|
request.headers.get("x-forwarded-host"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def deny_response_for_go2rtc_stream(
|
def deny_response_for_go2rtc_stream(
|
||||||
original_url: str | None, role: str | None, request: Request
|
original_url: str | None, role: str | None, request: Request
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
|
|||||||
+24
-1
@@ -40,6 +40,7 @@ from frigate.const import (
|
|||||||
)
|
)
|
||||||
from frigate.models import User
|
from frigate.models import User
|
||||||
from frigate.output.ws_auth import ws_has_camera_access
|
from frigate.output.ws_auth import ws_has_camera_access
|
||||||
|
from frigate.util.origin import origin_allowed
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -472,6 +473,28 @@ def _materialize_for_ws(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class OriginCheckedWebSocketApplication(WebSocketWSGIApplication): # type: ignore[misc]
|
||||||
|
"""ws4py application that rejects cross-origin browser handshakes."""
|
||||||
|
|
||||||
|
def __call__(self, environ: dict[str, Any], start_response: Callable) -> Any:
|
||||||
|
if not origin_allowed(
|
||||||
|
environ.get("HTTP_ORIGIN"),
|
||||||
|
environ.get("HTTP_HOST"),
|
||||||
|
environ.get("HTTP_X_FORWARDED_HOST"),
|
||||||
|
):
|
||||||
|
logger.warning(
|
||||||
|
"Rejected cross-origin websocket handshake: origin=%s, host=%s, "
|
||||||
|
"x-forwarded-host=%s",
|
||||||
|
environ.get("HTTP_ORIGIN"),
|
||||||
|
environ.get("HTTP_HOST"),
|
||||||
|
environ.get("HTTP_X_FORWARDED_HOST"),
|
||||||
|
)
|
||||||
|
start_response("403 Forbidden", [("Content-Length", "0")])
|
||||||
|
return []
|
||||||
|
|
||||||
|
return super().__call__(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
class WebSocket(WebSocket_): # type: ignore[misc]
|
class WebSocket(WebSocket_): # type: ignore[misc]
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -621,7 +644,7 @@ class WebSocketClient(Communicator):
|
|||||||
5002,
|
5002,
|
||||||
server_class=WSGIServer,
|
server_class=WSGIServer,
|
||||||
handler_class=WebSocketWSGIRequestHandler,
|
handler_class=WebSocketWSGIRequestHandler,
|
||||||
app=WebSocketWSGIApplication(handler_cls=_WebSocketHandler),
|
app=OriginCheckedWebSocketApplication(handler_cls=_WebSocketHandler),
|
||||||
)
|
)
|
||||||
self.websocket_server.initialize_websockets_manager()
|
self.websocket_server.initialize_websockets_manager()
|
||||||
self.websocket_thread = threading.Thread(
|
self.websocket_thread = threading.Thread(
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
|
|
||||||
from frigate.const import MODEL_CACHE_DIR
|
|
||||||
from frigate.util.model import get_ort_providers
|
from frigate.util.model import get_ort_providers
|
||||||
from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible
|
from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible
|
||||||
|
|
||||||
@@ -56,9 +55,6 @@ _PROVIDER_LABELS = {
|
|||||||
LIGHTER_ANE_EP_NAME = "LighterANE"
|
LIGHTER_ANE_EP_NAME = "LighterANE"
|
||||||
LIGHTER_ANE_LIBRARY = "/usr/lib/lighter/liblighter_ane_ep.so"
|
LIGHTER_ANE_LIBRARY = "/usr/lib/lighter/liblighter_ane_ep.so"
|
||||||
|
|
||||||
# AMD's MIGraphX plugin execution provider, installed in the ROCm image
|
|
||||||
MIGRAPHX_EP_NAME = "MIGraphXExecutionProvider"
|
|
||||||
|
|
||||||
|
|
||||||
def get_lighter_ane_devices() -> list[Any]:
|
def get_lighter_ane_devices() -> list[Any]:
|
||||||
"""Get the Neural Engine devices lighter's provider offers, registering it once.
|
"""Get the Neural Engine devices lighter's provider offers, registering it once.
|
||||||
@@ -87,42 +83,6 @@ def get_lighter_ane_devices() -> list[Any]:
|
|||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
def get_migraphx_devices() -> list[Any]:
|
|
||||||
"""Get the AMD GPU devices the MIGraphX provider offers, registering it once.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The provider's ONNX Runtime devices, or an empty list outside the ROCm
|
|
||||||
image or without an AMD GPU
|
|
||||||
"""
|
|
||||||
# Import here to avoid circular imports
|
|
||||||
from frigate.detectors.hardware import detect_amd_gpu
|
|
||||||
|
|
||||||
try:
|
|
||||||
import onnxruntime_ep_migraphx
|
|
||||||
except ImportError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# ONNX Runtime finds the provider's GPUs in the same /sys/class/drm entries,
|
|
||||||
# so an AMD GPU here means the provider lists it once registered
|
|
||||||
if detect_amd_gpu() is None:
|
|
||||||
return []
|
|
||||||
|
|
||||||
devices = [d for d in ort.get_ep_devices() if d.ep_name == MIGRAPHX_EP_NAME]
|
|
||||||
|
|
||||||
if not devices:
|
|
||||||
try:
|
|
||||||
ort.register_execution_provider_library(
|
|
||||||
MIGRAPHX_EP_NAME, onnxruntime_ep_migraphx.get_library_path()
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning("Failed to load the MIGraphX provider: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
devices = [d for d in ort.get_ep_devices() if d.ep_name == MIGRAPHX_EP_NAME]
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def is_arm64_platform() -> bool:
|
def is_arm64_platform() -> bool:
|
||||||
"""Check if we're running on an ARM platform."""
|
"""Check if we're running on an ARM platform."""
|
||||||
machine = platform.machine().lower()
|
machine = platform.machine().lower()
|
||||||
@@ -796,41 +756,12 @@ def get_optimized_runner(
|
|||||||
ONNXModelRunner(session, model_type=model_type),
|
ONNXModelRunner(session, model_type=model_type),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
device != "CPU"
|
|
||||||
and not ONNXModelRunner.is_migraphx_complex_model(model_type)
|
|
||||||
and (migraphx_devices := get_migraphx_devices())
|
|
||||||
):
|
|
||||||
migraphx_cache_dir = os.path.join(MODEL_CACHE_DIR, "migraphx/ort")
|
|
||||||
os.makedirs(migraphx_cache_dir, exist_ok=True)
|
|
||||||
sess_options = get_ort_session_options(model_type) or ort.SessionOptions()
|
|
||||||
# only the first AMD GPU is used, matching detect_amd_gpu
|
|
||||||
sess_options.add_provider_for_devices(
|
|
||||||
migraphx_devices[:1], {"cache_dir": migraphx_cache_dir}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = ort.InferenceSession(model_path, sess_options=sess_options)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(
|
|
||||||
"Failed to load %s with MIGraphX, using the default providers: %s",
|
|
||||||
model_path,
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return _record_runner(
|
|
||||||
model_path,
|
|
||||||
model_type,
|
|
||||||
ONNXModelRunner(session, model_type=model_type),
|
|
||||||
)
|
|
||||||
|
|
||||||
providers, options = get_ort_providers(device == "CPU", device, **kwargs)
|
providers, options = get_ort_providers(device == "CPU", device, **kwargs)
|
||||||
|
|
||||||
if providers[0] == "CPUExecutionProvider":
|
if providers[0] == "CPUExecutionProvider":
|
||||||
# In the default image, ONNXRuntime is used so we will only get CPUExecutionProvider
|
# In the default image, ONNXRuntime is used so we will only get CPUExecutionProvider
|
||||||
# In other images we will get CUDA which is preferred over OpenVINO, while
|
# In other images we will get CUDA / ROCm which are preferred over OpenVINO
|
||||||
# MIGraphX in the ROCm image is already handled above
|
# There is currently no way to prioritize OpenVINO over CUDA / ROCm in these images
|
||||||
# There is currently no way to prioritize OpenVINO over CUDA in these images
|
|
||||||
if device != "CPU" and is_openvino_gpu_npu_available():
|
if device != "CPU" and is_openvino_gpu_npu_available():
|
||||||
return _record_runner(
|
return _record_runner(
|
||||||
model_path,
|
model_path,
|
||||||
@@ -859,6 +790,15 @@ def get_optimized_runner(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
providers
|
||||||
|
and providers[0] == "MIGraphXExecutionProvider"
|
||||||
|
and ONNXModelRunner.is_migraphx_complex_model(model_type)
|
||||||
|
):
|
||||||
|
# Don't use MIGraphX for models that are not supported
|
||||||
|
providers.pop(0)
|
||||||
|
options.pop(0)
|
||||||
|
|
||||||
return _record_runner(
|
return _record_runner(
|
||||||
model_path,
|
model_path,
|
||||||
model_type,
|
model_type,
|
||||||
|
|||||||
@@ -14,12 +14,11 @@ from ws4py.server.wsgirefserver import (
|
|||||||
WebSocketWSGIRequestHandler,
|
WebSocketWSGIRequestHandler,
|
||||||
WSGIServer,
|
WSGIServer,
|
||||||
)
|
)
|
||||||
from ws4py.server.wsgiutils import WebSocketWSGIApplication
|
|
||||||
|
|
||||||
from frigate.comms.config_updater import ConfigSubscriber
|
from frigate.comms.config_updater import ConfigSubscriber
|
||||||
from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum
|
from frigate.comms.detections_updater import DetectionSubscriber, DetectionTypeEnum
|
||||||
from frigate.comms.ws import WebSocket
|
from frigate.comms.ws import OriginCheckedWebSocketApplication, WebSocket
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import AuthConfig, FrigateConfig
|
||||||
from frigate.config.camera.updater import (
|
from frigate.config.camera.updater import (
|
||||||
CameraConfigUpdateEnum,
|
CameraConfigUpdateEnum,
|
||||||
CameraConfigUpdateSubscriber,
|
CameraConfigUpdateSubscriber,
|
||||||
@@ -136,7 +135,7 @@ class OutputProcess(FrigateProcess):
|
|||||||
8082,
|
8082,
|
||||||
server_class=WSGIServer,
|
server_class=WSGIServer,
|
||||||
handler_class=WebSocketWSGIRequestHandler,
|
handler_class=WebSocketWSGIRequestHandler,
|
||||||
app=WebSocketWSGIApplication(handler_cls=WebSocket),
|
app=OriginCheckedWebSocketApplication(handler_cls=WebSocket),
|
||||||
)
|
)
|
||||||
websocket_server.initialize_websockets_manager()
|
websocket_server.initialize_websockets_manager()
|
||||||
websocket_thread = threading.Thread(target=websocket_server.serve_forever)
|
websocket_thread = threading.Thread(target=websocket_server.serve_forever)
|
||||||
@@ -154,6 +153,7 @@ class OutputProcess(FrigateProcess):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
birdseye_config_subscriber = ConfigSubscriber("config/birdseye", exact=True)
|
birdseye_config_subscriber = ConfigSubscriber("config/birdseye", exact=True)
|
||||||
|
auth_config_subscriber = ConfigSubscriber("config/auth", exact=True)
|
||||||
|
|
||||||
jsmpeg_cameras: dict[str, JsmpegCamera] = {}
|
jsmpeg_cameras: dict[str, JsmpegCamera] = {}
|
||||||
birdseye: Birdseye | None = None
|
birdseye: Birdseye | None = None
|
||||||
@@ -194,6 +194,15 @@ class OutputProcess(FrigateProcess):
|
|||||||
self.config.birdseye = birdseye_config
|
self.config.birdseye = birdseye_config
|
||||||
logger.debug("Applied dynamic birdseye config update")
|
logger.debug("Applied dynamic birdseye config update")
|
||||||
|
|
||||||
|
# user endpoints publish None on this topic and role edits an AuthConfig
|
||||||
|
while True:
|
||||||
|
auth_topic, auth_config = auth_config_subscriber.check_for_update()
|
||||||
|
if auth_topic is None:
|
||||||
|
break
|
||||||
|
if isinstance(auth_config, AuthConfig):
|
||||||
|
self.config.auth = auth_config
|
||||||
|
logger.debug("Applied dynamic auth config update")
|
||||||
|
|
||||||
# drain review updates every iteration, not just when birdseye is
|
# drain review updates every iteration, not just when birdseye is
|
||||||
# being consumed, so a dropped end never strands a camera
|
# being consumed, so a dropped end never strands a camera
|
||||||
if birdseye is not None:
|
if birdseye is not None:
|
||||||
@@ -330,6 +339,7 @@ class OutputProcess(FrigateProcess):
|
|||||||
|
|
||||||
config_subscriber.stop()
|
config_subscriber.stop()
|
||||||
birdseye_config_subscriber.stop()
|
birdseye_config_subscriber.stop()
|
||||||
|
auth_config_subscriber.stop()
|
||||||
websocket_server.manager.close_all()
|
websocket_server.manager.close_all()
|
||||||
websocket_server.manager.stop()
|
websocket_server.manager.stop()
|
||||||
websocket_server.manager.join()
|
websocket_server.manager.join()
|
||||||
|
|||||||
@@ -101,6 +101,71 @@ class TestAuthInternalPort(BaseTestHttp):
|
|||||||
self.assertEqual(resp.headers["remote-user"], "anonymous")
|
self.assertEqual(resp.headers["remote-user"], "anonymous")
|
||||||
self.assertEqual(resp.headers["remote-role"], "admin")
|
self.assertEqual(resp.headers["remote-role"], "admin")
|
||||||
|
|
||||||
|
def _internal_auth(self, original_url: str, **headers: str):
|
||||||
|
app = self._create_app()
|
||||||
|
|
||||||
|
with AuthTestClient(app) as client:
|
||||||
|
return client.get(
|
||||||
|
"/auth",
|
||||||
|
headers={
|
||||||
|
"x-server-port": "5000",
|
||||||
|
"x-original-url": original_url,
|
||||||
|
"x-original-host": "frigate.lan:5000",
|
||||||
|
**{k.replace("_", "-"): v for k, v in headers.items()},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cross_origin_websocket_on_internal_port_denied(self):
|
||||||
|
resp = self._internal_auth(
|
||||||
|
"/ws", origin="https://evil.example", x_original_upgrade="websocket"
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
def test_cross_origin_go2rtc_stream_denied(self):
|
||||||
|
resp = self._internal_auth(
|
||||||
|
"/live/mse/api/ws?src=front_door",
|
||||||
|
origin="https://evil.example",
|
||||||
|
x_original_upgrade="websocket",
|
||||||
|
x_go2rtc_stream_proxy="1",
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
def test_cross_origin_webrtc_post_denied(self):
|
||||||
|
# the offer POST is not an upgrade, so the stream marker alone gates it
|
||||||
|
resp = self._internal_auth(
|
||||||
|
"/api/go2rtc/webrtc?src=front_door",
|
||||||
|
origin="https://evil.example",
|
||||||
|
x_go2rtc_stream_proxy="1",
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
def test_same_origin_websocket_allowed(self):
|
||||||
|
resp = self._internal_auth(
|
||||||
|
"/live/mse/api/ws?src=front_door",
|
||||||
|
origin="http://frigate.lan:5000",
|
||||||
|
x_original_upgrade="websocket",
|
||||||
|
x_go2rtc_stream_proxy="1",
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
|
||||||
|
def test_websocket_without_origin_allowed(self):
|
||||||
|
resp = self._internal_auth("/ws", x_original_upgrade="websocket")
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
|
||||||
|
def test_forwarded_host_origin_allowed(self):
|
||||||
|
# Home Assistant integration proxy: Host is Frigate, Origin is HA
|
||||||
|
resp = self._internal_auth(
|
||||||
|
"/api/go2rtc/webrtc?src=front_door",
|
||||||
|
origin="https://ha.example.com:8123",
|
||||||
|
x_forwarded_host="ha.example.com:8123",
|
||||||
|
x_go2rtc_stream_proxy="1",
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
|
||||||
|
def test_cross_origin_plain_http_not_checked(self):
|
||||||
|
resp = self._internal_auth("/api/events", origin="https://evil.example")
|
||||||
|
self.assertEqual(resp.status_code, 202)
|
||||||
|
|
||||||
def test_external_port_requires_auth(self):
|
def test_external_port_requires_auth(self):
|
||||||
app = self._create_app()
|
app = self._create_app()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import AuthConfig, FrigateConfig
|
||||||
from frigate.output.ws_auth import ws_has_camera_access
|
from frigate.output.ws_auth import ws_has_camera_access
|
||||||
|
|
||||||
|
|
||||||
@@ -55,3 +55,11 @@ class TestWsHasCameraAccess(unittest.TestCase):
|
|||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
ws_has_camera_access(self._make_ws("limited_user"), "birdseye", self.config)
|
ws_has_camera_access(self._make_ws("limited_user"), "birdseye", self.config)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_replaced_auth_config_revokes_camera(self):
|
||||||
|
ws = self._make_ws("limited_user")
|
||||||
|
self.assertTrue(ws_has_camera_access(ws, "front_door", self.config))
|
||||||
|
|
||||||
|
self.config.auth = AuthConfig(roles={"limited_user": ["back_door"]})
|
||||||
|
self.assertFalse(ws_has_camera_access(ws, "front_door", self.config))
|
||||||
|
self.assertTrue(ws_has_camera_access(ws, "back_door", self.config))
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
"""Tests for the device each model runner reports after loading."""
|
"""Tests for the device each model runner reports after loading."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from frigate.detectors import detection_runners, hardware
|
from frigate.detectors import detection_runners
|
||||||
from frigate.detectors.detection_runners import (
|
from frigate.detectors.detection_runners import (
|
||||||
CudaGraphRunner,
|
CudaGraphRunner,
|
||||||
ONNXModelRunner,
|
ONNXModelRunner,
|
||||||
@@ -18,7 +17,6 @@ from frigate.detectors.detection_runners import (
|
|||||||
record_loaded_device,
|
record_loaded_device,
|
||||||
snapshot_loaded_devices,
|
snapshot_loaded_devices,
|
||||||
)
|
)
|
||||||
from frigate.util import model as model_util
|
|
||||||
|
|
||||||
|
|
||||||
class TestRunnerDeviceName(unittest.TestCase):
|
class TestRunnerDeviceName(unittest.TestCase):
|
||||||
@@ -319,215 +317,3 @@ class TestLighterANE(unittest.TestCase):
|
|||||||
get_optimized_runner("/models/arcface.onnx", "CPU", "arcface")
|
get_optimized_runner("/models/arcface.onnx", "CPU", "arcface")
|
||||||
|
|
||||||
ane.assert_not_called()
|
ane.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestMIGraphX(unittest.TestCase):
|
|
||||||
"""AMD's MIGraphX plugin provider, selected through its devices in the ROCm image."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
loaded_devices.clear()
|
|
||||||
self.root = tempfile.TemporaryDirectory()
|
|
||||||
self.addCleanup(self.root.cleanup)
|
|
||||||
|
|
||||||
for patcher in (
|
|
||||||
patch.object(hardware, "detect_amd_gpu", return_value=MagicMock()),
|
|
||||||
patch.object(detection_runners, "MODEL_CACHE_DIR", self.root.name),
|
|
||||||
patch.object(detection_runners, "is_rknn_compatible", return_value=False),
|
|
||||||
patch.object(detection_runners, "get_lighter_ane_devices", return_value=[]),
|
|
||||||
):
|
|
||||||
patcher.start()
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
|
|
||||||
self.plugin = MagicMock()
|
|
||||||
self.plugin.get_library_path.return_value = "/site/libmigraphx-ep.so"
|
|
||||||
|
|
||||||
def _device(self) -> MagicMock:
|
|
||||||
device = MagicMock()
|
|
||||||
device.ep_name = "MIGraphXExecutionProvider"
|
|
||||||
return device
|
|
||||||
|
|
||||||
def test_no_devices_outside_the_rocm_image(self):
|
|
||||||
with (
|
|
||||||
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": None}),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort, "register_execution_provider_library"
|
|
||||||
) as register,
|
|
||||||
):
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [])
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [])
|
|
||||||
|
|
||||||
register.assert_not_called()
|
|
||||||
|
|
||||||
def test_no_devices_without_an_amd_gpu(self):
|
|
||||||
with (
|
|
||||||
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
|
|
||||||
patch.object(hardware, "detect_amd_gpu", return_value=None),
|
|
||||||
patch.object(detection_runners.ort, "get_ep_devices") as get_ep_devices,
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort, "register_execution_provider_library"
|
|
||||||
) as register,
|
|
||||||
):
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [])
|
|
||||||
|
|
||||||
get_ep_devices.assert_not_called()
|
|
||||||
register.assert_not_called()
|
|
||||||
|
|
||||||
def test_the_provider_is_registered_once(self):
|
|
||||||
device = self._device()
|
|
||||||
other = MagicMock()
|
|
||||||
other.ep_name = "CPUExecutionProvider"
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort,
|
|
||||||
"get_ep_devices",
|
|
||||||
side_effect=[[other], [other, device], [other, device]],
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort, "register_execution_provider_library"
|
|
||||||
) as register,
|
|
||||||
):
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [device])
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [device])
|
|
||||||
|
|
||||||
register.assert_called_once_with(
|
|
||||||
"MIGraphXExecutionProvider", "/site/libmigraphx-ep.so"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_a_provider_that_will_not_load_is_skipped(self):
|
|
||||||
with (
|
|
||||||
patch.dict(sys.modules, {"onnxruntime_ep_migraphx": self.plugin}),
|
|
||||||
patch.object(detection_runners.ort, "get_ep_devices", return_value=[]),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort,
|
|
||||||
"register_execution_provider_library",
|
|
||||||
side_effect=RuntimeError("libamdhip64.so.7: cannot open shared object"),
|
|
||||||
),
|
|
||||||
self.assertLogs(detection_runners.logger, level="WARNING"),
|
|
||||||
):
|
|
||||||
self.assertEqual(detection_runners.get_migraphx_devices(), [])
|
|
||||||
|
|
||||||
def test_the_session_runs_on_the_first_gpu(self):
|
|
||||||
device = self._device()
|
|
||||||
session = MagicMock()
|
|
||||||
session.get_providers.return_value = [
|
|
||||||
"MIGraphXExecutionProvider",
|
|
||||||
"CPUExecutionProvider",
|
|
||||||
]
|
|
||||||
options = MagicMock()
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"get_migraphx_devices",
|
|
||||||
return_value=[device, self._device()],
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners, "get_ort_session_options", return_value=options
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort, "InferenceSession", return_value=session
|
|
||||||
) as inference_session,
|
|
||||||
):
|
|
||||||
runner = get_optimized_runner("/models/yolo.onnx", "AUTO", "yolo-generic")
|
|
||||||
|
|
||||||
cache_dir = os.path.join(self.root.name, "migraphx/ort")
|
|
||||||
options.add_provider_for_devices.assert_called_once_with(
|
|
||||||
[device], {"cache_dir": cache_dir}
|
|
||||||
)
|
|
||||||
inference_session.assert_called_once_with(
|
|
||||||
"/models/yolo.onnx", sess_options=options
|
|
||||||
)
|
|
||||||
self.assertTrue(os.path.isdir(cache_dir))
|
|
||||||
self.assertIsInstance(runner, ONNXModelRunner)
|
|
||||||
self.assertEqual(
|
|
||||||
loaded_devices["/models/yolo.onnx"], ("yolo-generic", "MIGraphX")
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_a_model_migraphx_cannot_load_uses_the_default_providers(self):
|
|
||||||
session = MagicMock()
|
|
||||||
session.get_providers.return_value = ["CPUExecutionProvider"]
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"get_migraphx_devices",
|
|
||||||
return_value=[self._device()],
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"get_ort_providers",
|
|
||||||
return_value=(["CPUExecutionProvider"], [{}]),
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners, "is_openvino_gpu_npu_available", return_value=False
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort,
|
|
||||||
"InferenceSession",
|
|
||||||
side_effect=[RuntimeError("compile failed"), session],
|
|
||||||
) as inference_session,
|
|
||||||
patch.object(
|
|
||||||
detection_runners, "get_ort_session_options", return_value=MagicMock()
|
|
||||||
),
|
|
||||||
self.assertLogs(detection_runners.logger, level="WARNING"),
|
|
||||||
):
|
|
||||||
runner = get_optimized_runner("/models/arcface.onnx", "AUTO", "arcface")
|
|
||||||
|
|
||||||
self.assertEqual(inference_session.call_count, 2)
|
|
||||||
self.assertIsInstance(runner, ONNXModelRunner)
|
|
||||||
self.assertEqual(loaded_devices["/models/arcface.onnx"], ("arcface", "CPU"))
|
|
||||||
|
|
||||||
def test_unsupported_models_and_cpu_models_skip_migraphx(self):
|
|
||||||
for model_path, device, model_type in [
|
|
||||||
("/models/paddleocr.onnx", "AUTO", "paddleocr"),
|
|
||||||
("/models/dfine.onnx", "AUTO", "dfine"),
|
|
||||||
("/models/yolo.onnx", "CPU", "yolo-generic"),
|
|
||||||
]:
|
|
||||||
session = MagicMock()
|
|
||||||
session.get_providers.return_value = ["CPUExecutionProvider"]
|
|
||||||
|
|
||||||
with (
|
|
||||||
self.subTest(model_type=model_type, device=device),
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"get_migraphx_devices",
|
|
||||||
return_value=[self._device()],
|
|
||||||
) as migraphx,
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"get_ort_providers",
|
|
||||||
return_value=(["CPUExecutionProvider"], [{}]),
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners,
|
|
||||||
"is_openvino_gpu_npu_available",
|
|
||||||
return_value=False,
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners.ort, "InferenceSession", return_value=session
|
|
||||||
),
|
|
||||||
patch.object(
|
|
||||||
detection_runners, "get_ort_session_options", return_value=None
|
|
||||||
),
|
|
||||||
):
|
|
||||||
get_optimized_runner(model_path, device, model_type)
|
|
||||||
|
|
||||||
migraphx.assert_not_called()
|
|
||||||
self.assertEqual(loaded_devices[model_path], (model_type, "CPU"))
|
|
||||||
|
|
||||||
def test_migraphx_is_never_requested_by_name(self):
|
|
||||||
with patch.object(
|
|
||||||
model_util.ort,
|
|
||||||
"get_available_providers",
|
|
||||||
return_value=[
|
|
||||||
"AzureExecutionProvider",
|
|
||||||
"CPUExecutionProvider",
|
|
||||||
"MIGraphXExecutionProvider",
|
|
||||||
],
|
|
||||||
):
|
|
||||||
providers, options = model_util.get_ort_providers()
|
|
||||||
|
|
||||||
self.assertEqual(providers, ["CPUExecutionProvider"])
|
|
||||||
self.assertEqual(options, [{"enable_cpu_mem_arena": False}])
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
"""Tests for the same-origin check on WebSocket handshakes."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from frigate.comms.ws import OriginCheckedWebSocketApplication, WebSocket
|
||||||
|
from frigate.util.origin import origin_allowed
|
||||||
|
|
||||||
|
|
||||||
|
def ws_origin_allowed(environ: dict[str, str]) -> bool:
|
||||||
|
return origin_allowed(
|
||||||
|
environ.get("HTTP_ORIGIN"),
|
||||||
|
environ.get("HTTP_HOST"),
|
||||||
|
environ.get("HTTP_X_FORWARDED_HOST"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestOriginAllowed(unittest.TestCase):
|
||||||
|
def test_missing_origin_allowed(self):
|
||||||
|
self.assertTrue(ws_origin_allowed({"HTTP_HOST": "frigate.lan"}))
|
||||||
|
|
||||||
|
def test_direct_access_same_origin(self):
|
||||||
|
# nginx forwards Host without the port and X-Forwarded-Host with it
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "http://192.168.1.10:5000",
|
||||||
|
"HTTP_HOST": "192.168.1.10",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "192.168.1.10:5000",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_default_port_origin(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://frigate.example.com",
|
||||||
|
"HTTP_HOST": "frigate.example.com",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_host_comparison_is_case_insensitive(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://Frigate.Example.com",
|
||||||
|
"HTTP_HOST": "frigate.example.COM",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_ipv6_host(self):
|
||||||
|
environ = {"HTTP_ORIGIN": "http://[::1]:8971", "HTTP_HOST": "[::1]"}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_cross_origin_rejected(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://evil.example",
|
||||||
|
"HTTP_HOST": "frigate.lan",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "frigate.lan:5000",
|
||||||
|
}
|
||||||
|
self.assertFalse(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_null_origin_rejected(self):
|
||||||
|
self.assertFalse(
|
||||||
|
ws_origin_allowed({"HTTP_ORIGIN": "null", "HTTP_HOST": "frigate.lan"})
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_proxy_rewriting_host_with_forwarded_host(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://frigate.example.com",
|
||||||
|
"HTTP_HOST": "10.0.0.5",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "frigate.example.com",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_forwarded_host_list_uses_first_entry(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://frigate.example.com",
|
||||||
|
"HTTP_HOST": "10.0.0.5",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "frigate.example.com, proxy.internal",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
environ["HTTP_ORIGIN"] = "https://proxy.internal"
|
||||||
|
self.assertFalse(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_home_assistant_ingress(self):
|
||||||
|
# HA core copies Host and sets X-Forwarded-Host to its own host
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://ha.example.com:8123",
|
||||||
|
"HTTP_HOST": "ha.example.com",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "ha.example.com:8123",
|
||||||
|
"HTTP_X_INGRESS_PATH": "/api/hassio_ingress/abc123",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_ingress_path_does_not_bypass_origin_check(self):
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://evil.example",
|
||||||
|
"HTTP_HOST": "frigate.example.com",
|
||||||
|
"HTTP_X_INGRESS_PATH": "/frigate",
|
||||||
|
}
|
||||||
|
self.assertFalse(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
def test_home_assistant_integration_proxy(self):
|
||||||
|
# hass-web-proxy-lib drops Host, so Host is the integration's Frigate URL
|
||||||
|
environ = {
|
||||||
|
"HTTP_ORIGIN": "https://ha.example.com:8123",
|
||||||
|
"HTTP_HOST": "frigate.lan",
|
||||||
|
"HTTP_X_FORWARDED_HOST": "ha.example.com:8123",
|
||||||
|
}
|
||||||
|
self.assertTrue(ws_origin_allowed(environ))
|
||||||
|
|
||||||
|
|
||||||
|
class TestOriginCheckedWebSocketApplication(unittest.TestCase):
|
||||||
|
def test_cross_origin_handshake_gets_403(self):
|
||||||
|
app = OriginCheckedWebSocketApplication(handler_cls=WebSocket)
|
||||||
|
start_response = MagicMock()
|
||||||
|
environ = {
|
||||||
|
"REQUEST_METHOD": "GET",
|
||||||
|
"HTTP_ORIGIN": "https://evil.example",
|
||||||
|
"HTTP_HOST": "frigate.lan",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(app(environ, start_response), [])
|
||||||
|
start_response.assert_called_once()
|
||||||
|
self.assertEqual(start_response.call_args.args[0], "403 Forbidden")
|
||||||
|
self.assertNotIn("ws4py.websocket", environ)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -385,9 +385,15 @@ def get_ort_providers(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif provider == "MIGraphXExecutionProvider":
|
elif provider == "MIGraphXExecutionProvider":
|
||||||
# MIGraphX is a plugin provider, which ONNX Runtime can't create by
|
migraphx_cache_dir = os.path.join(MODEL_CACHE_DIR, "migraphx")
|
||||||
# name, so get_optimized_runner selects it through its devices
|
os.makedirs(migraphx_cache_dir, exist_ok=True)
|
||||||
continue
|
|
||||||
|
providers.append(provider)
|
||||||
|
options.append(
|
||||||
|
{
|
||||||
|
"migraphx_model_cache_dir": migraphx_cache_dir,
|
||||||
|
}
|
||||||
|
)
|
||||||
elif provider == "CPUExecutionProvider":
|
elif provider == "CPUExecutionProvider":
|
||||||
providers.append(provider)
|
providers.append(provider)
|
||||||
options.append(
|
options.append(
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""Same-origin check for browser WebSocket handshakes and go2rtc stream requests."""
|
||||||
|
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
|
||||||
|
def _hostname(value: str) -> str | None:
|
||||||
|
try:
|
||||||
|
return urlsplit(value).hostname
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def origin_allowed(
|
||||||
|
origin: str | None, host: str | None, forwarded_host: str | None
|
||||||
|
) -> bool:
|
||||||
|
"""Return True unless a browser sent the request from a foreign origin.
|
||||||
|
|
||||||
|
Browsers always send Origin on a WebSocket handshake, so a missing Origin
|
||||||
|
means a non-browser client. A page cannot add headers to a handshake, so
|
||||||
|
X-Forwarded-Host comes from a proxy. Ports are ignored because nginx
|
||||||
|
forwards Host without one.
|
||||||
|
"""
|
||||||
|
if origin is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
origin_host = _hostname(origin)
|
||||||
|
if not origin_host:
|
||||||
|
return False
|
||||||
|
|
||||||
|
hosts = ((forwarded_host or "").split(",")[0].strip(), host or "")
|
||||||
|
return any(origin_host == _hostname(f"//{h}") for h in hosts if h)
|
||||||
Generated
+426
-305
File diff suppressed because it is too large
Load Diff
+23
-22
@@ -23,28 +23,28 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cycjimmy/jsmpeg-player": "^6.1.2",
|
"@cycjimmy/jsmpeg-player": "^6.1.2",
|
||||||
"@hookform/resolvers": "^5.9.1",
|
"@hookform/resolvers": "^5.9.1",
|
||||||
"@radix-ui/react-alert-dialog": "^1.2.1",
|
"@radix-ui/react-alert-dialog": "^1.1.24",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.2.0",
|
"@radix-ui/react-aspect-ratio": "^1.1.16",
|
||||||
"@radix-ui/react-checkbox": "^1.4.1",
|
"@radix-ui/react-checkbox": "^1.3.12",
|
||||||
"@radix-ui/react-collapsible": "^1.2.1",
|
"@radix-ui/react-collapsible": "^1.1.21",
|
||||||
"@radix-ui/react-context-menu": "^2.4.1",
|
"@radix-ui/react-context-menu": "^2.3.8",
|
||||||
"@radix-ui/react-dialog": "^1.2.0",
|
"@radix-ui/react-dialog": "^1.2.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.2.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.25",
|
||||||
"@radix-ui/react-hover-card": "^1.2.1",
|
"@radix-ui/react-hover-card": "^1.1.24",
|
||||||
"@radix-ui/react-label": "^2.1.17",
|
"@radix-ui/react-label": "^2.1.16",
|
||||||
"@radix-ui/react-popover": "^1.3.1",
|
"@radix-ui/react-popover": "^1.2.0",
|
||||||
"@radix-ui/react-progress": "^1.2.1",
|
"@radix-ui/react-progress": "^1.1.17",
|
||||||
"@radix-ui/react-radio-group": "^1.5.1",
|
"@radix-ui/react-radio-group": "^1.4.8",
|
||||||
"@radix-ui/react-scroll-area": "^1.4.0",
|
"@radix-ui/react-scroll-area": "^1.3.0",
|
||||||
"@radix-ui/react-select": "^2.4.1",
|
"@radix-ui/react-select": "^2.3.8",
|
||||||
"@radix-ui/react-separator": "^1.2.0",
|
"@radix-ui/react-separator": "^1.1.16",
|
||||||
"@radix-ui/react-slider": "^1.6.1",
|
"@radix-ui/react-slider": "^1.5.0",
|
||||||
"@radix-ui/react-slot": "1.5.0",
|
"@radix-ui/react-slot": "1.4.0",
|
||||||
"@radix-ui/react-switch": "^1.4.1",
|
"@radix-ui/react-switch": "^1.3.8",
|
||||||
"@radix-ui/react-tabs": "^1.2.1",
|
"@radix-ui/react-tabs": "^1.1.22",
|
||||||
"@radix-ui/react-toggle": "^1.2.1",
|
"@radix-ui/react-toggle": "^1.1.19",
|
||||||
"@radix-ui/react-toggle-group": "^1.2.1",
|
"@radix-ui/react-toggle-group": "^1.1.20",
|
||||||
"@radix-ui/react-tooltip": "^1.4.1",
|
"@radix-ui/react-tooltip": "^1.3.0",
|
||||||
"@rjsf/core": "^6.10.1",
|
"@rjsf/core": "^6.10.1",
|
||||||
"@rjsf/shadcn": "^6.10.1",
|
"@rjsf/shadcn": "^6.10.1",
|
||||||
"@rjsf/utils": "^6.10.1",
|
"@rjsf/utils": "^6.10.1",
|
||||||
@@ -127,6 +127,7 @@
|
|||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2",
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
"@radix-ui/react-slot": "1.5.0"
|
"@radix-ui/react-popper": "1.2.8",
|
||||||
|
"@radix-ui/react-slot": "1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export function VideoPreview({
|
|||||||
}, [playerRef]);
|
}, [playerRef]);
|
||||||
|
|
||||||
const onProgressHover = useCallback(
|
const onProgressHover = useCallback(
|
||||||
(event: React.MouseEvent<HTMLElement>) => {
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!sliderRef.current) {
|
if (!sliderRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -401,7 +401,7 @@ export function InProgressPreview({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onProgressHover = useCallback(
|
const onProgressHover = useCallback(
|
||||||
(event: React.MouseEvent<HTMLElement>) => {
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (!sliderRef.current || !previewFrames) {
|
if (!sliderRef.current || !previewFrames) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
"/live": {
|
"/live": {
|
||||||
target: `ws://${proxyHost}`,
|
target: `ws://${proxyHost}`,
|
||||||
changeOrigin: true,
|
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user