Compare commits

..

No commits in common. "41e290449e9efb35d250cf67c38eeffe201b3331" and "4e71a835cb734430444d4ff6c4e58c8313abcfae" have entirely different histories.

8 changed files with 35 additions and 156 deletions

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -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,

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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()