mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
Compare commits
No commits in common. "41e290449e9efb35d250cf67c38eeffe201b3331" and "4e71a835cb734430444d4ff6c4e58c8313abcfae" have entirely different histories.
41e290449e
...
4e71a835cb
@ -44,21 +44,13 @@ go2rtc:
|
||||
|
||||
### `environment_vars`
|
||||
|
||||
This section can be used to set environment variables for those unable to modify the environment of the container, like within Home Assistant OS. Docker users should set environment variables in their `docker run` command (`-e FRIGATE_MQTT_PASSWORD=secret`) or `docker-compose.yml` file (`environment:` section) instead. Note that values set here are stored in plain text in your config file, so if the goal is to keep credentials out of your configuration, use Docker environment variables or Docker secrets instead.
|
||||
|
||||
Variables prefixed with `FRIGATE_` can be referenced in config fields that support environment variable substitution (such as MQTT host and credentials, camera stream URLs, and ONVIF host and credentials) using the `{FRIGATE_VARIABLE_NAME}` syntax.
|
||||
This section can be used to set environment variables for those unable to modify the environment of the container, like within Home Assistant OS.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
environment_vars:
|
||||
FRIGATE_MQTT_USER: my_mqtt_user
|
||||
FRIGATE_MQTT_PASSWORD: my_mqtt_password
|
||||
|
||||
mqtt:
|
||||
host: "{FRIGATE_MQTT_HOST}"
|
||||
user: "{FRIGATE_MQTT_USER}"
|
||||
password: "{FRIGATE_MQTT_PASSWORD}"
|
||||
VARIABLE_NAME: variable_value
|
||||
```
|
||||
|
||||
#### TensorFlow Thread Configuration
|
||||
|
||||
@ -50,7 +50,6 @@ Frigate supports the use of environment variables starting with `FRIGATE_` **onl
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
host: "{FRIGATE_MQTT_HOST}"
|
||||
user: "{FRIGATE_MQTT_USER}"
|
||||
password: "{FRIGATE_MQTT_PASSWORD}"
|
||||
```
|
||||
@ -61,7 +60,7 @@ mqtt:
|
||||
|
||||
```yaml
|
||||
onvif:
|
||||
host: "{FRIGATE_ONVIF_HOST}"
|
||||
host: 10.0.10.10
|
||||
port: 8000
|
||||
user: "{FRIGATE_RTSP_USER}"
|
||||
password: "{FRIGATE_RTSP_PASSWORD}"
|
||||
|
||||
@ -16,8 +16,6 @@ mqtt:
|
||||
# Optional: Enable mqtt server (default: shown below)
|
||||
enabled: True
|
||||
# Required: host name
|
||||
# NOTE: MQTT host can be specified with an environment variable or docker secrets that must begin with 'FRIGATE_'.
|
||||
# e.g. host: '{FRIGATE_MQTT_HOST}'
|
||||
host: mqtt.server.com
|
||||
# Optional: port (default: shown below)
|
||||
port: 1883
|
||||
@ -908,8 +906,6 @@ cameras:
|
||||
onvif:
|
||||
# Required: host of the camera being connected to.
|
||||
# NOTE: HTTP is assumed by default; HTTPS is supported if you specify the scheme, ex: "https://0.0.0.0".
|
||||
# NOTE: ONVIF host, user, and password can be specified with environment variables or docker secrets
|
||||
# that must begin with 'FRIGATE_'. e.g. host: '{FRIGATE_ONVIF_HOST}'
|
||||
host: 0.0.0.0
|
||||
# Optional: ONVIF port for device (default: shown below).
|
||||
port: 8000
|
||||
|
||||
@ -1186,12 +1186,11 @@ async def event_thumbnail(
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
img_as_np = np.frombuffer(thumbnail_bytes, dtype=np.uint8)
|
||||
img = cv2.imdecode(img_as_np, flags=1)
|
||||
|
||||
# android notifications prefer a 2:1 ratio
|
||||
if format == "android":
|
||||
img = cv2.copyMakeBorder(
|
||||
img_as_np = np.frombuffer(thumbnail_bytes, dtype=np.uint8)
|
||||
img = cv2.imdecode(img_as_np, flags=1)
|
||||
thumbnail = cv2.copyMakeBorder(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
@ -1201,14 +1200,14 @@ async def event_thumbnail(
|
||||
(0, 0, 0),
|
||||
)
|
||||
|
||||
quality_params = None
|
||||
if extension in (Extension.jpg, Extension.jpeg):
|
||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), 70]
|
||||
elif extension == Extension.webp:
|
||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
||||
quality_params = None
|
||||
if extension in (Extension.jpg, Extension.jpeg):
|
||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), 70]
|
||||
elif extension == Extension.webp:
|
||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
||||
|
||||
_, encoded = cv2.imencode(f".{extension.value}", img, quality_params)
|
||||
thumbnail_bytes = encoded.tobytes()
|
||||
_, img = cv2.imencode(f".{extension.value}", thumbnail, quality_params)
|
||||
thumbnail_bytes = img.tobytes()
|
||||
|
||||
return Response(
|
||||
thumbnail_bytes,
|
||||
@ -1532,25 +1531,25 @@ def preview_gif(
|
||||
):
|
||||
if datetime.fromtimestamp(start_ts) < datetime.now().replace(minute=0, second=0):
|
||||
# has preview mp4
|
||||
try:
|
||||
preview: Previews = (
|
||||
Previews.select(
|
||||
Previews.camera,
|
||||
Previews.path,
|
||||
Previews.duration,
|
||||
Previews.start_time,
|
||||
Previews.end_time,
|
||||
)
|
||||
.where(
|
||||
Previews.start_time.between(start_ts, end_ts)
|
||||
| Previews.end_time.between(start_ts, end_ts)
|
||||
| ((start_ts > Previews.start_time) & (end_ts < Previews.end_time))
|
||||
)
|
||||
.where(Previews.camera == camera_name)
|
||||
.limit(1)
|
||||
.get()
|
||||
preview: Previews = (
|
||||
Previews.select(
|
||||
Previews.camera,
|
||||
Previews.path,
|
||||
Previews.duration,
|
||||
Previews.start_time,
|
||||
Previews.end_time,
|
||||
)
|
||||
except DoesNotExist:
|
||||
.where(
|
||||
Previews.start_time.between(start_ts, end_ts)
|
||||
| Previews.end_time.between(start_ts, end_ts)
|
||||
| ((start_ts > Previews.start_time) & (end_ts < Previews.end_time))
|
||||
)
|
||||
.where(Previews.camera == camera_name)
|
||||
.limit(1)
|
||||
.get()
|
||||
)
|
||||
|
||||
if not preview:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Preview not found"},
|
||||
status_code=404,
|
||||
|
||||
@ -72,7 +72,7 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
||||
|
||||
|
||||
class OnvifConfig(FrigateBaseModel):
|
||||
host: EnvString = Field(default="", title="Onvif Host")
|
||||
host: str = Field(default="", title="Onvif Host")
|
||||
port: int = Field(default=8000, title="Onvif Port")
|
||||
user: Optional[EnvString] = Field(default=None, title="Onvif Username")
|
||||
password: Optional[EnvString] = Field(default=None, title="Onvif Password")
|
||||
|
||||
@ -24,10 +24,8 @@ EnvString = Annotated[str, AfterValidator(validate_env_string)]
|
||||
|
||||
def validate_env_vars(v: dict[str, str], info: ValidationInfo) -> dict[str, str]:
|
||||
if isinstance(info.context, dict) and info.context.get("install", False):
|
||||
for k, val in v.items():
|
||||
os.environ[k] = val
|
||||
if k.startswith("FRIGATE_"):
|
||||
FRIGATE_ENV_VARS[k] = val
|
||||
for k, v in v.items():
|
||||
os.environ[k] = v
|
||||
|
||||
return v
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ __all__ = ["MqttConfig"]
|
||||
|
||||
class MqttConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=True, title="Enable MQTT Communication.")
|
||||
host: EnvString = Field(default="", title="MQTT Host")
|
||||
host: str = Field(default="", title="MQTT Host")
|
||||
port: int = Field(default=1883, title="MQTT Port")
|
||||
topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix")
|
||||
client_id: str = Field(default="frigate", title="MQTT Client ID")
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
"""Tests for environment variable handling."""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from frigate.config.env import (
|
||||
FRIGATE_ENV_VARS,
|
||||
validate_env_string,
|
||||
validate_env_vars,
|
||||
)
|
||||
|
||||
|
||||
class TestEnvString(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._original_env_vars = dict(FRIGATE_ENV_VARS)
|
||||
|
||||
def tearDown(self):
|
||||
FRIGATE_ENV_VARS.clear()
|
||||
FRIGATE_ENV_VARS.update(self._original_env_vars)
|
||||
|
||||
def test_substitution(self):
|
||||
"""EnvString substitutes FRIGATE_ env vars."""
|
||||
FRIGATE_ENV_VARS["FRIGATE_TEST_HOST"] = "192.168.1.100"
|
||||
result = validate_env_string("{FRIGATE_TEST_HOST}")
|
||||
self.assertEqual(result, "192.168.1.100")
|
||||
|
||||
def test_substitution_in_url(self):
|
||||
"""EnvString substitutes vars embedded in a URL."""
|
||||
FRIGATE_ENV_VARS["FRIGATE_CAM_USER"] = "admin"
|
||||
FRIGATE_ENV_VARS["FRIGATE_CAM_PASS"] = "secret"
|
||||
result = validate_env_string(
|
||||
"rtsp://{FRIGATE_CAM_USER}:{FRIGATE_CAM_PASS}@10.0.0.1/stream"
|
||||
)
|
||||
self.assertEqual(result, "rtsp://admin:secret@10.0.0.1/stream")
|
||||
|
||||
def test_no_placeholder(self):
|
||||
"""Plain strings pass through unchanged."""
|
||||
result = validate_env_string("192.168.1.1")
|
||||
self.assertEqual(result, "192.168.1.1")
|
||||
|
||||
def test_unknown_var_raises(self):
|
||||
"""Referencing an unknown var raises KeyError."""
|
||||
with self.assertRaises(KeyError):
|
||||
validate_env_string("{FRIGATE_NONEXISTENT_VAR}")
|
||||
|
||||
|
||||
class TestEnvVars(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._original_env_vars = dict(FRIGATE_ENV_VARS)
|
||||
self._original_environ = os.environ.copy()
|
||||
|
||||
def tearDown(self):
|
||||
FRIGATE_ENV_VARS.clear()
|
||||
FRIGATE_ENV_VARS.update(self._original_env_vars)
|
||||
# Clean up any env vars we set
|
||||
for key in list(os.environ.keys()):
|
||||
if key not in self._original_environ:
|
||||
del os.environ[key]
|
||||
|
||||
def _make_context(self, install: bool):
|
||||
"""Create a mock ValidationInfo with the given install flag."""
|
||||
|
||||
class MockContext:
|
||||
def __init__(self, ctx):
|
||||
self.context = ctx
|
||||
|
||||
mock = MockContext({"install": install})
|
||||
return mock
|
||||
|
||||
def test_install_sets_os_environ(self):
|
||||
"""validate_env_vars with install=True sets os.environ."""
|
||||
ctx = self._make_context(install=True)
|
||||
validate_env_vars({"MY_CUSTOM_VAR": "value123"}, ctx)
|
||||
self.assertEqual(os.environ.get("MY_CUSTOM_VAR"), "value123")
|
||||
|
||||
def test_install_updates_frigate_env_vars(self):
|
||||
"""validate_env_vars with install=True updates FRIGATE_ENV_VARS for FRIGATE_ keys."""
|
||||
ctx = self._make_context(install=True)
|
||||
validate_env_vars({"FRIGATE_MQTT_PASS": "secret"}, ctx)
|
||||
self.assertEqual(FRIGATE_ENV_VARS["FRIGATE_MQTT_PASS"], "secret")
|
||||
|
||||
def test_install_skips_non_frigate_in_env_vars_dict(self):
|
||||
"""Non-FRIGATE_ keys are set in os.environ but not in FRIGATE_ENV_VARS."""
|
||||
ctx = self._make_context(install=True)
|
||||
validate_env_vars({"OTHER_VAR": "value"}, ctx)
|
||||
self.assertEqual(os.environ.get("OTHER_VAR"), "value")
|
||||
self.assertNotIn("OTHER_VAR", FRIGATE_ENV_VARS)
|
||||
|
||||
def test_no_install_does_not_set(self):
|
||||
"""validate_env_vars without install=True does not modify state."""
|
||||
ctx = self._make_context(install=False)
|
||||
validate_env_vars({"FRIGATE_SKIP": "nope"}, ctx)
|
||||
self.assertNotIn("FRIGATE_SKIP", FRIGATE_ENV_VARS)
|
||||
self.assertNotIn("FRIGATE_SKIP", os.environ)
|
||||
|
||||
def test_env_vars_available_for_env_string(self):
|
||||
"""Vars set via validate_env_vars are usable in validate_env_string."""
|
||||
ctx = self._make_context(install=True)
|
||||
validate_env_vars({"FRIGATE_BROKER": "mqtt.local"}, ctx)
|
||||
result = validate_env_string("{FRIGATE_BROKER}")
|
||||
self.assertEqual(result, "mqtt.local")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue
Block a user