mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
Environment variable fixes (#22335)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* fix environment_vars timing bug for EnvString substitution FRIGATE_ENV_VARS is populated at module import time but was never updated when environment_vars config section set new vars into os.environ. This meant HA OS users setting FRIGATE_* vars via environment_vars could not use them in EnvString fields. * add EnvString support to MQTT and ONVIF host fields * add tests * docs * update reference config
This commit is contained in:
parent
b6f78bd1f2
commit
41e290449e
@ -44,13 +44,21 @@ go2rtc:
|
|||||||
|
|
||||||
### `environment_vars`
|
### `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.
|
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.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
environment_vars:
|
environment_vars:
|
||||||
VARIABLE_NAME: variable_value
|
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}"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### TensorFlow Thread Configuration
|
#### TensorFlow Thread Configuration
|
||||||
|
|||||||
@ -50,6 +50,7 @@ Frigate supports the use of environment variables starting with `FRIGATE_` **onl
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
mqtt:
|
mqtt:
|
||||||
|
host: "{FRIGATE_MQTT_HOST}"
|
||||||
user: "{FRIGATE_MQTT_USER}"
|
user: "{FRIGATE_MQTT_USER}"
|
||||||
password: "{FRIGATE_MQTT_PASSWORD}"
|
password: "{FRIGATE_MQTT_PASSWORD}"
|
||||||
```
|
```
|
||||||
@ -60,7 +61,7 @@ mqtt:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
onvif:
|
onvif:
|
||||||
host: 10.0.10.10
|
host: "{FRIGATE_ONVIF_HOST}"
|
||||||
port: 8000
|
port: 8000
|
||||||
user: "{FRIGATE_RTSP_USER}"
|
user: "{FRIGATE_RTSP_USER}"
|
||||||
password: "{FRIGATE_RTSP_PASSWORD}"
|
password: "{FRIGATE_RTSP_PASSWORD}"
|
||||||
|
|||||||
@ -16,6 +16,8 @@ mqtt:
|
|||||||
# Optional: Enable mqtt server (default: shown below)
|
# Optional: Enable mqtt server (default: shown below)
|
||||||
enabled: True
|
enabled: True
|
||||||
# Required: host name
|
# 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
|
host: mqtt.server.com
|
||||||
# Optional: port (default: shown below)
|
# Optional: port (default: shown below)
|
||||||
port: 1883
|
port: 1883
|
||||||
@ -906,6 +908,8 @@ cameras:
|
|||||||
onvif:
|
onvif:
|
||||||
# Required: host of the camera being connected to.
|
# 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: 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
|
host: 0.0.0.0
|
||||||
# Optional: ONVIF port for device (default: shown below).
|
# Optional: ONVIF port for device (default: shown below).
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|||||||
@ -72,7 +72,7 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class OnvifConfig(FrigateBaseModel):
|
class OnvifConfig(FrigateBaseModel):
|
||||||
host: str = Field(default="", title="Onvif Host")
|
host: EnvString = Field(default="", title="Onvif Host")
|
||||||
port: int = Field(default=8000, title="Onvif Port")
|
port: int = Field(default=8000, title="Onvif Port")
|
||||||
user: Optional[EnvString] = Field(default=None, title="Onvif Username")
|
user: Optional[EnvString] = Field(default=None, title="Onvif Username")
|
||||||
password: Optional[EnvString] = Field(default=None, title="Onvif Password")
|
password: Optional[EnvString] = Field(default=None, title="Onvif Password")
|
||||||
|
|||||||
@ -24,8 +24,10 @@ EnvString = Annotated[str, AfterValidator(validate_env_string)]
|
|||||||
|
|
||||||
def validate_env_vars(v: dict[str, str], info: ValidationInfo) -> dict[str, str]:
|
def validate_env_vars(v: dict[str, str], info: ValidationInfo) -> dict[str, str]:
|
||||||
if isinstance(info.context, dict) and info.context.get("install", False):
|
if isinstance(info.context, dict) and info.context.get("install", False):
|
||||||
for k, v in v.items():
|
for k, val in v.items():
|
||||||
os.environ[k] = v
|
os.environ[k] = val
|
||||||
|
if k.startswith("FRIGATE_"):
|
||||||
|
FRIGATE_ENV_VARS[k] = val
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ __all__ = ["MqttConfig"]
|
|||||||
|
|
||||||
class MqttConfig(FrigateBaseModel):
|
class MqttConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=True, title="Enable MQTT Communication.")
|
enabled: bool = Field(default=True, title="Enable MQTT Communication.")
|
||||||
host: str = Field(default="", title="MQTT Host")
|
host: EnvString = Field(default="", title="MQTT Host")
|
||||||
port: int = Field(default=1883, title="MQTT Port")
|
port: int = Field(default=1883, title="MQTT Port")
|
||||||
topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix")
|
topic_prefix: str = Field(default="frigate", title="MQTT Topic Prefix")
|
||||||
client_id: str = Field(default="frigate", title="MQTT Client ID")
|
client_id: str = Field(default="frigate", title="MQTT Client ID")
|
||||||
|
|||||||
105
frigate/test/test_env.py
Normal file
105
frigate/test/test_env.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""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