mirror of
https://github.com/ep1cman/unifi-protect-backup.git
synced 2025-12-05 23:53:30 +00:00
Split retention and missing range
This commit is contained in:
20
README.md
20
README.md
@@ -23,7 +23,7 @@ retention period.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Listens to events in real-time via the Unifi Protect websocket API
|
- Listens to events in real-time via the Unifi Protect websocket API
|
||||||
- Ensures any previous and/or missed events within the retention period are also backed up
|
- Ensures any previous and/or missed events within the missing range are also backed up
|
||||||
- Supports uploading to a [wide range of storage systems using `rclone`](https://rclone.org/overview/)
|
- Supports uploading to a [wide range of storage systems using `rclone`](https://rclone.org/overview/)
|
||||||
- Automatic pruning of old clips
|
- Automatic pruning of old clips
|
||||||
|
|
||||||
@@ -123,6 +123,10 @@ Options:
|
|||||||
`--max-age` argument of `rclone`
|
`--max-age` argument of `rclone`
|
||||||
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-
|
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-
|
||||||
older-than-this) [default: 7d]
|
older-than-this) [default: 7d]
|
||||||
|
--missing-range TEXT How far back should missing events be checked for. Defaults to
|
||||||
|
the same as the retention time. Format as per the `--max-age`
|
||||||
|
argument of `rclone` (https://rclone.org/filtering/#max-age-don-
|
||||||
|
t-transfer-any-file-older-than-this)
|
||||||
--rclone-args TEXT Optional extra arguments to pass to `rclone rcat` directly.
|
--rclone-args TEXT Optional extra arguments to pass to `rclone rcat` directly.
|
||||||
Common usage for this would be to set a bandwidth limit, for
|
Common usage for this would be to set a bandwidth limit, for
|
||||||
example.
|
example.
|
||||||
@@ -131,14 +135,21 @@ Options:
|
|||||||
instead of using the recycle bin on a destination. Google Drive
|
instead of using the recycle bin on a destination. Google Drive
|
||||||
example: `--drive-use-trash=false`
|
example: `--drive-use-trash=false`
|
||||||
--detection-types TEXT A comma separated list of which types of detections to backup.
|
--detection-types TEXT A comma separated list of which types of detections to backup.
|
||||||
Valid options are: `motion`, `person`, `vehicle`, `ring`
|
Valid options are: `motion`, `ring`, `line`, `fingerprint`,
|
||||||
[default: motion,person,vehicle,ring]
|
`nfc`, `person`, `animal`, `vehicle`, `licensePlate`, `package`,
|
||||||
|
`face`, `car`, `pet`, `alrmSmoke`, `alrmCmonx`, `smoke_cmonx`,
|
||||||
|
`alrmSiren`, `alrmBabyCry`, `alrmSpeak`, `alrmBark`,
|
||||||
|
`alrmBurglar`, `alrmCarHorn`, `alrmGlassBreak` [default: motion
|
||||||
|
,ring,line,fingerprint,nfc,person,animal,vehicle,licensePlate,pa
|
||||||
|
ckage,face,car,pet,alrmSmoke,alrmCmonx,smoke_cmonx,alrmSiren,alr
|
||||||
|
mBabyCry,alrmSpeak,alrmBark,alrmBurglar,alrmCarHorn,alrmGlassBre
|
||||||
|
ak]
|
||||||
--ignore-camera TEXT IDs of cameras for which events should not be backed up. Use
|
--ignore-camera TEXT IDs of cameras for which events should not be backed up. Use
|
||||||
multiple times to ignore multiple IDs. If being set as an
|
multiple times to ignore multiple IDs. If being set as an
|
||||||
environment variable the IDs should be separated by whitespace.
|
environment variable the IDs should be separated by whitespace.
|
||||||
Alternatively, use a Unifi user with a role which has access
|
Alternatively, use a Unifi user with a role which has access
|
||||||
restricted to the subset of cameras that you wish to backup.
|
restricted to the subset of cameras that you wish to backup.
|
||||||
--camera TEXT IDs of *ONLY* cameras for which events should be backed up. Use
|
--camera TEXT IDs of *ONLY* cameras for which events should be backed up. Use
|
||||||
multiple times to include multiple IDs. If being set as an
|
multiple times to include multiple IDs. If being set as an
|
||||||
environment variable the IDs should be separated by whitespace.
|
environment variable the IDs should be separated by whitespace.
|
||||||
Alternatively, use a Unifi user with a role which has access
|
Alternatively, use a Unifi user with a role which has access
|
||||||
@@ -214,6 +225,7 @@ always take priority over environment variables):
|
|||||||
- `UFP_PORT`
|
- `UFP_PORT`
|
||||||
- `UFP_SSL_VERIFY`
|
- `UFP_SSL_VERIFY`
|
||||||
- `RCLONE_RETENTION`
|
- `RCLONE_RETENTION`
|
||||||
|
- `MISSING_RANGE`
|
||||||
- `RCLONE_DESTINATION`
|
- `RCLONE_DESTINATION`
|
||||||
- `RCLONE_ARGS`
|
- `RCLONE_ARGS`
|
||||||
- `RCLONE_PURGE_ARGS`
|
- `RCLONE_PURGE_ARGS`
|
||||||
|
|||||||
@@ -30,8 +30,11 @@ def _parse_detection_types(ctx, param, value):
|
|||||||
return types
|
return types
|
||||||
|
|
||||||
|
|
||||||
def parse_rclone_retention(ctx, param, retention) -> relativedelta:
|
def parse_rclone_retention(ctx, param, retention) -> relativedelta | None:
|
||||||
"""Parse the rclone `retention` parameter into a relativedelta which can then be used to calculate datetimes."""
|
"""Parse the rclone `retention` parameter into a relativedelta which can then be used to calculate datetimes."""
|
||||||
|
if retention is None:
|
||||||
|
return None
|
||||||
|
|
||||||
matches = {k: int(v) for v, k in re.findall(r"([\d]+)(ms|s|m|h|d|w|M|y)", retention)}
|
matches = {k: int(v) for v, k in re.findall(r"([\d]+)(ms|s|m|h|d|w|M|y)", retention)}
|
||||||
|
|
||||||
# Check that we matched the whole string
|
# Check that we matched the whole string
|
||||||
@@ -79,6 +82,15 @@ def parse_rclone_retention(ctx, param, retention) -> relativedelta:
|
|||||||
"(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)",
|
"(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)",
|
||||||
callback=parse_rclone_retention,
|
callback=parse_rclone_retention,
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--missing-range",
|
||||||
|
default=None,
|
||||||
|
envvar="MISSING_RANGE",
|
||||||
|
help="How far back should missing events be checked for. Defaults to the same as the retention time. "
|
||||||
|
"Format as per the `--max-age` argument of `rclone` "
|
||||||
|
"(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)",
|
||||||
|
callback=parse_rclone_retention,
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--rclone-args",
|
"--rclone-args",
|
||||||
default="",
|
default="",
|
||||||
@@ -261,6 +273,9 @@ def main(**kwargs):
|
|||||||
)
|
)
|
||||||
raise SystemExit(200) # throw 200 = arg error, service will not be restarted (docker)
|
raise SystemExit(200) # throw 200 = arg error, service will not be restarted (docker)
|
||||||
|
|
||||||
|
if kwargs.get("missing_range") is None:
|
||||||
|
kwargs["missing_range"] = kwargs.get("retention")
|
||||||
|
|
||||||
# Only create the event listener and run if validation passes
|
# Only create the event listener and run if validation passes
|
||||||
event_listener = UnifiProtectBackup(**kwargs)
|
event_listener = UnifiProtectBackup(**kwargs)
|
||||||
run(event_listener.start(), stop_on_unhandled_errors=True)
|
run(event_listener.start(), stop_on_unhandled_errors=True)
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class UnifiProtectBackup:
|
|||||||
verify_ssl: bool,
|
verify_ssl: bool,
|
||||||
rclone_destination: str,
|
rclone_destination: str,
|
||||||
retention: relativedelta,
|
retention: relativedelta,
|
||||||
|
missing_range: relativedelta,
|
||||||
rclone_args: str,
|
rclone_args: str,
|
||||||
rclone_purge_args: str,
|
rclone_purge_args: str,
|
||||||
detection_types: List[str],
|
detection_types: List[str],
|
||||||
@@ -98,9 +99,13 @@ class UnifiProtectBackup:
|
|||||||
rclone_destination (str): `rclone` destination path in the format
|
rclone_destination (str): `rclone` destination path in the format
|
||||||
{rclone remote}:{path on remote}. E.g.
|
{rclone remote}:{path on remote}. E.g.
|
||||||
`gdrive:/backups/unifi_protect`
|
`gdrive:/backups/unifi_protect`
|
||||||
retention (str): How long should event clips be backed up for. Format as per the
|
retention (relativedelta): How long should event clips be backed up for. Format as per the
|
||||||
`--max-age` argument of `rclone`
|
`--max-age` argument of `rclone`
|
||||||
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)
|
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)
|
||||||
|
missing_range (relativedelta): How far back should missing events be checked for. Defaults to
|
||||||
|
the same as the retention time. Format as per the
|
||||||
|
`--max-age` argument of `rclone`
|
||||||
|
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)
|
||||||
rclone_args (str): A bandwidth limit which is passed to the `--bwlimit` argument of
|
rclone_args (str): A bandwidth limit which is passed to the `--bwlimit` argument of
|
||||||
`rclone` (https://rclone.org/docs/#bwlimit-bandwidth-spec)
|
`rclone` (https://rclone.org/docs/#bwlimit-bandwidth-spec)
|
||||||
rclone_purge_args (str): Optional extra arguments to pass to `rclone delete` directly.
|
rclone_purge_args (str): Optional extra arguments to pass to `rclone delete` directly.
|
||||||
@@ -144,6 +149,7 @@ class UnifiProtectBackup:
|
|||||||
logger.debug(f" {verify_ssl=}")
|
logger.debug(f" {verify_ssl=}")
|
||||||
logger.debug(f" {rclone_destination=}")
|
logger.debug(f" {rclone_destination=}")
|
||||||
logger.debug(f" {retention=}")
|
logger.debug(f" {retention=}")
|
||||||
|
logger.debug(f" {missing_range=}")
|
||||||
logger.debug(f" {rclone_args=}")
|
logger.debug(f" {rclone_args=}")
|
||||||
logger.debug(f" {rclone_purge_args=}")
|
logger.debug(f" {rclone_purge_args=}")
|
||||||
logger.debug(f" {ignore_cameras=}")
|
logger.debug(f" {ignore_cameras=}")
|
||||||
@@ -163,6 +169,7 @@ class UnifiProtectBackup:
|
|||||||
|
|
||||||
self.rclone_destination = rclone_destination
|
self.rclone_destination = rclone_destination
|
||||||
self.retention = retention
|
self.retention = retention
|
||||||
|
self.missing_range = missing_range
|
||||||
self.rclone_args = rclone_args
|
self.rclone_args = rclone_args
|
||||||
self.rclone_purge_args = rclone_purge_args
|
self.rclone_purge_args = rclone_purge_args
|
||||||
self.file_structure_format = file_structure_format
|
self.file_structure_format = file_structure_format
|
||||||
@@ -314,15 +321,15 @@ class UnifiProtectBackup:
|
|||||||
tasks.append(purge.start())
|
tasks.append(purge.start())
|
||||||
|
|
||||||
# Create missing event task
|
# Create missing event task
|
||||||
# This will check all the events within the retention period, if any have been missed and not backed up
|
# This will check all the events within the missing_range period, if any have been missed and not
|
||||||
# they will be added to the event queue
|
# backed up. they will be added to the event queue
|
||||||
missing = MissingEventChecker(
|
missing = MissingEventChecker(
|
||||||
self._protect,
|
self._protect,
|
||||||
self._db,
|
self._db,
|
||||||
download_queue,
|
download_queue,
|
||||||
downloader,
|
downloader,
|
||||||
uploaders,
|
uploaders,
|
||||||
self.retention,
|
self.missing_range,
|
||||||
self.detection_types,
|
self.detection_types,
|
||||||
self.ignore_cameras,
|
self.ignore_cameras,
|
||||||
self.cameras,
|
self.cameras,
|
||||||
|
|||||||
Reference in New Issue
Block a user