mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-09 08:37:37 +03:00
refactor env var handling
- use shared helper - use left-to-right parser
This commit is contained in:
parent
ed3bebc967
commit
20d76fc0ef
@ -9,6 +9,7 @@ from typing import Any
|
|||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
sys.path.insert(0, "/opt/frigate")
|
sys.path.insert(0, "/opt/frigate")
|
||||||
|
from frigate.config.env import substitute_frigate_vars
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
BIRDSEYE_PIPE,
|
BIRDSEYE_PIPE,
|
||||||
DEFAULT_FFMPEG_VERSION,
|
DEFAULT_FFMPEG_VERSION,
|
||||||
@ -47,14 +48,6 @@ ALLOW_ARBITRARY_EXEC = allow_arbitrary_exec is not None and str(
|
|||||||
allow_arbitrary_exec
|
allow_arbitrary_exec
|
||||||
).lower() in ("true", "1", "yes")
|
).lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
|
|
||||||
# read docker secret files as env vars too
|
|
||||||
if os.path.isdir("/run/secrets"):
|
|
||||||
for secret_file in os.listdir("/run/secrets"):
|
|
||||||
if secret_file.startswith("FRIGATE_"):
|
|
||||||
FRIGATE_ENV_VARS[secret_file] = (
|
|
||||||
Path(os.path.join("/run/secrets", secret_file)).read_text().strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
config_file = find_config_file()
|
config_file = find_config_file()
|
||||||
|
|
||||||
@ -103,13 +96,13 @@ if go2rtc_config["webrtc"].get("candidates") is None:
|
|||||||
go2rtc_config["webrtc"]["candidates"] = default_candidates
|
go2rtc_config["webrtc"]["candidates"] = default_candidates
|
||||||
|
|
||||||
if go2rtc_config.get("rtsp", {}).get("username") is not None:
|
if go2rtc_config.get("rtsp", {}).get("username") is not None:
|
||||||
go2rtc_config["rtsp"]["username"] = go2rtc_config["rtsp"]["username"].format(
|
go2rtc_config["rtsp"]["username"] = substitute_frigate_vars(
|
||||||
**FRIGATE_ENV_VARS
|
go2rtc_config["rtsp"]["username"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
||||||
go2rtc_config["rtsp"]["password"] = go2rtc_config["rtsp"]["password"].format(
|
go2rtc_config["rtsp"]["password"] = substitute_frigate_vars(
|
||||||
**FRIGATE_ENV_VARS
|
go2rtc_config["rtsp"]["password"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# ensure ffmpeg path is set correctly
|
# ensure ffmpeg path is set correctly
|
||||||
@ -145,7 +138,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
|||||||
|
|
||||||
if isinstance(stream, str):
|
if isinstance(stream, str):
|
||||||
try:
|
try:
|
||||||
formatted_stream = stream.format(**FRIGATE_ENV_VARS)
|
formatted_stream = substitute_frigate_vars(stream)
|
||||||
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
||||||
print(
|
print(
|
||||||
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||||
@ -164,7 +157,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
|||||||
filtered_streams = []
|
filtered_streams = []
|
||||||
for i, stream_item in enumerate(stream):
|
for i, stream_item in enumerate(stream):
|
||||||
try:
|
try:
|
||||||
formatted_stream = stream_item.format(**FRIGATE_ENV_VARS)
|
formatted_stream = substitute_frigate_vars(stream_item)
|
||||||
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
||||||
print(
|
print(
|
||||||
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||||
|
|||||||
@ -30,7 +30,7 @@ from frigate.config.camera.updater import (
|
|||||||
CameraConfigUpdateEnum,
|
CameraConfigUpdateEnum,
|
||||||
CameraConfigUpdateTopic,
|
CameraConfigUpdateTopic,
|
||||||
)
|
)
|
||||||
from frigate.config.env import FRIGATE_ENV_VARS
|
from frigate.config.env import substitute_frigate_vars
|
||||||
from frigate.util.builtin import clean_camera_user_pass
|
from frigate.util.builtin import clean_camera_user_pass
|
||||||
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
||||||
from frigate.util.config import find_config_file
|
from frigate.util.config import find_config_file
|
||||||
@ -126,7 +126,7 @@ def go2rtc_add_stream(request: Request, stream_name: str, src: str = ""):
|
|||||||
params = {"name": stream_name}
|
params = {"name": stream_name}
|
||||||
if src:
|
if src:
|
||||||
try:
|
try:
|
||||||
params["src"] = src.format(**FRIGATE_ENV_VARS)
|
params["src"] = substitute_frigate_vars(src)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
params["src"] = src
|
params["src"] = src
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
@ -15,8 +16,77 @@ if os.path.isdir(secrets_dir) and os.access(secrets_dir, os.R_OK):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Matches a FRIGATE_* identifier following an opening brace.
|
||||||
|
_FRIGATE_IDENT_RE = re.compile(r"FRIGATE_[A-Za-z0-9_]+")
|
||||||
|
|
||||||
|
|
||||||
|
def substitute_frigate_vars(value: str) -> str:
|
||||||
|
"""Substitute `{FRIGATE_*}` placeholders in *value*.
|
||||||
|
|
||||||
|
Reproduces the subset of `str.format()` brace semantics that Frigate's
|
||||||
|
config has historically supported, while leaving unrelated brace content
|
||||||
|
(e.g. ffmpeg `%{localtime\\:...}` expressions) untouched:
|
||||||
|
|
||||||
|
* `{{` and `}}` collapse to literal `{` / `}` (the documented escape).
|
||||||
|
* `{FRIGATE_NAME}` is replaced from `FRIGATE_ENV_VARS`; an unknown name
|
||||||
|
raises `KeyError` to preserve the existing "Invalid substitution"
|
||||||
|
error path.
|
||||||
|
* A `{` that begins `{FRIGATE_` but is not a well-formed
|
||||||
|
`{FRIGATE_NAME}` placeholder raises `ValueError` (malformed
|
||||||
|
placeholder). Callers that catch `KeyError` to allow unknown-var
|
||||||
|
passthrough will still surface malformed syntax as an error.
|
||||||
|
* Any other `{` or `}` is treated as a literal and passed through.
|
||||||
|
"""
|
||||||
|
out: list[str] = []
|
||||||
|
i = 0
|
||||||
|
n = len(value)
|
||||||
|
while i < n:
|
||||||
|
ch = value[i]
|
||||||
|
if ch == "{":
|
||||||
|
# Escaped literal `{{`.
|
||||||
|
if i + 1 < n and value[i + 1] == "{":
|
||||||
|
out.append("{")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
# Possible `{FRIGATE_*}` placeholder.
|
||||||
|
if value.startswith("{FRIGATE_", i):
|
||||||
|
ident_match = _FRIGATE_IDENT_RE.match(value, i + 1)
|
||||||
|
if (
|
||||||
|
ident_match is not None
|
||||||
|
and ident_match.end() < n
|
||||||
|
and value[ident_match.end()] == "}"
|
||||||
|
):
|
||||||
|
key = ident_match.group(0)
|
||||||
|
if key not in FRIGATE_ENV_VARS:
|
||||||
|
raise KeyError(key)
|
||||||
|
out.append(FRIGATE_ENV_VARS[key])
|
||||||
|
i = ident_match.end() + 1
|
||||||
|
continue
|
||||||
|
# Looks like a FRIGATE placeholder but is malformed
|
||||||
|
# (no closing brace, illegal char, format spec, etc.).
|
||||||
|
raise ValueError(
|
||||||
|
f"Malformed FRIGATE_ placeholder near {value[i : i + 32]!r}"
|
||||||
|
)
|
||||||
|
# Plain `{` — pass through (e.g. `%{localtime\:...}`).
|
||||||
|
out.append("{")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == "}":
|
||||||
|
# Escaped literal `}}`.
|
||||||
|
if i + 1 < n and value[i + 1] == "}":
|
||||||
|
out.append("}")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
out.append("}")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
def validate_env_string(v: str) -> str:
|
def validate_env_string(v: str) -> str:
|
||||||
return v.format(**FRIGATE_ENV_VARS)
|
return substitute_frigate_vars(v)
|
||||||
|
|
||||||
|
|
||||||
EnvString = Annotated[str, AfterValidator(validate_env_string)]
|
EnvString = Annotated[str, AfterValidator(validate_env_string)]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user