Commit Graph

1547 Commits

Author SHA1 Message Date
Cédric Verstraeten
7459eb02ee Ensure agent home is world-readable
Set `/home/agent` permissions to 0755 during image setup so its contents remain accessible as required.
2026-08-17 11:49:22 +02:00
Cédric Verstraeten
92d3311192 Merge pull request #316 from kerberos-io/feature/srtsp-support
feature/srtsp-support
v3.11.0
2026-08-13 10:00:47 +02:00
Cédric Verstraeten
97a8c1fcaf Update README-RTSPS-TLS.md 2026-08-13 09:49:45 +02:00
Cédric Verstraeten
de5b0666bd Clarify SSL_CERT_DIR usage and provide examples for empty trust sources in README-RTSPS-TLS.md 2026-08-12 15:13:54 +00:00
Cédric Verstraeten
ffdb8b6f22 Update README-RTSPS-TLS.md with configuration details and validation steps for RTSPS 2026-08-12 15:11:13 +00:00
Cédric Verstraeten
250e3b0b20 Add detailed guide for RTSPS and TLS certificate configuration 2026-08-12 15:09:32 +00:00
Cédric Verstraeten
2bb8144e79 Update RTSPS certificate verification instructions in README 2026-08-12 14:47:24 +00:00
Cédric Verstraeten
72d4fca63c Add RTSPS support and configuration options for secure camera streams
- Updated README.md to include instructions for using RTSPS camera URLs.
- Enhanced gortsplib.go to support insecure TLS configuration for self-signed certificates.
- Added unit tests for RTSPS TLS configuration in gortsplib_test.go.
2026-08-12 14:28:01 +00:00
Cédric Verstraeten
81cd95379b Merge pull request #315 from kerberos-io/fix/backchannel-reconnect
fix/backchannel-reconnect
v3.10.0
2026-08-10 17:49:16 +02:00
Cédric Verstraeten
cd50f58138 Upgrade Go and RTSP dependencies
Upgrade to Go 1.25, gortsplib v5, and refreshed Pion dependencies. Adapt RTSP client APIs and normalize RTP timestamps for FPS tracking, with coverage for PTS conversion.
2026-08-10 15:46:02 +02:00
Cédric Verstraeten
b2f029117e Fix test cases by adding missing newlines and improving timeout handling 2026-08-10 12:52:37 +00:00
Cédric Verstraeten
db135acea9 Implement backchannel reconnection logic and enhance test coverage for write failures 2026-08-10 12:47:10 +00:00
Cédric Verstraeten
7b589b53f9 Harden RTSP backchannel streaming
Add paced, randomized RTP packetization with talkspurt markers and rollover-safe timestamps. Reconnect failed backchannel sessions with cancellable exponential backoff, initialize audio channels during bootstrap, and cover packetizer behavior with tests.
2026-08-10 14:45:18 +02:00
Cédric Verstraeten
c1740c752e Merge pull request #313 from kerberos-io/fix/moq-recovery-strategy
fix/moq-recovery-strategy
v3.9.2
2026-08-07 16:28:27 +02:00
Cédric Verstraeten
5862786381 Deduplicate repeated H.264 keyframes
Remove exact duplicate IDR NALUs during normalization and drop repeated keyframes within a short timestamp window. Add normalization statistics, logging, reset handling, and coverage for deduplication behavior.
2026-08-07 15:46:50 +02:00
Cédric Verstraeten
e8dd64f54b Enhance MoQ streaming: implement quality tier broadcasting and subscriber management 2026-08-07 13:32:20 +00:00
Cédric Verstraeten
ba96b63002 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-07 13:28:38 +02:00
Cédric Verstraeten
c7c6bcbdf2 Add live stream recovery gating
Drop stale H.264 packets until a recent keyframe arrives, with lifecycle logging and slow MoQ write diagnostics. Add focused FrameGate tests and configure the UI package registry.
2026-08-07 13:23:44 +02:00
Cédric Verstraeten
faa3b4eabb Merge pull request #312 from kerberos-io/fix/moq-double-pts-insertion
fix/moq-double-pts-insertion
v3.9.1
2026-08-06 22:46:30 +02:00
Cédric Verstraeten
f0a6eb7d98 Merge pull request #311 from kerberos-io/fix/preserve-recording-fps-precision
preserve-recording-fps-precision
2026-08-06 21:19:18 +02:00
Cédric Verstraeten
33a58cddf7 Fix live stream presentation timestamps
Use capture presentation time directly for MoQ timestamps instead of adding composition time, which is already reflected in PTS.
2026-08-06 21:05:13 +02:00
Kilian Boute
fea6d81246 prevent fps rounding 2026-08-06 17:46:38 +02:00
Cédric Verstraeten
dbff9fbc8e Merge pull request #310 from kerberos-io/feature/integrate-moq-streaming-protocol
feature/integrate-moq-streaming-protocol
v3.9.0
2026-08-05 22:40:46 +02:00
Cédric Verstraeten
63b352b5e2 Remove obsolete MoQ Dockerfile and update build workflows to streamline image creation 2026-08-05 20:36:26 +00:00
Cédric Verstraeten
2ffb210ccb Implement NormalizeH264AccessUnit for H.264 payload normalization and update live stream publishing to use the new function 2026-08-05 20:25:05 +00:00
Cédric Verstraeten
ff643d21ef Add MoQ devcontainer verification
Switch devcontainers to Debian Trixie for the required glibc version and add automated MoQ package tests, Agent linking, and version checks through a script and VS Code task.
2026-08-05 21:07:32 +02:00
Cédric Verstraeten
8f04a6d42f Add MoQ image build workflow
Build and verify MoQ images on amd64 and arm64 pull requests, and document MoQ Docker build and runtime configuration.
2026-08-05 20:54:06 +02:00
Cédric Verstraeten
4395fe2417 Add optional MoQ live-stream publisher
Adds a dedicated MoQ build path that publishes H.264 live streams to a configurable relay, with retry handling, stream selection, Annex B framing, and Docker packaging. Standard builds retain a no-op implementation.
2026-08-05 20:53:37 +02:00
Cédric Verstraeten
18392e136e Merge pull request #308 from kerberos-io/feat/recording-fps-metadata
feat: propagate recording FPS with uploads
v3.8.2
2026-08-05 15:16:25 +02:00
Cédric Verstraeten
ed916eb042 Extend recording upload metadata with duration and timestamp
The upload marker now carries filename, device key, timestamp and duration alongside FPS, populated from the finalized MP4 at recording time.

