From aed3793ce1b5920f74ed417e9aaaca17f277148e Mon Sep 17 00:00:00 2001 From: jon Date: Sun, 1 Mar 2026 12:17:06 -0600 Subject: [PATCH] Add RetainPolicyEnum and retain_policy config field for NVR-style storage Introduce a new RetainPolicyEnum with 'time' (default, existing behavior) and 'continuous_rollover' (fill disk, overwrite oldest) options. Add the retain_policy field to RecordConfig and include unit tests for config validation. --- frigate/config/camera/record.py | 11 +++++ frigate/test/test_config_retain_policy.py | 49 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 frigate/test/test_config_retain_policy.py diff --git a/frigate/config/camera/record.py b/frigate/config/camera/record.py index 7eae7500d..6378432db 100644 --- a/frigate/config/camera/record.py +++ b/frigate/config/camera/record.py @@ -17,6 +17,7 @@ __all__ = [ "ReviewRetainConfig", "RecordRetainConfig", "RetainModeEnum", + "RetainPolicyEnum", ] @@ -35,6 +36,11 @@ class RetainModeEnum(str, Enum): active_objects = "active_objects" +class RetainPolicyEnum(str, Enum): + time = "time" + continuous_rollover = "continuous_rollover" + + class ReviewRetainConfig(FrigateBaseModel): days: float = Field( default=10, @@ -100,6 +106,11 @@ class RecordConfig(FrigateBaseModel): title="Enable recording", description="Enable or disable recording for all cameras; can be overridden per-camera.", ) + retain_policy: RetainPolicyEnum = Field( + default=RetainPolicyEnum.time, + title="Retention policy", + description="Storage retention policy. 'time' expires recordings after configured days. 'continuous_rollover' fills available disk space and overwrites oldest recordings when space is needed.", + ) expire_interval: int = Field( default=60, title="Record cleanup interval", diff --git a/frigate/test/test_config_retain_policy.py b/frigate/test/test_config_retain_policy.py new file mode 100644 index 000000000..2b935d5e2 --- /dev/null +++ b/frigate/test/test_config_retain_policy.py @@ -0,0 +1,49 @@ +import unittest + +from frigate.config import FrigateConfig + + +class TestRetainPolicyConfig(unittest.TestCase): + def setUp(self): + self.base_config = { + "mqtt": {"host": "mqtt"}, + "cameras": { + "front_door": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": {"height": 1080, "width": 1920, "fps": 5}, + } + }, + } + + def test_default_retain_policy_is_time(self): + config = FrigateConfig(**self.base_config) + assert config.record.retain_policy.value == "time" + + def test_continuous_rollover_policy(self): + self.base_config["record"] = { + "enabled": True, + "retain_policy": "continuous_rollover", + } + config = FrigateConfig(**self.base_config) + assert config.record.retain_policy.value == "continuous_rollover" + + def test_continuous_rollover_ignores_continuous_days(self): + self.base_config["record"] = { + "enabled": True, + "retain_policy": "continuous_rollover", + "continuous": {"days": 30}, + } + config = FrigateConfig(**self.base_config) + assert config.record.retain_policy.value == "continuous_rollover" + assert config.record.continuous.days == 30 + + def test_invalid_retain_policy_rejected(self): + self.base_config["record"] = { + "retain_policy": "invalid_value", + } + with self.assertRaises(Exception): + FrigateConfig(**self.base_config)