Uploads propagate the new fields: legacy uploads add X-Kerberos-Storage-Duration and X-Kerberos-Storage-Timestamp headers, and resumable (tus) uploads include duration and timestamp in Upload-Metadata. setQueuedRecordingFPSHeader is renamed to setQueuedRecordingMetadataHeaders, and decoding of historical markers remains backwards compatible.
2026-08-05 15:12:51 +02:00
Cédric Verstraeten
72b8160dc4 Use JSON .metadata markers for upload queue
Replace plain-text FPS upload markers (named after the recording file) with JSON-encoded .metadata files via a new models.RecordingUploadMetadata type. This makes the marker format extensible for future fields beyond FPS.

Backward compatibility is preserved: cleanup, upload, and FPS-lookup code now check both the new .metadata marker and the legacy same-named marker, so recordings queued by older agents are still recognized as pending and their FPS still honored.
2026-08-05 14:58:14 +02:00
Cédric Verstraeten
4f41786038 Store measured FPS in upload marker
Instead of snapshotting the configured IP camera FPS, derive the average frame rate from the samples actually committed to the finalized MP4.

Adds MP4.AverageFPS(), backed by a SampleCount that is now incremented when a video sample is successfully written, and passes that value to queueRecordingForUpload. Validation (0 < fps <= 240, finite) moves to the numeric value, and unknown FPS still produces an empty, backwards-compatible marker.
2026-08-05 14:36:40 +02:00
Cédric Verstraeten
8fb186fd6d Merge branch 'master' into feat/recording-fps-metadata 2026-08-05 14:19:53 +02:00
Cédric Verstraeten
420b8b8a01 Merge pull request #309 from kerberos-io/fix/pixel-threshold-default
fix/pixel-threshold-default
v3.8.1
2026-08-05 14:12:09 +02:00
Cédric Verstraeten
5a13416bed Upgrade ONVIF dependency to v1.2.2
Update the Kerberos ONVIF module dependency and its recorded checksums.
2026-08-05 14:09:14 +02:00
Cédric Verstraeten
704011c20b Restore motion threshold defaults
Treat nil, zero, and negative pixel-change thresholds as the historical default of 150, removing the disabled-motion behavior and adding configuration tests.
2026-08-05 14:04:46 +02:00
Kilian Boute
6683c9b994 feat: propagate recording FPS with uploads 2026-08-04 14:07:44 +00:00
Cédric Verstraeten
2092f3e49d Merge pull request #306 from kerberos-io/fix/heartbeat-offline-tus-retry
fix(cloud): prevent cameras going offline from unbounded TUS retries
v3.8.0
2026-07-28 11:26:50 +02:00
Cédric Verstraeten
d815e39e1d Merge pull request #307 from kerberos-io/fix/default-pixel-threshold-value
fix/default-pixel-threshold-value
2026-07-28 11:25:27 +02:00
Cédric Verstraeten
e2e1f8cfa8 Merge pull request #273 from sharedjourney/feature/onvif-event-stream
Feature/onvif event stream
2026-07-28 11:25:07 +02:00
Cédric Verstraeten
ba5992378e Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-28 11:19:15 +02:00
Cédric Verstraeten
2163a8e146 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-28 11:19:05 +02:00
Cédric Verstraeten
1fec49500e Refactor pixel change threshold comment formatting for clarity 2026-07-28 09:14:54 +00:00
Cédric Verstraeten
8175908073 Fix default pixel change threshold value for motion detection 2026-07-28 09:14:44 +00:00
Kilian Boute
1bcce4694d fix(cloud): prevent cameras going offline from unbounded TUS retries
Two fixes for cameras flipping to offline while capture is healthy:

- Heartbeat HTTP timeout (cloud.go): add a 30s Timeout to both
  http.Client branches so a hung heartbeat POST can no longer stall the
  heartbeat loop past the 180s window Hub uses to mark a camera offline.

- Bounded TUS retry loop (tus_client.go): refresh the retry budget only
  on genuine net progress by tracking a highWaterOffset across all
  attempts (vs a per-attempt startHighWater snapshot). A vault that keeps
  resetting the offset (persistent 409 ERR_MISMATCHED_OFFSET) now gives
  up after maxAttempts and re-queues instead of re-uploading the first
  chunk forever and saturating the uplink.

Adds regression test TestUploadVaultResumable_MismatchedOffsetGivesUp
with a loseProgress fake-server mode reproducing the cross-replica
offset-reset loop; asserts the upload terminates with a bounded PATCH
count. Root cause (vault-side cross-replica offset reset) remains
deferred to the vault repo.
2026-07-27 14:47:42 +00:00
T. Tradesman
b26f0190c6 fix(onvif): only real transitions trigger, and sanitise logged topics
Three defects in the dispatch path.

Deleted was still a trigger. The Initialized guard was a denylist of one
value, so a property removal with an active-looking payload passed
straight through. Both Initialized and Deleted are announcements about a
property, not motion starting, so accept the transitions instead:
Changed, and Unknown for the events that omit the optional attribute.

ev.Topic reached the log unmodified. It is camera-controlled, unbounded
and unfiltered, and logrus's coloured text formatter — the default —
writes the message without quoting, so an embedded newline forges whole
log entries. A compromised camera could fabricate ERROR lines or spoof
another device's id in the logs an operator is reading to diagnose that
camera. Escape control characters and bound the length; the reject path
logs every event received, so an oversized topic was also a cheap way to
evict a container's retained history.

The trigger line was logged before the send, so an event dropped on a
full channel or at shutdown left a line claiming a recording that never
started. Log it in the send case.
2026-07-23 14:55:47 +02:00
T. Tradesman
91194f5c1a fix(onvif): ignore subscription state replays as recording triggers
A camera replays the current state of every property topic as
PropertyOperation=Initialized whenever a pull-point subscription is
created. dispatchEvent looked only at Kind and State, so any motion
property that happened to be active at that moment counted as a fresh
trigger — meaning every reconnect restarts a recording, and a flapping
subscription manufactures motion with no motion.

Observed on a camera whose pull-point was being recreated every ~18s:
each recreate replayed ~90 property events, and once real motion made
the VMD property active the replays kept re-triggering it.

Rejects Initialized specifically rather than accepting only Changed.
PropertyOperation is optional per WS-Notification and absent on many
non-property events, which decode reports as PropertyUnknown; those are
real events and must still trigger.
2026-07-23 14:55:47 +02:00
T. Tradesman
57cfc90c4b feat(onvif): log the topic that triggered a recording
dispatchEvent logged only rejected events, so the topic that actually
started a recording was invisible — the only way to identify it was to
enumerate every rejected topic and reason about what was left. On a
camera emitting 18 distinct topics that is not a diagnosis.

Log the Kind and topic on the dispatch path too, at debug, matching the
reject line's shape so both sides of the decision grep the same way.
2026-07-23 14:55:47 +02:00
Sebastian Norling
357cc719a5 docs(machinery/onvif): trim event-stream comments to WHY
Audit against CLAUDE.md's 'default to no comments; only when WHY is
non-obvious'. Net ~30 lines removed.

Dropped (rot-prone or redundant)
--------------------------------
* 'matching what the pixel-diff detector emits' — references a
  sibling file's behaviour.
* 'Timestamp in seconds matches what computervision/main.go emits;
  downstream consumers (capture/main.go) tolerate...' — both
  cross-file references; classic 'will rot when the sibling
  changes'.
* 'Motion-stop wiring into the recorder state machine is tracked as
  a follow-up; today the recorder uses a fixed PostRecording
  timeout' — PR-description content masquerading as a code comment.
* 'happens only on ctx cancel today (library handles its own
  reconnect)' — 'today' is a red flag; either drop or assert via
  test, not narrate.
* dispatchEvent's first paragraph restating what the function does.
* runStreamOnce's first sentence (WHAT).
* isONVIFMotionEnabled's reference to sibling Capture fields
  ('unlike Recording / Motion / Snapshots which default to
  enabled').

Kept (real WHYs)
----------------
* The shutdown-race rationale on dispatchEvent's ctx guards.
* logStreamError's severity-mapping rationale.
* The library-handles-reconnect-but-not-initial-connect rationale
  for the backoff constants.
* The recovering-flag rationale (on-call ops use case).
* The flag-read-once invariant on HandleONVIFEventStream.
2026-07-23 11:21:23 +02:00
Sebastian Norling
4f2a96b5e1 fix(machinery/onvif): harden event-stream dispatch and add TDD coverage
Addresses the critical and important findings from the second review of
the agent integration. TDD followed locally: tests were written first
and confirmed RED against the previous implementation before the fix
turned them GREEN.

Critical fixes
--------------
* Shutdown-race panic (concurrency P0): the 3s gap between the agent's
  ctx cancel and close(HandleMotion) was reachable by a buffered event
  delivered after cancel, where dispatchEvent's send-with-default
  select would panic on the closed channel. dispatchEvent now takes
  ctx, has a pre-check after the kind/state/recording filters, and
  the send select includes a <-ctx.Done() arm. Pinned by
  TestDispatchEvent_CtxCancelledAndHandleMotionClosed_DoesNotPanic
  (asserts NotPanics; current code without the fix panics).

* No retry on initial connect (Go P0 + ops P1): previously the
  goroutine exited permanently if ConnectToOnvifDevice or
  stream.NewStream failed at agent start — a brief boot-time DNS or
  network blip silently disabled ONVIF until restart. Construction is
  now wrapped in a retry loop with exponential backoff (1s -> 5min),
  matching what cloud.HandleHeartBeat does for its ONVIF connection
  attempts. The library handles in-stream recovery already; this
  covers the gap the library cannot see.

* Strict 'true' match (Go P0): isONVIFMotionEnabled now normalises
  case and trims whitespace, so 'True', 'TRUE', ' true ' all enable
  the feature. Pinned by TestIsONVIFMotionEnabled_CaseAndWhitespace.

Important fixes
---------------
* Empty DeviceID fallback (Go P1): resolveDeviceID falls back from
  configuration.Name to camera.ONVIFXAddr to 'unknown' so log lines
  and metrics always have a useful identifier. Pinned by
  TestResolveDeviceID_FallbackChain.

* Recovery log (ops P1): the run loop tracks a 'recovering' flag set
  when an ErrPullFailed/ErrRecreateFailed lands on Errors and cleared
  on the first successful Event. Logs an Info 'event stream recovered'
  line so on-call operators can see error streaks clear, instead of
  waking up to ERROR with no closure.

* Misconfig log bumped Info -> Warning so the
  'ONVIFXAddr is empty' line stands out from the heartbeat noise.

Tests
-----
events_test.go covers the dispatch contract end-to-end:
  * Motion+Active -> HandleMotion (happy path).
  * Motion+Inactive ignored (motion-stop is a documented follow-up).
  * Non-motion kinds ignored.
  * Recording='false' gates the send.
  * Full HandleMotion drops rather than blocks.
  * Ctx-cancelled + closed HandleMotion does not panic.
  * isONVIFMotionEnabled handles case and whitespace.
  * resolveDeviceID fallback chain.

go.mod / go.sum: testify moved from indirect to direct dependency.

Deferred (out of scope for this commit, tracked as follow-ups):
  * Heartbeat surface for ONVIF state ('disabled|running|failed') —
    requires a Cloud.go change beyond this integration's scope.
  * OTel span/metric for stream lifecycle.
  * Runtime toggle without restart (config-reload).
  * Replace-directive layout documentation — separate docs commit.
2026-07-23 11:21:23 +02:00